commit 782986a6324335da7215c827a906865c1ae441a2 Author: Kazimierz CioĊ‚ek Date: Thu Feb 19 02:49:29 2026 +0100 Initial commit diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..3b1a4ca --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,12 @@ +{ + "permissions": { + "allow": [ + "Bash(flutter pub get:*)", + "Bash(flutter pub add:*)", + "Bash(dart run build_runner:*)", + "Bash(findstr:*)", + "Bash(dir /s /b \"C:\\\\Users\\\\kaziu\\\\Desktop\\\\Trainhubv2\\\\trainhub_flutter\\\\lib\\\\presentation\\\\*.dart\")", + "Bash(flutter build:*)" + ] + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..faeb7c3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,46 @@ +# 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/ +/trainhub/ + +# 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 diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..09904a9 --- /dev/null +++ b/.metadata @@ -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: "3b62efc2a3da49882f43c372e0bc53daef7295a6" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + - platform: windows + create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + + # 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' diff --git a/README.md b/README.md new file mode 100644 index 0000000..e4c5001 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# trainhub_flutter + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/analysis_options.yaml @@ -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 diff --git a/lib/core/constants/app_constants.dart b/lib/core/constants/app_constants.dart new file mode 100644 index 0000000..f296e60 --- /dev/null +++ b/lib/core/constants/app_constants.dart @@ -0,0 +1,10 @@ +class AppConstants { + AppConstants._(); + + static const String appName = 'TrainHub'; + static const double windowWidth = 1280; + static const double windowHeight = 800; + static const double minWindowWidth = 800; + static const double minWindowHeight = 600; + static const String databaseName = 'trainhub.sqlite'; +} diff --git a/lib/core/constants/ui_constants.dart b/lib/core/constants/ui_constants.dart new file mode 100644 index 0000000..00acf05 --- /dev/null +++ b/lib/core/constants/ui_constants.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; + +class UIConstants { + UIConstants._(); + + static const double pagePadding = 24.0; + static const double cardPadding = 16.0; + static const double sidebarWidth = 300.0; + static const double analysisSidebarWidth = 350.0; + static const double dialogWidth = 400.0; + + static const double spacing4 = 4.0; + static const double spacing8 = 8.0; + static const double spacing12 = 12.0; + static const double spacing16 = 16.0; + static const double spacing24 = 24.0; + static const double spacing32 = 32.0; + + static const double borderRadius = 12.0; + static const double smallBorderRadius = 8.0; + + static const Duration animationDuration = Duration(milliseconds: 200); + static const Duration snackBarDuration = Duration(seconds: 2); + + static const double navRailWidth = 80.0; + + static BorderRadius get cardBorderRadius => + BorderRadius.circular(borderRadius); + + static BorderRadius get smallCardBorderRadius => + BorderRadius.circular(smallBorderRadius); +} diff --git a/lib/core/extensions/context_extensions.dart b/lib/core/extensions/context_extensions.dart new file mode 100644 index 0000000..9548a36 --- /dev/null +++ b/lib/core/extensions/context_extensions.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:trainhub_flutter/core/theme/app_colors.dart'; + +extension BuildContextExtensions on BuildContext { + ThemeData get theme => Theme.of(this); + ColorScheme get colors => theme.colorScheme; + TextTheme get textTheme => theme.textTheme; + MediaQueryData get mediaQuery => MediaQuery.of(this); + double get screenWidth => mediaQuery.size.width; + double get screenHeight => mediaQuery.size.height; + + void showSnackBar(String message, {bool isError = false}) { + ScaffoldMessenger.of(this).showSnackBar( + SnackBar( + content: Text(message), + backgroundColor: + isError ? AppColors.destructive : AppColors.surfaceContainer, + ), + ); + } + + void showSuccessSnackBar(String message) { + ScaffoldMessenger.of(this).showSnackBar( + SnackBar( + content: Row( + children: [ + const Icon(Icons.check_circle, color: AppColors.success, size: 18), + const SizedBox(width: 8), + Expanded(child: Text(message)), + ], + ), + ), + ); + } +} diff --git a/lib/core/extensions/date_extensions.dart b/lib/core/extensions/date_extensions.dart new file mode 100644 index 0000000..3a67783 --- /dev/null +++ b/lib/core/extensions/date_extensions.dart @@ -0,0 +1,7 @@ +extension DateTimeExtensions on DateTime { + String toDisplayDate() { + return '${year.toString()}-${month.toString().padLeft(2, '0')}-${day.toString().padLeft(2, '0')}'; + } + + String toIso() => toIso8601String(); +} diff --git a/lib/core/extensions/duration_extensions.dart b/lib/core/extensions/duration_extensions.dart new file mode 100644 index 0000000..970f43f --- /dev/null +++ b/lib/core/extensions/duration_extensions.dart @@ -0,0 +1,15 @@ +extension DurationExtensions on Duration { + String toMmSs() { + final int minutes = inMinutes.remainder(60); + final int seconds = inSeconds.remainder(60); + return '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}'; + } +} + +extension IntTimeExtensions on int { + String toMmSs() { + final int minutes = this ~/ 60; + final int seconds = this % 60; + return '$minutes:${seconds.toString().padLeft(2, '0')}'; + } +} diff --git a/lib/core/router/app_router.dart b/lib/core/router/app_router.dart new file mode 100644 index 0000000..d72c55c --- /dev/null +++ b/lib/core/router/app_router.dart @@ -0,0 +1,32 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:trainhub_flutter/presentation/analysis/analysis_page.dart'; +import 'package:trainhub_flutter/presentation/calendar/calendar_page.dart'; +import 'package:trainhub_flutter/presentation/chat/chat_page.dart'; +import 'package:trainhub_flutter/presentation/home/home_page.dart'; +import 'package:trainhub_flutter/presentation/plan_editor/plan_editor_page.dart'; +import 'package:trainhub_flutter/presentation/shell/shell_page.dart'; +import 'package:trainhub_flutter/presentation/trainings/trainings_page.dart'; +import 'package:trainhub_flutter/presentation/workout_session/workout_session_page.dart'; + +part 'app_router.gr.dart'; + +@AutoRouterConfig(replaceInRouteName: 'Page|Screen,Route') +class AppRouter extends RootStackRouter { + @override + List get routes => [ + AutoRoute( + page: ShellRoute.page, + initial: true, + children: [ + AutoRoute(page: HomeRoute.page, initial: true), + AutoRoute(page: TrainingsRoute.page), + AutoRoute(page: AnalysisRoute.page), + AutoRoute(page: CalendarRoute.page), + AutoRoute(page: ChatRoute.page), + ], + ), + AutoRoute(page: PlanEditorRoute.page), + AutoRoute(page: WorkoutSessionRoute.page), + ]; +} diff --git a/lib/core/router/app_router.gr.dart b/lib/core/router/app_router.gr.dart new file mode 100644 index 0000000..78ebc59 --- /dev/null +++ b/lib/core/router/app_router.gr.dart @@ -0,0 +1,191 @@ +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// AutoRouterGenerator +// ************************************************************************** + +// ignore_for_file: type=lint +// coverage:ignore-file + +part of 'app_router.dart'; + +/// generated route for +/// [AnalysisPage] +class AnalysisRoute extends PageRouteInfo { + const AnalysisRoute({List? children}) + : super(AnalysisRoute.name, initialChildren: children); + + static const String name = 'AnalysisRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const AnalysisPage(); + }, + ); +} + +/// generated route for +/// [CalendarPage] +class CalendarRoute extends PageRouteInfo { + const CalendarRoute({List? children}) + : super(CalendarRoute.name, initialChildren: children); + + static const String name = 'CalendarRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const CalendarPage(); + }, + ); +} + +/// generated route for +/// [ChatPage] +class ChatRoute extends PageRouteInfo { + const ChatRoute({List? children}) + : super(ChatRoute.name, initialChildren: children); + + static const String name = 'ChatRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const ChatPage(); + }, + ); +} + +/// generated route for +/// [HomePage] +class HomeRoute extends PageRouteInfo { + const HomeRoute({List? children}) + : super(HomeRoute.name, initialChildren: children); + + static const String name = 'HomeRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const HomePage(); + }, + ); +} + +/// generated route for +/// [PlanEditorPage] +class PlanEditorRoute extends PageRouteInfo { + PlanEditorRoute({ + Key? key, + required String planId, + List? children, + }) : super( + PlanEditorRoute.name, + args: PlanEditorRouteArgs(key: key, planId: planId), + rawPathParams: {'planId': planId}, + initialChildren: children, + ); + + static const String name = 'PlanEditorRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final pathParams = data.inheritedPathParams; + final args = data.argsAs( + orElse: () => + PlanEditorRouteArgs(planId: pathParams.getString('planId')), + ); + return PlanEditorPage(key: args.key, planId: args.planId); + }, + ); +} + +class PlanEditorRouteArgs { + const PlanEditorRouteArgs({this.key, required this.planId}); + + final Key? key; + + final String planId; + + @override + String toString() { + return 'PlanEditorRouteArgs{key: $key, planId: $planId}'; + } +} + +/// generated route for +/// [ShellPage] +class ShellRoute extends PageRouteInfo { + const ShellRoute({List? children}) + : super(ShellRoute.name, initialChildren: children); + + static const String name = 'ShellRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const ShellPage(); + }, + ); +} + +/// generated route for +/// [TrainingsPage] +class TrainingsRoute extends PageRouteInfo { + const TrainingsRoute({List? children}) + : super(TrainingsRoute.name, initialChildren: children); + + static const String name = 'TrainingsRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const TrainingsPage(); + }, + ); +} + +/// generated route for +/// [WorkoutSessionPage] +class WorkoutSessionRoute extends PageRouteInfo { + WorkoutSessionRoute({ + Key? key, + required String planId, + List? children, + }) : super( + WorkoutSessionRoute.name, + args: WorkoutSessionRouteArgs(key: key, planId: planId), + rawPathParams: {'planId': planId}, + initialChildren: children, + ); + + static const String name = 'WorkoutSessionRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final pathParams = data.inheritedPathParams; + final args = data.argsAs( + orElse: () => + WorkoutSessionRouteArgs(planId: pathParams.getString('planId')), + ); + return WorkoutSessionPage(key: args.key, planId: args.planId); + }, + ); +} + +class WorkoutSessionRouteArgs { + const WorkoutSessionRouteArgs({this.key, required this.planId}); + + final Key? key; + + final String planId; + + @override + String toString() { + return 'WorkoutSessionRouteArgs{key: $key, planId: $planId}'; + } +} diff --git a/lib/core/theme/app_colors.dart b/lib/core/theme/app_colors.dart new file mode 100644 index 0000000..e2f5b1d --- /dev/null +++ b/lib/core/theme/app_colors.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; + +class AppColors { + AppColors._(); + + // Zinc palette + static const Color zinc50 = Color(0xFFFAFAFA); + static const Color zinc100 = Color(0xFFF4F4F5); + static const Color zinc200 = Color(0xFFE4E4E7); + static const Color zinc300 = Color(0xFFD4D4D8); + static const Color zinc400 = Color(0xFFA1A1AA); + static const Color zinc500 = Color(0xFF71717A); + static const Color zinc600 = Color(0xFF52525B); + static const Color zinc700 = Color(0xFF3F3F46); + static const Color zinc800 = Color(0xFF27272A); + static const Color zinc900 = Color(0xFF18181B); + static const Color zinc950 = Color(0xFF09090B); + + // Semantic colors + static const Color surface = zinc950; + static const Color surfaceContainer = zinc900; + static const Color surfaceContainerHigh = zinc800; + static const Color border = zinc800; + static const Color borderSubtle = Color(0xFF1F1F23); + static const Color textPrimary = zinc50; + static const Color textSecondary = zinc400; + static const Color textMuted = zinc500; + + // Accent colors + static const Color accent = Color(0xFFFF9800); + static const Color accentMuted = Color(0x33FF9800); + static const Color success = Color(0xFF22C55E); + static const Color successMuted = Color(0x3322C55E); + static const Color destructive = Color(0xFFEF4444); + static const Color destructiveMuted = Color(0x33EF4444); + static const Color info = Color(0xFF3B82F6); + static const Color infoMuted = Color(0x333B82F6); + static const Color purple = Color(0xFF8B5CF6); + static const Color purpleMuted = Color(0x338B5CF6); + static const Color warning = Color(0xFFF59E0B); +} diff --git a/lib/core/theme/app_theme.dart b/lib/core/theme/app_theme.dart new file mode 100644 index 0000000..2ba3683 --- /dev/null +++ b/lib/core/theme/app_theme.dart @@ -0,0 +1,209 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:trainhub_flutter/core/theme/app_colors.dart'; +import 'package:trainhub_flutter/core/constants/ui_constants.dart'; + +class AppTheme { + AppTheme._(); + + static final ThemeData dark = ThemeData( + useMaterial3: true, + brightness: Brightness.dark, + colorScheme: const ColorScheme.dark( + primary: AppColors.zinc50, + onPrimary: AppColors.zinc950, + secondary: AppColors.zinc200, + onSecondary: AppColors.zinc950, + surface: AppColors.zinc950, + onSurface: AppColors.zinc50, + surfaceContainer: AppColors.zinc900, + error: AppColors.destructive, + onError: AppColors.zinc50, + outline: AppColors.zinc800, + outlineVariant: AppColors.zinc700, + ), + scaffoldBackgroundColor: AppColors.surface, + textTheme: GoogleFonts.interTextTheme(ThemeData.dark().textTheme).apply( + bodyColor: AppColors.textPrimary, + displayColor: AppColors.textPrimary, + ), + cardTheme: CardThemeData( + color: AppColors.surfaceContainer, + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: UIConstants.cardBorderRadius, + side: const BorderSide(color: AppColors.border, width: 1), + ), + margin: EdgeInsets.zero, + ), + dividerTheme: const DividerThemeData( + color: AppColors.border, + thickness: 1, + space: 1, + ), + iconTheme: const IconThemeData( + color: AppColors.textSecondary, + size: 20, + ), + navigationRailTheme: NavigationRailThemeData( + backgroundColor: AppColors.surfaceContainer, + selectedIconTheme: const IconThemeData(color: AppColors.textPrimary), + unselectedIconTheme: const IconThemeData(color: AppColors.textMuted), + selectedLabelTextStyle: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w500, + color: AppColors.textPrimary, + ), + unselectedLabelTextStyle: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w400, + color: AppColors.textMuted, + ), + indicatorColor: AppColors.zinc700, + ), + inputDecorationTheme: InputDecorationTheme( + filled: true, + fillColor: AppColors.surfaceContainer, + contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), + border: OutlineInputBorder( + borderRadius: UIConstants.smallCardBorderRadius, + borderSide: const BorderSide(color: AppColors.border), + ), + enabledBorder: OutlineInputBorder( + borderRadius: UIConstants.smallCardBorderRadius, + borderSide: const BorderSide(color: AppColors.border), + ), + focusedBorder: OutlineInputBorder( + borderRadius: UIConstants.smallCardBorderRadius, + borderSide: const BorderSide(color: AppColors.zinc400, width: 1), + ), + hintStyle: GoogleFonts.inter( + color: AppColors.textMuted, + fontSize: 14, + ), + labelStyle: GoogleFonts.inter( + color: AppColors.textSecondary, + fontSize: 14, + ), + ), + dialogTheme: DialogThemeData( + backgroundColor: AppColors.surfaceContainer, + shape: RoundedRectangleBorder( + borderRadius: UIConstants.cardBorderRadius, + side: const BorderSide(color: AppColors.border), + ), + titleTextStyle: GoogleFonts.inter( + fontSize: 18, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), + ), + filledButtonTheme: FilledButtonThemeData( + style: FilledButton.styleFrom( + backgroundColor: AppColors.zinc50, + foregroundColor: AppColors.zinc950, + shape: RoundedRectangleBorder( + borderRadius: UIConstants.smallCardBorderRadius, + ), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + textStyle: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + ), + outlinedButtonTheme: OutlinedButtonThemeData( + style: OutlinedButton.styleFrom( + foregroundColor: AppColors.textPrimary, + side: const BorderSide(color: AppColors.border), + shape: RoundedRectangleBorder( + borderRadius: UIConstants.smallCardBorderRadius, + ), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + textStyle: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + ), + textButtonTheme: TextButtonThemeData( + style: TextButton.styleFrom( + foregroundColor: AppColors.textSecondary, + shape: RoundedRectangleBorder( + borderRadius: UIConstants.smallCardBorderRadius, + ), + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + textStyle: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + ), + tabBarTheme: TabBarThemeData( + labelColor: AppColors.textPrimary, + unselectedLabelColor: AppColors.textMuted, + indicatorColor: AppColors.textPrimary, + dividerColor: AppColors.border, + labelStyle: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w500, + ), + unselectedLabelStyle: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w400, + ), + ), + snackBarTheme: SnackBarThemeData( + backgroundColor: AppColors.surfaceContainer, + contentTextStyle: GoogleFonts.inter( + color: AppColors.textPrimary, + fontSize: 14, + ), + shape: RoundedRectangleBorder( + borderRadius: UIConstants.smallCardBorderRadius, + side: const BorderSide(color: AppColors.border), + ), + behavior: SnackBarBehavior.floating, + ), + bottomSheetTheme: const BottomSheetThemeData( + backgroundColor: AppColors.surfaceContainer, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(16)), + ), + ), + dropdownMenuTheme: DropdownMenuThemeData( + inputDecorationTheme: InputDecorationTheme( + filled: true, + fillColor: AppColors.surfaceContainer, + border: OutlineInputBorder( + borderRadius: UIConstants.smallCardBorderRadius, + borderSide: const BorderSide(color: AppColors.border), + ), + ), + ), + checkboxTheme: CheckboxThemeData( + fillColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return AppColors.zinc50; + } + return Colors.transparent; + }), + checkColor: WidgetStateProperty.all(AppColors.zinc950), + side: const BorderSide(color: AppColors.zinc400), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4), + ), + ), + tooltipTheme: TooltipThemeData( + decoration: BoxDecoration( + color: AppColors.zinc800, + borderRadius: BorderRadius.circular(6), + border: Border.all(color: AppColors.zinc700), + ), + textStyle: GoogleFonts.inter( + color: AppColors.textPrimary, + fontSize: 12, + ), + ), + ); +} diff --git a/lib/core/theme/app_typography.dart b/lib/core/theme/app_typography.dart new file mode 100644 index 0000000..97f0181 --- /dev/null +++ b/lib/core/theme/app_typography.dart @@ -0,0 +1,103 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:trainhub_flutter/core/theme/app_colors.dart'; + +class AppTypography { + AppTypography._(); + + static TextStyle get displayLarge => GoogleFonts.inter( + fontSize: 36, + fontWeight: FontWeight.w700, + color: AppColors.textPrimary, + height: 1.2, + ); + + static TextStyle get headlineLarge => GoogleFonts.inter( + fontSize: 28, + fontWeight: FontWeight.w700, + color: AppColors.textPrimary, + height: 1.3, + ); + + static TextStyle get headlineMedium => GoogleFonts.inter( + fontSize: 24, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + height: 1.3, + ); + + static TextStyle get titleLarge => GoogleFonts.inter( + fontSize: 20, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + height: 1.4, + ); + + static TextStyle get titleMedium => GoogleFonts.inter( + fontSize: 16, + fontWeight: FontWeight.w500, + color: AppColors.textPrimary, + height: 1.4, + ); + + static TextStyle get titleSmall => GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w500, + color: AppColors.textPrimary, + height: 1.4, + ); + + static TextStyle get bodyLarge => GoogleFonts.inter( + fontSize: 16, + fontWeight: FontWeight.w400, + color: AppColors.textPrimary, + height: 1.5, + ); + + static TextStyle get bodyMedium => GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w400, + color: AppColors.textPrimary, + height: 1.5, + ); + + static TextStyle get bodySmall => GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w400, + color: AppColors.textSecondary, + height: 1.5, + ); + + static TextStyle get labelLarge => GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w500, + color: AppColors.textPrimary, + height: 1.4, + ); + + static TextStyle get labelMedium => GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w500, + color: AppColors.textPrimary, + height: 1.4, + ); + + static TextStyle get caption => GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w400, + color: AppColors.textMuted, + height: 1.4, + ); + + static TextStyle get monoLarge => GoogleFonts.jetBrainsMono( + fontSize: 72, + fontWeight: FontWeight.w700, + color: AppColors.textPrimary, + ); + + static TextStyle get monoMedium => GoogleFonts.jetBrainsMono( + fontSize: 14, + fontWeight: FontWeight.w400, + color: AppColors.textPrimary, + ); +} diff --git a/lib/core/utils/id_generator.dart b/lib/core/utils/id_generator.dart new file mode 100644 index 0000000..31c9e3b --- /dev/null +++ b/lib/core/utils/id_generator.dart @@ -0,0 +1,9 @@ +import 'package:uuid/uuid.dart'; + +class IdGenerator { + IdGenerator._(); + + static const Uuid _uuid = Uuid(); + + static String generate() => _uuid.v4(); +} diff --git a/lib/core/utils/json_utils.dart b/lib/core/utils/json_utils.dart new file mode 100644 index 0000000..fa698db --- /dev/null +++ b/lib/core/utils/json_utils.dart @@ -0,0 +1,20 @@ +import 'dart:convert'; + +class JsonUtils { + JsonUtils._(); + + static List safeDecodeList(String? json) { + if (json == null || json.isEmpty) return []; + try { + final decoded = jsonDecode(json); + if (decoded is List) return decoded; + return []; + } catch (_) { + return []; + } + } + + static String encodeList(List> list) { + return jsonEncode(list); + } +} diff --git a/lib/data/database/app_database.dart b/lib/data/database/app_database.dart new file mode 100644 index 0000000..45d4578 --- /dev/null +++ b/lib/data/database/app_database.dart @@ -0,0 +1,152 @@ +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:path/path.dart' as p; +import 'package:trainhub_flutter/core/constants/app_constants.dart'; +import 'package:trainhub_flutter/data/database/daos/exercise_dao.dart'; +import 'package:trainhub_flutter/data/database/daos/training_plan_dao.dart'; +import 'package:trainhub_flutter/data/database/daos/program_dao.dart'; +import 'package:trainhub_flutter/data/database/daos/analysis_dao.dart'; +import 'package:trainhub_flutter/data/database/daos/chat_dao.dart'; +import 'dart:io'; + +part 'app_database.g.dart'; + +class Exercises extends Table { + TextColumn get id => text()(); + TextColumn get name => text()(); + TextColumn get instructions => text().nullable()(); + TextColumn get enrichment => text().nullable()(); + TextColumn get tags => text().nullable()(); + TextColumn get videoUrl => text().nullable()(); + + @override + Set get primaryKey => {id}; +} + +class TrainingPlans extends Table { + TextColumn get id => text()(); + TextColumn get name => text()(); + TextColumn get sections => text().nullable()(); + + @override + Set get primaryKey => {id}; +} + +class Programs extends Table { + TextColumn get id => text()(); + TextColumn get name => text()(); + TextColumn get createdAt => text()(); + + @override + Set get primaryKey => {id}; +} + +class ProgramWeeks extends Table { + TextColumn get id => text()(); + TextColumn get programId => + text().references(Programs, #id, onDelete: KeyAction.cascade)(); + IntColumn get position => integer()(); + TextColumn get notes => text().nullable()(); + + @override + Set get primaryKey => {id}; +} + +class ProgramWorkouts extends Table { + TextColumn get id => text()(); + TextColumn get weekId => + text().references(ProgramWeeks, #id, onDelete: KeyAction.cascade)(); + TextColumn get programId => + text().references(Programs, #id, onDelete: KeyAction.cascade)(); + TextColumn get day => text()(); + TextColumn get type => text()(); + TextColumn get refId => text().nullable()(); + TextColumn get name => text().nullable()(); + TextColumn get description => text().nullable()(); + BoolColumn get completed => boolean().withDefault(const Constant(false))(); + + @override + Set get primaryKey => {id}; +} + +class AnalysisSessions extends Table { + TextColumn get id => text()(); + TextColumn get name => text()(); + TextColumn get date => text()(); + TextColumn get videoPath => text().nullable()(); + + @override + Set get primaryKey => {id}; +} + +class Annotations extends Table { + TextColumn get id => text()(); + TextColumn get sessionId => + text().references(AnalysisSessions, #id, onDelete: KeyAction.cascade)(); + RealColumn get startTime => real()(); + RealColumn get endTime => real()(); + TextColumn get name => text().nullable()(); + TextColumn get description => text().nullable()(); + TextColumn get color => text().nullable()(); + + @override + Set get primaryKey => {id}; +} + +class ChatSessions extends Table { + TextColumn get id => text()(); + TextColumn get title => text().nullable()(); + TextColumn get createdAt => text()(); + TextColumn get updatedAt => text()(); + + @override + Set get primaryKey => {id}; +} + +class ChatMessages extends Table { + TextColumn get id => text()(); + TextColumn get sessionId => + text().references(ChatSessions, #id, onDelete: KeyAction.cascade)(); + TextColumn get role => text()(); + TextColumn get content => text()(); + TextColumn get createdAt => text()(); + + @override + Set get primaryKey => {id}; +} + +@DriftDatabase( + tables: [ + Exercises, + TrainingPlans, + Programs, + ProgramWeeks, + ProgramWorkouts, + AnalysisSessions, + Annotations, + ChatSessions, + ChatMessages, + ], + daos: [ + ExerciseDao, + TrainingPlanDao, + ProgramDao, + AnalysisDao, + ChatDao, + ], +) +class AppDatabase extends _$AppDatabase { + AppDatabase() : super(_openConnection()); + + @override + int get schemaVersion => 1; +} + +LazyDatabase _openConnection() { + return LazyDatabase(() async { + final dbFolder = await getApplicationDocumentsDirectory(); + final file = File(p.join(dbFolder.path, AppConstants.databaseName)); + return NativeDatabase.createInBackground(file); + }); +} diff --git a/lib/data/database/app_database.g.dart b/lib/data/database/app_database.g.dart new file mode 100644 index 0000000..cbabd97 --- /dev/null +++ b/lib/data/database/app_database.g.dart @@ -0,0 +1,6249 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'app_database.dart'; + +// ignore_for_file: type=lint +class $ExercisesTable extends Exercises + with TableInfo<$ExercisesTable, Exercise> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $ExercisesTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _instructionsMeta = const VerificationMeta( + 'instructions', + ); + @override + late final GeneratedColumn instructions = GeneratedColumn( + 'instructions', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _enrichmentMeta = const VerificationMeta( + 'enrichment', + ); + @override + late final GeneratedColumn enrichment = GeneratedColumn( + 'enrichment', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _tagsMeta = const VerificationMeta('tags'); + @override + late final GeneratedColumn tags = GeneratedColumn( + 'tags', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _videoUrlMeta = const VerificationMeta( + 'videoUrl', + ); + @override + late final GeneratedColumn videoUrl = GeneratedColumn( + 'video_url', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + name, + instructions, + enrichment, + tags, + videoUrl, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'exercises'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, + name.isAcceptableOrUnknown(data['name']!, _nameMeta), + ); + } else if (isInserting) { + context.missing(_nameMeta); + } + if (data.containsKey('instructions')) { + context.handle( + _instructionsMeta, + instructions.isAcceptableOrUnknown( + data['instructions']!, + _instructionsMeta, + ), + ); + } + if (data.containsKey('enrichment')) { + context.handle( + _enrichmentMeta, + enrichment.isAcceptableOrUnknown(data['enrichment']!, _enrichmentMeta), + ); + } + if (data.containsKey('tags')) { + context.handle( + _tagsMeta, + tags.isAcceptableOrUnknown(data['tags']!, _tagsMeta), + ); + } + if (data.containsKey('video_url')) { + context.handle( + _videoUrlMeta, + videoUrl.isAcceptableOrUnknown(data['video_url']!, _videoUrlMeta), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + Exercise map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return Exercise( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + instructions: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}instructions'], + ), + enrichment: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}enrichment'], + ), + tags: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}tags'], + ), + videoUrl: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}video_url'], + ), + ); + } + + @override + $ExercisesTable createAlias(String alias) { + return $ExercisesTable(attachedDatabase, alias); + } +} + +class Exercise extends DataClass implements Insertable { + final String id; + final String name; + final String? instructions; + final String? enrichment; + final String? tags; + final String? videoUrl; + const Exercise({ + required this.id, + required this.name, + this.instructions, + this.enrichment, + this.tags, + this.videoUrl, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + if (!nullToAbsent || instructions != null) { + map['instructions'] = Variable(instructions); + } + if (!nullToAbsent || enrichment != null) { + map['enrichment'] = Variable(enrichment); + } + if (!nullToAbsent || tags != null) { + map['tags'] = Variable(tags); + } + if (!nullToAbsent || videoUrl != null) { + map['video_url'] = Variable(videoUrl); + } + return map; + } + + ExercisesCompanion toCompanion(bool nullToAbsent) { + return ExercisesCompanion( + id: Value(id), + name: Value(name), + instructions: instructions == null && nullToAbsent + ? const Value.absent() + : Value(instructions), + enrichment: enrichment == null && nullToAbsent + ? const Value.absent() + : Value(enrichment), + tags: tags == null && nullToAbsent ? const Value.absent() : Value(tags), + videoUrl: videoUrl == null && nullToAbsent + ? const Value.absent() + : Value(videoUrl), + ); + } + + factory Exercise.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return Exercise( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + instructions: serializer.fromJson(json['instructions']), + enrichment: serializer.fromJson(json['enrichment']), + tags: serializer.fromJson(json['tags']), + videoUrl: serializer.fromJson(json['videoUrl']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'instructions': serializer.toJson(instructions), + 'enrichment': serializer.toJson(enrichment), + 'tags': serializer.toJson(tags), + 'videoUrl': serializer.toJson(videoUrl), + }; + } + + Exercise copyWith({ + String? id, + String? name, + Value instructions = const Value.absent(), + Value enrichment = const Value.absent(), + Value tags = const Value.absent(), + Value videoUrl = const Value.absent(), + }) => Exercise( + id: id ?? this.id, + name: name ?? this.name, + instructions: instructions.present ? instructions.value : this.instructions, + enrichment: enrichment.present ? enrichment.value : this.enrichment, + tags: tags.present ? tags.value : this.tags, + videoUrl: videoUrl.present ? videoUrl.value : this.videoUrl, + ); + Exercise copyWithCompanion(ExercisesCompanion data) { + return Exercise( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + instructions: data.instructions.present + ? data.instructions.value + : this.instructions, + enrichment: data.enrichment.present + ? data.enrichment.value + : this.enrichment, + tags: data.tags.present ? data.tags.value : this.tags, + videoUrl: data.videoUrl.present ? data.videoUrl.value : this.videoUrl, + ); + } + + @override + String toString() { + return (StringBuffer('Exercise(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('instructions: $instructions, ') + ..write('enrichment: $enrichment, ') + ..write('tags: $tags, ') + ..write('videoUrl: $videoUrl') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, name, instructions, enrichment, tags, videoUrl); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is Exercise && + other.id == this.id && + other.name == this.name && + other.instructions == this.instructions && + other.enrichment == this.enrichment && + other.tags == this.tags && + other.videoUrl == this.videoUrl); +} + +class ExercisesCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value instructions; + final Value enrichment; + final Value tags; + final Value videoUrl; + final Value rowid; + const ExercisesCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.instructions = const Value.absent(), + this.enrichment = const Value.absent(), + this.tags = const Value.absent(), + this.videoUrl = const Value.absent(), + this.rowid = const Value.absent(), + }); + ExercisesCompanion.insert({ + required String id, + required String name, + this.instructions = const Value.absent(), + this.enrichment = const Value.absent(), + this.tags = const Value.absent(), + this.videoUrl = const Value.absent(), + this.rowid = const Value.absent(), + }) : id = Value(id), + name = Value(name); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? instructions, + Expression? enrichment, + Expression? tags, + Expression? videoUrl, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (instructions != null) 'instructions': instructions, + if (enrichment != null) 'enrichment': enrichment, + if (tags != null) 'tags': tags, + if (videoUrl != null) 'video_url': videoUrl, + if (rowid != null) 'rowid': rowid, + }); + } + + ExercisesCompanion copyWith({ + Value? id, + Value? name, + Value? instructions, + Value? enrichment, + Value? tags, + Value? videoUrl, + Value? rowid, + }) { + return ExercisesCompanion( + id: id ?? this.id, + name: name ?? this.name, + instructions: instructions ?? this.instructions, + enrichment: enrichment ?? this.enrichment, + tags: tags ?? this.tags, + videoUrl: videoUrl ?? this.videoUrl, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (instructions.present) { + map['instructions'] = Variable(instructions.value); + } + if (enrichment.present) { + map['enrichment'] = Variable(enrichment.value); + } + if (tags.present) { + map['tags'] = Variable(tags.value); + } + if (videoUrl.present) { + map['video_url'] = Variable(videoUrl.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ExercisesCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('instructions: $instructions, ') + ..write('enrichment: $enrichment, ') + ..write('tags: $tags, ') + ..write('videoUrl: $videoUrl, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $TrainingPlansTable extends TrainingPlans + with TableInfo<$TrainingPlansTable, TrainingPlan> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $TrainingPlansTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _sectionsMeta = const VerificationMeta( + 'sections', + ); + @override + late final GeneratedColumn sections = GeneratedColumn( + 'sections', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [id, name, sections]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'training_plans'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, + name.isAcceptableOrUnknown(data['name']!, _nameMeta), + ); + } else if (isInserting) { + context.missing(_nameMeta); + } + if (data.containsKey('sections')) { + context.handle( + _sectionsMeta, + sections.isAcceptableOrUnknown(data['sections']!, _sectionsMeta), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + TrainingPlan map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return TrainingPlan( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + sections: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}sections'], + ), + ); + } + + @override + $TrainingPlansTable createAlias(String alias) { + return $TrainingPlansTable(attachedDatabase, alias); + } +} + +class TrainingPlan extends DataClass implements Insertable { + final String id; + final String name; + final String? sections; + const TrainingPlan({required this.id, required this.name, this.sections}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + if (!nullToAbsent || sections != null) { + map['sections'] = Variable(sections); + } + return map; + } + + TrainingPlansCompanion toCompanion(bool nullToAbsent) { + return TrainingPlansCompanion( + id: Value(id), + name: Value(name), + sections: sections == null && nullToAbsent + ? const Value.absent() + : Value(sections), + ); + } + + factory TrainingPlan.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return TrainingPlan( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + sections: serializer.fromJson(json['sections']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'sections': serializer.toJson(sections), + }; + } + + TrainingPlan copyWith({ + String? id, + String? name, + Value sections = const Value.absent(), + }) => TrainingPlan( + id: id ?? this.id, + name: name ?? this.name, + sections: sections.present ? sections.value : this.sections, + ); + TrainingPlan copyWithCompanion(TrainingPlansCompanion data) { + return TrainingPlan( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + sections: data.sections.present ? data.sections.value : this.sections, + ); + } + + @override + String toString() { + return (StringBuffer('TrainingPlan(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('sections: $sections') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, name, sections); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TrainingPlan && + other.id == this.id && + other.name == this.name && + other.sections == this.sections); +} + +class TrainingPlansCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value sections; + final Value rowid; + const TrainingPlansCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.sections = const Value.absent(), + this.rowid = const Value.absent(), + }); + TrainingPlansCompanion.insert({ + required String id, + required String name, + this.sections = const Value.absent(), + this.rowid = const Value.absent(), + }) : id = Value(id), + name = Value(name); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? sections, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (sections != null) 'sections': sections, + if (rowid != null) 'rowid': rowid, + }); + } + + TrainingPlansCompanion copyWith({ + Value? id, + Value? name, + Value? sections, + Value? rowid, + }) { + return TrainingPlansCompanion( + id: id ?? this.id, + name: name ?? this.name, + sections: sections ?? this.sections, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (sections.present) { + map['sections'] = Variable(sections.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TrainingPlansCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('sections: $sections, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $ProgramsTable extends Programs with TableInfo<$ProgramsTable, Program> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $ProgramsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _createdAtMeta = const VerificationMeta( + 'createdAt', + ); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [id, name, createdAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'programs'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, + name.isAcceptableOrUnknown(data['name']!, _nameMeta), + ); + } else if (isInserting) { + context.missing(_nameMeta); + } + if (data.containsKey('created_at')) { + context.handle( + _createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta), + ); + } else if (isInserting) { + context.missing(_createdAtMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + Program map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return Program( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + ); + } + + @override + $ProgramsTable createAlias(String alias) { + return $ProgramsTable(attachedDatabase, alias); + } +} + +class Program extends DataClass implements Insertable { + final String id; + final String name; + final String createdAt; + const Program({ + required this.id, + required this.name, + required this.createdAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['created_at'] = Variable(createdAt); + return map; + } + + ProgramsCompanion toCompanion(bool nullToAbsent) { + return ProgramsCompanion( + id: Value(id), + name: Value(name), + createdAt: Value(createdAt), + ); + } + + factory Program.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return Program( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + createdAt: serializer.fromJson(json['createdAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'createdAt': serializer.toJson(createdAt), + }; + } + + Program copyWith({String? id, String? name, String? createdAt}) => Program( + id: id ?? this.id, + name: name ?? this.name, + createdAt: createdAt ?? this.createdAt, + ); + Program copyWithCompanion(ProgramsCompanion data) { + return Program( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('Program(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, name, createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is Program && + other.id == this.id && + other.name == this.name && + other.createdAt == this.createdAt); +} + +class ProgramsCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value createdAt; + final Value rowid; + const ProgramsCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.createdAt = const Value.absent(), + this.rowid = const Value.absent(), + }); + ProgramsCompanion.insert({ + required String id, + required String name, + required String createdAt, + this.rowid = const Value.absent(), + }) : id = Value(id), + name = Value(name), + createdAt = Value(createdAt); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? createdAt, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (createdAt != null) 'created_at': createdAt, + if (rowid != null) 'rowid': rowid, + }); + } + + ProgramsCompanion copyWith({ + Value? id, + Value? name, + Value? createdAt, + Value? rowid, + }) { + return ProgramsCompanion( + id: id ?? this.id, + name: name ?? this.name, + createdAt: createdAt ?? this.createdAt, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ProgramsCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('createdAt: $createdAt, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $ProgramWeeksTable extends ProgramWeeks + with TableInfo<$ProgramWeeksTable, ProgramWeek> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $ProgramWeeksTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _programIdMeta = const VerificationMeta( + 'programId', + ); + @override + late final GeneratedColumn programId = GeneratedColumn( + 'program_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES programs (id) ON DELETE CASCADE', + ), + ); + static const VerificationMeta _positionMeta = const VerificationMeta( + 'position', + ); + @override + late final GeneratedColumn position = GeneratedColumn( + 'position', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + static const VerificationMeta _notesMeta = const VerificationMeta('notes'); + @override + late final GeneratedColumn notes = GeneratedColumn( + 'notes', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [id, programId, position, notes]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'program_weeks'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('program_id')) { + context.handle( + _programIdMeta, + programId.isAcceptableOrUnknown(data['program_id']!, _programIdMeta), + ); + } else if (isInserting) { + context.missing(_programIdMeta); + } + if (data.containsKey('position')) { + context.handle( + _positionMeta, + position.isAcceptableOrUnknown(data['position']!, _positionMeta), + ); + } else if (isInserting) { + context.missing(_positionMeta); + } + if (data.containsKey('notes')) { + context.handle( + _notesMeta, + notes.isAcceptableOrUnknown(data['notes']!, _notesMeta), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + ProgramWeek map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return ProgramWeek( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + programId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}program_id'], + )!, + position: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}position'], + )!, + notes: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}notes'], + ), + ); + } + + @override + $ProgramWeeksTable createAlias(String alias) { + return $ProgramWeeksTable(attachedDatabase, alias); + } +} + +class ProgramWeek extends DataClass implements Insertable { + final String id; + final String programId; + final int position; + final String? notes; + const ProgramWeek({ + required this.id, + required this.programId, + required this.position, + this.notes, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['program_id'] = Variable(programId); + map['position'] = Variable(position); + if (!nullToAbsent || notes != null) { + map['notes'] = Variable(notes); + } + return map; + } + + ProgramWeeksCompanion toCompanion(bool nullToAbsent) { + return ProgramWeeksCompanion( + id: Value(id), + programId: Value(programId), + position: Value(position), + notes: notes == null && nullToAbsent + ? const Value.absent() + : Value(notes), + ); + } + + factory ProgramWeek.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return ProgramWeek( + id: serializer.fromJson(json['id']), + programId: serializer.fromJson(json['programId']), + position: serializer.fromJson(json['position']), + notes: serializer.fromJson(json['notes']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'programId': serializer.toJson(programId), + 'position': serializer.toJson(position), + 'notes': serializer.toJson(notes), + }; + } + + ProgramWeek copyWith({ + String? id, + String? programId, + int? position, + Value notes = const Value.absent(), + }) => ProgramWeek( + id: id ?? this.id, + programId: programId ?? this.programId, + position: position ?? this.position, + notes: notes.present ? notes.value : this.notes, + ); + ProgramWeek copyWithCompanion(ProgramWeeksCompanion data) { + return ProgramWeek( + id: data.id.present ? data.id.value : this.id, + programId: data.programId.present ? data.programId.value : this.programId, + position: data.position.present ? data.position.value : this.position, + notes: data.notes.present ? data.notes.value : this.notes, + ); + } + + @override + String toString() { + return (StringBuffer('ProgramWeek(') + ..write('id: $id, ') + ..write('programId: $programId, ') + ..write('position: $position, ') + ..write('notes: $notes') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, programId, position, notes); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is ProgramWeek && + other.id == this.id && + other.programId == this.programId && + other.position == this.position && + other.notes == this.notes); +} + +class ProgramWeeksCompanion extends UpdateCompanion { + final Value id; + final Value programId; + final Value position; + final Value notes; + final Value rowid; + const ProgramWeeksCompanion({ + this.id = const Value.absent(), + this.programId = const Value.absent(), + this.position = const Value.absent(), + this.notes = const Value.absent(), + this.rowid = const Value.absent(), + }); + ProgramWeeksCompanion.insert({ + required String id, + required String programId, + required int position, + this.notes = const Value.absent(), + this.rowid = const Value.absent(), + }) : id = Value(id), + programId = Value(programId), + position = Value(position); + static Insertable custom({ + Expression? id, + Expression? programId, + Expression? position, + Expression? notes, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (programId != null) 'program_id': programId, + if (position != null) 'position': position, + if (notes != null) 'notes': notes, + if (rowid != null) 'rowid': rowid, + }); + } + + ProgramWeeksCompanion copyWith({ + Value? id, + Value? programId, + Value? position, + Value? notes, + Value? rowid, + }) { + return ProgramWeeksCompanion( + id: id ?? this.id, + programId: programId ?? this.programId, + position: position ?? this.position, + notes: notes ?? this.notes, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (programId.present) { + map['program_id'] = Variable(programId.value); + } + if (position.present) { + map['position'] = Variable(position.value); + } + if (notes.present) { + map['notes'] = Variable(notes.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ProgramWeeksCompanion(') + ..write('id: $id, ') + ..write('programId: $programId, ') + ..write('position: $position, ') + ..write('notes: $notes, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $ProgramWorkoutsTable extends ProgramWorkouts + with TableInfo<$ProgramWorkoutsTable, ProgramWorkout> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $ProgramWorkoutsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _weekIdMeta = const VerificationMeta('weekId'); + @override + late final GeneratedColumn weekId = GeneratedColumn( + 'week_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES program_weeks (id) ON DELETE CASCADE', + ), + ); + static const VerificationMeta _programIdMeta = const VerificationMeta( + 'programId', + ); + @override + late final GeneratedColumn programId = GeneratedColumn( + 'program_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES programs (id) ON DELETE CASCADE', + ), + ); + static const VerificationMeta _dayMeta = const VerificationMeta('day'); + @override + late final GeneratedColumn day = GeneratedColumn( + 'day', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _typeMeta = const VerificationMeta('type'); + @override + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _refIdMeta = const VerificationMeta('refId'); + @override + late final GeneratedColumn refId = GeneratedColumn( + 'ref_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _descriptionMeta = const VerificationMeta( + 'description', + ); + @override + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _completedMeta = const VerificationMeta( + 'completed', + ); + @override + late final GeneratedColumn completed = GeneratedColumn( + 'completed', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("completed" IN (0, 1))', + ), + defaultValue: const Constant(false), + ); + @override + List get $columns => [ + id, + weekId, + programId, + day, + type, + refId, + name, + description, + completed, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'program_workouts'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('week_id')) { + context.handle( + _weekIdMeta, + weekId.isAcceptableOrUnknown(data['week_id']!, _weekIdMeta), + ); + } else if (isInserting) { + context.missing(_weekIdMeta); + } + if (data.containsKey('program_id')) { + context.handle( + _programIdMeta, + programId.isAcceptableOrUnknown(data['program_id']!, _programIdMeta), + ); + } else if (isInserting) { + context.missing(_programIdMeta); + } + if (data.containsKey('day')) { + context.handle( + _dayMeta, + day.isAcceptableOrUnknown(data['day']!, _dayMeta), + ); + } else if (isInserting) { + context.missing(_dayMeta); + } + if (data.containsKey('type')) { + context.handle( + _typeMeta, + type.isAcceptableOrUnknown(data['type']!, _typeMeta), + ); + } else if (isInserting) { + context.missing(_typeMeta); + } + if (data.containsKey('ref_id')) { + context.handle( + _refIdMeta, + refId.isAcceptableOrUnknown(data['ref_id']!, _refIdMeta), + ); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, + name.isAcceptableOrUnknown(data['name']!, _nameMeta), + ); + } + if (data.containsKey('description')) { + context.handle( + _descriptionMeta, + description.isAcceptableOrUnknown( + data['description']!, + _descriptionMeta, + ), + ); + } + if (data.containsKey('completed')) { + context.handle( + _completedMeta, + completed.isAcceptableOrUnknown(data['completed']!, _completedMeta), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + ProgramWorkout map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return ProgramWorkout( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + weekId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}week_id'], + )!, + programId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}program_id'], + )!, + day: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}day'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}type'], + )!, + refId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}ref_id'], + ), + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + ), + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + completed: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}completed'], + )!, + ); + } + + @override + $ProgramWorkoutsTable createAlias(String alias) { + return $ProgramWorkoutsTable(attachedDatabase, alias); + } +} + +class ProgramWorkout extends DataClass implements Insertable { + final String id; + final String weekId; + final String programId; + final String day; + final String type; + final String? refId; + final String? name; + final String? description; + final bool completed; + const ProgramWorkout({ + required this.id, + required this.weekId, + required this.programId, + required this.day, + required this.type, + this.refId, + this.name, + this.description, + required this.completed, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['week_id'] = Variable(weekId); + map['program_id'] = Variable(programId); + map['day'] = Variable(day); + map['type'] = Variable(type); + if (!nullToAbsent || refId != null) { + map['ref_id'] = Variable(refId); + } + if (!nullToAbsent || name != null) { + map['name'] = Variable(name); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + map['completed'] = Variable(completed); + return map; + } + + ProgramWorkoutsCompanion toCompanion(bool nullToAbsent) { + return ProgramWorkoutsCompanion( + id: Value(id), + weekId: Value(weekId), + programId: Value(programId), + day: Value(day), + type: Value(type), + refId: refId == null && nullToAbsent + ? const Value.absent() + : Value(refId), + name: name == null && nullToAbsent ? const Value.absent() : Value(name), + description: description == null && nullToAbsent + ? const Value.absent() + : Value(description), + completed: Value(completed), + ); + } + + factory ProgramWorkout.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return ProgramWorkout( + id: serializer.fromJson(json['id']), + weekId: serializer.fromJson(json['weekId']), + programId: serializer.fromJson(json['programId']), + day: serializer.fromJson(json['day']), + type: serializer.fromJson(json['type']), + refId: serializer.fromJson(json['refId']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + completed: serializer.fromJson(json['completed']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'weekId': serializer.toJson(weekId), + 'programId': serializer.toJson(programId), + 'day': serializer.toJson(day), + 'type': serializer.toJson(type), + 'refId': serializer.toJson(refId), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'completed': serializer.toJson(completed), + }; + } + + ProgramWorkout copyWith({ + String? id, + String? weekId, + String? programId, + String? day, + String? type, + Value refId = const Value.absent(), + Value name = const Value.absent(), + Value description = const Value.absent(), + bool? completed, + }) => ProgramWorkout( + id: id ?? this.id, + weekId: weekId ?? this.weekId, + programId: programId ?? this.programId, + day: day ?? this.day, + type: type ?? this.type, + refId: refId.present ? refId.value : this.refId, + name: name.present ? name.value : this.name, + description: description.present ? description.value : this.description, + completed: completed ?? this.completed, + ); + ProgramWorkout copyWithCompanion(ProgramWorkoutsCompanion data) { + return ProgramWorkout( + id: data.id.present ? data.id.value : this.id, + weekId: data.weekId.present ? data.weekId.value : this.weekId, + programId: data.programId.present ? data.programId.value : this.programId, + day: data.day.present ? data.day.value : this.day, + type: data.type.present ? data.type.value : this.type, + refId: data.refId.present ? data.refId.value : this.refId, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + completed: data.completed.present ? data.completed.value : this.completed, + ); + } + + @override + String toString() { + return (StringBuffer('ProgramWorkout(') + ..write('id: $id, ') + ..write('weekId: $weekId, ') + ..write('programId: $programId, ') + ..write('day: $day, ') + ..write('type: $type, ') + ..write('refId: $refId, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('completed: $completed') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + weekId, + programId, + day, + type, + refId, + name, + description, + completed, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is ProgramWorkout && + other.id == this.id && + other.weekId == this.weekId && + other.programId == this.programId && + other.day == this.day && + other.type == this.type && + other.refId == this.refId && + other.name == this.name && + other.description == this.description && + other.completed == this.completed); +} + +class ProgramWorkoutsCompanion extends UpdateCompanion { + final Value id; + final Value weekId; + final Value programId; + final Value day; + final Value type; + final Value refId; + final Value name; + final Value description; + final Value completed; + final Value rowid; + const ProgramWorkoutsCompanion({ + this.id = const Value.absent(), + this.weekId = const Value.absent(), + this.programId = const Value.absent(), + this.day = const Value.absent(), + this.type = const Value.absent(), + this.refId = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.completed = const Value.absent(), + this.rowid = const Value.absent(), + }); + ProgramWorkoutsCompanion.insert({ + required String id, + required String weekId, + required String programId, + required String day, + required String type, + this.refId = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.completed = const Value.absent(), + this.rowid = const Value.absent(), + }) : id = Value(id), + weekId = Value(weekId), + programId = Value(programId), + day = Value(day), + type = Value(type); + static Insertable custom({ + Expression? id, + Expression? weekId, + Expression? programId, + Expression? day, + Expression? type, + Expression? refId, + Expression? name, + Expression? description, + Expression? completed, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (weekId != null) 'week_id': weekId, + if (programId != null) 'program_id': programId, + if (day != null) 'day': day, + if (type != null) 'type': type, + if (refId != null) 'ref_id': refId, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (completed != null) 'completed': completed, + if (rowid != null) 'rowid': rowid, + }); + } + + ProgramWorkoutsCompanion copyWith({ + Value? id, + Value? weekId, + Value? programId, + Value? day, + Value? type, + Value? refId, + Value? name, + Value? description, + Value? completed, + Value? rowid, + }) { + return ProgramWorkoutsCompanion( + id: id ?? this.id, + weekId: weekId ?? this.weekId, + programId: programId ?? this.programId, + day: day ?? this.day, + type: type ?? this.type, + refId: refId ?? this.refId, + name: name ?? this.name, + description: description ?? this.description, + completed: completed ?? this.completed, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (weekId.present) { + map['week_id'] = Variable(weekId.value); + } + if (programId.present) { + map['program_id'] = Variable(programId.value); + } + if (day.present) { + map['day'] = Variable(day.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (refId.present) { + map['ref_id'] = Variable(refId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (completed.present) { + map['completed'] = Variable(completed.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ProgramWorkoutsCompanion(') + ..write('id: $id, ') + ..write('weekId: $weekId, ') + ..write('programId: $programId, ') + ..write('day: $day, ') + ..write('type: $type, ') + ..write('refId: $refId, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('completed: $completed, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $AnalysisSessionsTable extends AnalysisSessions + with TableInfo<$AnalysisSessionsTable, AnalysisSession> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $AnalysisSessionsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _dateMeta = const VerificationMeta('date'); + @override + late final GeneratedColumn date = GeneratedColumn( + 'date', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _videoPathMeta = const VerificationMeta( + 'videoPath', + ); + @override + late final GeneratedColumn videoPath = GeneratedColumn( + 'video_path', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [id, name, date, videoPath]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'analysis_sessions'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, + name.isAcceptableOrUnknown(data['name']!, _nameMeta), + ); + } else if (isInserting) { + context.missing(_nameMeta); + } + if (data.containsKey('date')) { + context.handle( + _dateMeta, + date.isAcceptableOrUnknown(data['date']!, _dateMeta), + ); + } else if (isInserting) { + context.missing(_dateMeta); + } + if (data.containsKey('video_path')) { + context.handle( + _videoPathMeta, + videoPath.isAcceptableOrUnknown(data['video_path']!, _videoPathMeta), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + AnalysisSession map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AnalysisSession( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + date: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}date'], + )!, + videoPath: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}video_path'], + ), + ); + } + + @override + $AnalysisSessionsTable createAlias(String alias) { + return $AnalysisSessionsTable(attachedDatabase, alias); + } +} + +class AnalysisSession extends DataClass implements Insertable { + final String id; + final String name; + final String date; + final String? videoPath; + const AnalysisSession({ + required this.id, + required this.name, + required this.date, + this.videoPath, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['date'] = Variable(date); + if (!nullToAbsent || videoPath != null) { + map['video_path'] = Variable(videoPath); + } + return map; + } + + AnalysisSessionsCompanion toCompanion(bool nullToAbsent) { + return AnalysisSessionsCompanion( + id: Value(id), + name: Value(name), + date: Value(date), + videoPath: videoPath == null && nullToAbsent + ? const Value.absent() + : Value(videoPath), + ); + } + + factory AnalysisSession.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AnalysisSession( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + date: serializer.fromJson(json['date']), + videoPath: serializer.fromJson(json['videoPath']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'date': serializer.toJson(date), + 'videoPath': serializer.toJson(videoPath), + }; + } + + AnalysisSession copyWith({ + String? id, + String? name, + String? date, + Value videoPath = const Value.absent(), + }) => AnalysisSession( + id: id ?? this.id, + name: name ?? this.name, + date: date ?? this.date, + videoPath: videoPath.present ? videoPath.value : this.videoPath, + ); + AnalysisSession copyWithCompanion(AnalysisSessionsCompanion data) { + return AnalysisSession( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + date: data.date.present ? data.date.value : this.date, + videoPath: data.videoPath.present ? data.videoPath.value : this.videoPath, + ); + } + + @override + String toString() { + return (StringBuffer('AnalysisSession(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('date: $date, ') + ..write('videoPath: $videoPath') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, name, date, videoPath); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AnalysisSession && + other.id == this.id && + other.name == this.name && + other.date == this.date && + other.videoPath == this.videoPath); +} + +class AnalysisSessionsCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value date; + final Value videoPath; + final Value rowid; + const AnalysisSessionsCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.date = const Value.absent(), + this.videoPath = const Value.absent(), + this.rowid = const Value.absent(), + }); + AnalysisSessionsCompanion.insert({ + required String id, + required String name, + required String date, + this.videoPath = const Value.absent(), + this.rowid = const Value.absent(), + }) : id = Value(id), + name = Value(name), + date = Value(date); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? date, + Expression? videoPath, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (date != null) 'date': date, + if (videoPath != null) 'video_path': videoPath, + if (rowid != null) 'rowid': rowid, + }); + } + + AnalysisSessionsCompanion copyWith({ + Value? id, + Value? name, + Value? date, + Value? videoPath, + Value? rowid, + }) { + return AnalysisSessionsCompanion( + id: id ?? this.id, + name: name ?? this.name, + date: date ?? this.date, + videoPath: videoPath ?? this.videoPath, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (date.present) { + map['date'] = Variable(date.value); + } + if (videoPath.present) { + map['video_path'] = Variable(videoPath.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AnalysisSessionsCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('date: $date, ') + ..write('videoPath: $videoPath, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $AnnotationsTable extends Annotations + with TableInfo<$AnnotationsTable, Annotation> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $AnnotationsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _sessionIdMeta = const VerificationMeta( + 'sessionId', + ); + @override + late final GeneratedColumn sessionId = GeneratedColumn( + 'session_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES analysis_sessions (id) ON DELETE CASCADE', + ), + ); + static const VerificationMeta _startTimeMeta = const VerificationMeta( + 'startTime', + ); + @override + late final GeneratedColumn startTime = GeneratedColumn( + 'start_time', + aliasedName, + false, + type: DriftSqlType.double, + requiredDuringInsert: true, + ); + static const VerificationMeta _endTimeMeta = const VerificationMeta( + 'endTime', + ); + @override + late final GeneratedColumn endTime = GeneratedColumn( + 'end_time', + aliasedName, + false, + type: DriftSqlType.double, + requiredDuringInsert: true, + ); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _descriptionMeta = const VerificationMeta( + 'description', + ); + @override + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _colorMeta = const VerificationMeta('color'); + @override + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + sessionId, + startTime, + endTime, + name, + description, + color, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'annotations'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('session_id')) { + context.handle( + _sessionIdMeta, + sessionId.isAcceptableOrUnknown(data['session_id']!, _sessionIdMeta), + ); + } else if (isInserting) { + context.missing(_sessionIdMeta); + } + if (data.containsKey('start_time')) { + context.handle( + _startTimeMeta, + startTime.isAcceptableOrUnknown(data['start_time']!, _startTimeMeta), + ); + } else if (isInserting) { + context.missing(_startTimeMeta); + } + if (data.containsKey('end_time')) { + context.handle( + _endTimeMeta, + endTime.isAcceptableOrUnknown(data['end_time']!, _endTimeMeta), + ); + } else if (isInserting) { + context.missing(_endTimeMeta); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, + name.isAcceptableOrUnknown(data['name']!, _nameMeta), + ); + } + if (data.containsKey('description')) { + context.handle( + _descriptionMeta, + description.isAcceptableOrUnknown( + data['description']!, + _descriptionMeta, + ), + ); + } + if (data.containsKey('color')) { + context.handle( + _colorMeta, + color.isAcceptableOrUnknown(data['color']!, _colorMeta), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + Annotation map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return Annotation( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + sessionId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}session_id'], + )!, + startTime: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}start_time'], + )!, + endTime: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}end_time'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + ), + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + ); + } + + @override + $AnnotationsTable createAlias(String alias) { + return $AnnotationsTable(attachedDatabase, alias); + } +} + +class Annotation extends DataClass implements Insertable { + final String id; + final String sessionId; + final double startTime; + final double endTime; + final String? name; + final String? description; + final String? color; + const Annotation({ + required this.id, + required this.sessionId, + required this.startTime, + required this.endTime, + this.name, + this.description, + this.color, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['session_id'] = Variable(sessionId); + map['start_time'] = Variable(startTime); + map['end_time'] = Variable(endTime); + if (!nullToAbsent || name != null) { + map['name'] = Variable(name); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + return map; + } + + AnnotationsCompanion toCompanion(bool nullToAbsent) { + return AnnotationsCompanion( + id: Value(id), + sessionId: Value(sessionId), + startTime: Value(startTime), + endTime: Value(endTime), + name: name == null && nullToAbsent ? const Value.absent() : Value(name), + description: description == null && nullToAbsent + ? const Value.absent() + : Value(description), + color: color == null && nullToAbsent + ? const Value.absent() + : Value(color), + ); + } + + factory Annotation.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return Annotation( + id: serializer.fromJson(json['id']), + sessionId: serializer.fromJson(json['sessionId']), + startTime: serializer.fromJson(json['startTime']), + endTime: serializer.fromJson(json['endTime']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + color: serializer.fromJson(json['color']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'sessionId': serializer.toJson(sessionId), + 'startTime': serializer.toJson(startTime), + 'endTime': serializer.toJson(endTime), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'color': serializer.toJson(color), + }; + } + + Annotation copyWith({ + String? id, + String? sessionId, + double? startTime, + double? endTime, + Value name = const Value.absent(), + Value description = const Value.absent(), + Value color = const Value.absent(), + }) => Annotation( + id: id ?? this.id, + sessionId: sessionId ?? this.sessionId, + startTime: startTime ?? this.startTime, + endTime: endTime ?? this.endTime, + name: name.present ? name.value : this.name, + description: description.present ? description.value : this.description, + color: color.present ? color.value : this.color, + ); + Annotation copyWithCompanion(AnnotationsCompanion data) { + return Annotation( + id: data.id.present ? data.id.value : this.id, + sessionId: data.sessionId.present ? data.sessionId.value : this.sessionId, + startTime: data.startTime.present ? data.startTime.value : this.startTime, + endTime: data.endTime.present ? data.endTime.value : this.endTime, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + color: data.color.present ? data.color.value : this.color, + ); + } + + @override + String toString() { + return (StringBuffer('Annotation(') + ..write('id: $id, ') + ..write('sessionId: $sessionId, ') + ..write('startTime: $startTime, ') + ..write('endTime: $endTime, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('color: $color') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, sessionId, startTime, endTime, name, description, color); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is Annotation && + other.id == this.id && + other.sessionId == this.sessionId && + other.startTime == this.startTime && + other.endTime == this.endTime && + other.name == this.name && + other.description == this.description && + other.color == this.color); +} + +class AnnotationsCompanion extends UpdateCompanion { + final Value id; + final Value sessionId; + final Value startTime; + final Value endTime; + final Value name; + final Value description; + final Value color; + final Value rowid; + const AnnotationsCompanion({ + this.id = const Value.absent(), + this.sessionId = const Value.absent(), + this.startTime = const Value.absent(), + this.endTime = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.color = const Value.absent(), + this.rowid = const Value.absent(), + }); + AnnotationsCompanion.insert({ + required String id, + required String sessionId, + required double startTime, + required double endTime, + this.name = const Value.absent(), + this.description = const Value.absent(), + this.color = const Value.absent(), + this.rowid = const Value.absent(), + }) : id = Value(id), + sessionId = Value(sessionId), + startTime = Value(startTime), + endTime = Value(endTime); + static Insertable custom({ + Expression? id, + Expression? sessionId, + Expression? startTime, + Expression? endTime, + Expression? name, + Expression? description, + Expression? color, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (sessionId != null) 'session_id': sessionId, + if (startTime != null) 'start_time': startTime, + if (endTime != null) 'end_time': endTime, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (color != null) 'color': color, + if (rowid != null) 'rowid': rowid, + }); + } + + AnnotationsCompanion copyWith({ + Value? id, + Value? sessionId, + Value? startTime, + Value? endTime, + Value? name, + Value? description, + Value? color, + Value? rowid, + }) { + return AnnotationsCompanion( + id: id ?? this.id, + sessionId: sessionId ?? this.sessionId, + startTime: startTime ?? this.startTime, + endTime: endTime ?? this.endTime, + name: name ?? this.name, + description: description ?? this.description, + color: color ?? this.color, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (sessionId.present) { + map['session_id'] = Variable(sessionId.value); + } + if (startTime.present) { + map['start_time'] = Variable(startTime.value); + } + if (endTime.present) { + map['end_time'] = Variable(endTime.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AnnotationsCompanion(') + ..write('id: $id, ') + ..write('sessionId: $sessionId, ') + ..write('startTime: $startTime, ') + ..write('endTime: $endTime, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('color: $color, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $ChatSessionsTable extends ChatSessions + with TableInfo<$ChatSessionsTable, ChatSession> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $ChatSessionsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _titleMeta = const VerificationMeta('title'); + @override + late final GeneratedColumn title = GeneratedColumn( + 'title', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _createdAtMeta = const VerificationMeta( + 'createdAt', + ); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _updatedAtMeta = const VerificationMeta( + 'updatedAt', + ); + @override + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [id, title, createdAt, updatedAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'chat_sessions'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('title')) { + context.handle( + _titleMeta, + title.isAcceptableOrUnknown(data['title']!, _titleMeta), + ); + } + if (data.containsKey('created_at')) { + context.handle( + _createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta), + ); + } else if (isInserting) { + context.missing(_createdAtMeta); + } + if (data.containsKey('updated_at')) { + context.handle( + _updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta), + ); + } else if (isInserting) { + context.missing(_updatedAtMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + ChatSession map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return ChatSession( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + title: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}title'], + ), + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + ); + } + + @override + $ChatSessionsTable createAlias(String alias) { + return $ChatSessionsTable(attachedDatabase, alias); + } +} + +class ChatSession extends DataClass implements Insertable { + final String id; + final String? title; + final String createdAt; + final String updatedAt; + const ChatSession({ + required this.id, + this.title, + required this.createdAt, + required this.updatedAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + if (!nullToAbsent || title != null) { + map['title'] = Variable(title); + } + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + return map; + } + + ChatSessionsCompanion toCompanion(bool nullToAbsent) { + return ChatSessionsCompanion( + id: Value(id), + title: title == null && nullToAbsent + ? const Value.absent() + : Value(title), + createdAt: Value(createdAt), + updatedAt: Value(updatedAt), + ); + } + + factory ChatSession.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return ChatSession( + id: serializer.fromJson(json['id']), + title: serializer.fromJson(json['title']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'title': serializer.toJson(title), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + ChatSession copyWith({ + String? id, + Value title = const Value.absent(), + String? createdAt, + String? updatedAt, + }) => ChatSession( + id: id ?? this.id, + title: title.present ? title.value : this.title, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + ChatSession copyWithCompanion(ChatSessionsCompanion data) { + return ChatSession( + id: data.id.present ? data.id.value : this.id, + title: data.title.present ? data.title.value : this.title, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ); + } + + @override + String toString() { + return (StringBuffer('ChatSession(') + ..write('id: $id, ') + ..write('title: $title, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, title, createdAt, updatedAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is ChatSession && + other.id == this.id && + other.title == this.title && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt); +} + +class ChatSessionsCompanion extends UpdateCompanion { + final Value id; + final Value title; + final Value createdAt; + final Value updatedAt; + final Value rowid; + const ChatSessionsCompanion({ + this.id = const Value.absent(), + this.title = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.rowid = const Value.absent(), + }); + ChatSessionsCompanion.insert({ + required String id, + this.title = const Value.absent(), + required String createdAt, + required String updatedAt, + this.rowid = const Value.absent(), + }) : id = Value(id), + createdAt = Value(createdAt), + updatedAt = Value(updatedAt); + static Insertable custom({ + Expression? id, + Expression? title, + Expression? createdAt, + Expression? updatedAt, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (title != null) 'title': title, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (rowid != null) 'rowid': rowid, + }); + } + + ChatSessionsCompanion copyWith({ + Value? id, + Value? title, + Value? createdAt, + Value? updatedAt, + Value? rowid, + }) { + return ChatSessionsCompanion( + id: id ?? this.id, + title: title ?? this.title, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (title.present) { + map['title'] = Variable(title.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ChatSessionsCompanion(') + ..write('id: $id, ') + ..write('title: $title, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $ChatMessagesTable extends ChatMessages + with TableInfo<$ChatMessagesTable, ChatMessage> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $ChatMessagesTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _sessionIdMeta = const VerificationMeta( + 'sessionId', + ); + @override + late final GeneratedColumn sessionId = GeneratedColumn( + 'session_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES chat_sessions (id) ON DELETE CASCADE', + ), + ); + static const VerificationMeta _roleMeta = const VerificationMeta('role'); + @override + late final GeneratedColumn role = GeneratedColumn( + 'role', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _contentMeta = const VerificationMeta( + 'content', + ); + @override + late final GeneratedColumn content = GeneratedColumn( + 'content', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _createdAtMeta = const VerificationMeta( + 'createdAt', + ); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + sessionId, + role, + content, + createdAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'chat_messages'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('session_id')) { + context.handle( + _sessionIdMeta, + sessionId.isAcceptableOrUnknown(data['session_id']!, _sessionIdMeta), + ); + } else if (isInserting) { + context.missing(_sessionIdMeta); + } + if (data.containsKey('role')) { + context.handle( + _roleMeta, + role.isAcceptableOrUnknown(data['role']!, _roleMeta), + ); + } else if (isInserting) { + context.missing(_roleMeta); + } + if (data.containsKey('content')) { + context.handle( + _contentMeta, + content.isAcceptableOrUnknown(data['content']!, _contentMeta), + ); + } else if (isInserting) { + context.missing(_contentMeta); + } + if (data.containsKey('created_at')) { + context.handle( + _createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta), + ); + } else if (isInserting) { + context.missing(_createdAtMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + ChatMessage map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return ChatMessage( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + sessionId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}session_id'], + )!, + role: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}role'], + )!, + content: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}content'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + ); + } + + @override + $ChatMessagesTable createAlias(String alias) { + return $ChatMessagesTable(attachedDatabase, alias); + } +} + +class ChatMessage extends DataClass implements Insertable { + final String id; + final String sessionId; + final String role; + final String content; + final String createdAt; + const ChatMessage({ + required this.id, + required this.sessionId, + required this.role, + required this.content, + required this.createdAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['session_id'] = Variable(sessionId); + map['role'] = Variable(role); + map['content'] = Variable(content); + map['created_at'] = Variable(createdAt); + return map; + } + + ChatMessagesCompanion toCompanion(bool nullToAbsent) { + return ChatMessagesCompanion( + id: Value(id), + sessionId: Value(sessionId), + role: Value(role), + content: Value(content), + createdAt: Value(createdAt), + ); + } + + factory ChatMessage.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return ChatMessage( + id: serializer.fromJson(json['id']), + sessionId: serializer.fromJson(json['sessionId']), + role: serializer.fromJson(json['role']), + content: serializer.fromJson(json['content']), + createdAt: serializer.fromJson(json['createdAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'sessionId': serializer.toJson(sessionId), + 'role': serializer.toJson(role), + 'content': serializer.toJson(content), + 'createdAt': serializer.toJson(createdAt), + }; + } + + ChatMessage copyWith({ + String? id, + String? sessionId, + String? role, + String? content, + String? createdAt, + }) => ChatMessage( + id: id ?? this.id, + sessionId: sessionId ?? this.sessionId, + role: role ?? this.role, + content: content ?? this.content, + createdAt: createdAt ?? this.createdAt, + ); + ChatMessage copyWithCompanion(ChatMessagesCompanion data) { + return ChatMessage( + id: data.id.present ? data.id.value : this.id, + sessionId: data.sessionId.present ? data.sessionId.value : this.sessionId, + role: data.role.present ? data.role.value : this.role, + content: data.content.present ? data.content.value : this.content, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('ChatMessage(') + ..write('id: $id, ') + ..write('sessionId: $sessionId, ') + ..write('role: $role, ') + ..write('content: $content, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, sessionId, role, content, createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is ChatMessage && + other.id == this.id && + other.sessionId == this.sessionId && + other.role == this.role && + other.content == this.content && + other.createdAt == this.createdAt); +} + +class ChatMessagesCompanion extends UpdateCompanion { + final Value id; + final Value sessionId; + final Value role; + final Value content; + final Value createdAt; + final Value rowid; + const ChatMessagesCompanion({ + this.id = const Value.absent(), + this.sessionId = const Value.absent(), + this.role = const Value.absent(), + this.content = const Value.absent(), + this.createdAt = const Value.absent(), + this.rowid = const Value.absent(), + }); + ChatMessagesCompanion.insert({ + required String id, + required String sessionId, + required String role, + required String content, + required String createdAt, + this.rowid = const Value.absent(), + }) : id = Value(id), + sessionId = Value(sessionId), + role = Value(role), + content = Value(content), + createdAt = Value(createdAt); + static Insertable custom({ + Expression? id, + Expression? sessionId, + Expression? role, + Expression? content, + Expression? createdAt, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (sessionId != null) 'session_id': sessionId, + if (role != null) 'role': role, + if (content != null) 'content': content, + if (createdAt != null) 'created_at': createdAt, + if (rowid != null) 'rowid': rowid, + }); + } + + ChatMessagesCompanion copyWith({ + Value? id, + Value? sessionId, + Value? role, + Value? content, + Value? createdAt, + Value? rowid, + }) { + return ChatMessagesCompanion( + id: id ?? this.id, + sessionId: sessionId ?? this.sessionId, + role: role ?? this.role, + content: content ?? this.content, + createdAt: createdAt ?? this.createdAt, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (sessionId.present) { + map['session_id'] = Variable(sessionId.value); + } + if (role.present) { + map['role'] = Variable(role.value); + } + if (content.present) { + map['content'] = Variable(content.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ChatMessagesCompanion(') + ..write('id: $id, ') + ..write('sessionId: $sessionId, ') + ..write('role: $role, ') + ..write('content: $content, ') + ..write('createdAt: $createdAt, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +abstract class _$AppDatabase extends GeneratedDatabase { + _$AppDatabase(QueryExecutor e) : super(e); + $AppDatabaseManager get managers => $AppDatabaseManager(this); + late final $ExercisesTable exercises = $ExercisesTable(this); + late final $TrainingPlansTable trainingPlans = $TrainingPlansTable(this); + late final $ProgramsTable programs = $ProgramsTable(this); + late final $ProgramWeeksTable programWeeks = $ProgramWeeksTable(this); + late final $ProgramWorkoutsTable programWorkouts = $ProgramWorkoutsTable( + this, + ); + late final $AnalysisSessionsTable analysisSessions = $AnalysisSessionsTable( + this, + ); + late final $AnnotationsTable annotations = $AnnotationsTable(this); + late final $ChatSessionsTable chatSessions = $ChatSessionsTable(this); + late final $ChatMessagesTable chatMessages = $ChatMessagesTable(this); + late final ExerciseDao exerciseDao = ExerciseDao(this as AppDatabase); + late final TrainingPlanDao trainingPlanDao = TrainingPlanDao( + this as AppDatabase, + ); + late final ProgramDao programDao = ProgramDao(this as AppDatabase); + late final AnalysisDao analysisDao = AnalysisDao(this as AppDatabase); + late final ChatDao chatDao = ChatDao(this as AppDatabase); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + exercises, + trainingPlans, + programs, + programWeeks, + programWorkouts, + analysisSessions, + annotations, + chatSessions, + chatMessages, + ]; + @override + StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules([ + WritePropagation( + on: TableUpdateQuery.onTableName( + 'programs', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('program_weeks', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'program_weeks', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('program_workouts', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'programs', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('program_workouts', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'analysis_sessions', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('annotations', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'chat_sessions', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('chat_messages', kind: UpdateKind.delete)], + ), + ]); +} + +typedef $$ExercisesTableCreateCompanionBuilder = + ExercisesCompanion Function({ + required String id, + required String name, + Value instructions, + Value enrichment, + Value tags, + Value videoUrl, + Value rowid, + }); +typedef $$ExercisesTableUpdateCompanionBuilder = + ExercisesCompanion Function({ + Value id, + Value name, + Value instructions, + Value enrichment, + Value tags, + Value videoUrl, + Value rowid, + }); + +class $$ExercisesTableFilterComposer + extends Composer<_$AppDatabase, $ExercisesTable> { + $$ExercisesTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get instructions => $composableBuilder( + column: $table.instructions, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get enrichment => $composableBuilder( + column: $table.enrichment, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get tags => $composableBuilder( + column: $table.tags, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get videoUrl => $composableBuilder( + column: $table.videoUrl, + builder: (column) => ColumnFilters(column), + ); +} + +class $$ExercisesTableOrderingComposer + extends Composer<_$AppDatabase, $ExercisesTable> { + $$ExercisesTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get instructions => $composableBuilder( + column: $table.instructions, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get enrichment => $composableBuilder( + column: $table.enrichment, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get tags => $composableBuilder( + column: $table.tags, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get videoUrl => $composableBuilder( + column: $table.videoUrl, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$ExercisesTableAnnotationComposer + extends Composer<_$AppDatabase, $ExercisesTable> { + $$ExercisesTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + GeneratedColumn get instructions => $composableBuilder( + column: $table.instructions, + builder: (column) => column, + ); + + GeneratedColumn get enrichment => $composableBuilder( + column: $table.enrichment, + builder: (column) => column, + ); + + GeneratedColumn get tags => + $composableBuilder(column: $table.tags, builder: (column) => column); + + GeneratedColumn get videoUrl => + $composableBuilder(column: $table.videoUrl, builder: (column) => column); +} + +class $$ExercisesTableTableManager + extends + RootTableManager< + _$AppDatabase, + $ExercisesTable, + Exercise, + $$ExercisesTableFilterComposer, + $$ExercisesTableOrderingComposer, + $$ExercisesTableAnnotationComposer, + $$ExercisesTableCreateCompanionBuilder, + $$ExercisesTableUpdateCompanionBuilder, + (Exercise, BaseReferences<_$AppDatabase, $ExercisesTable, Exercise>), + Exercise, + PrefetchHooks Function() + > { + $$ExercisesTableTableManager(_$AppDatabase db, $ExercisesTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$ExercisesTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$ExercisesTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$ExercisesTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value name = const Value.absent(), + Value instructions = const Value.absent(), + Value enrichment = const Value.absent(), + Value tags = const Value.absent(), + Value videoUrl = const Value.absent(), + Value rowid = const Value.absent(), + }) => ExercisesCompanion( + id: id, + name: name, + instructions: instructions, + enrichment: enrichment, + tags: tags, + videoUrl: videoUrl, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + required String name, + Value instructions = const Value.absent(), + Value enrichment = const Value.absent(), + Value tags = const Value.absent(), + Value videoUrl = const Value.absent(), + Value rowid = const Value.absent(), + }) => ExercisesCompanion.insert( + id: id, + name: name, + instructions: instructions, + enrichment: enrichment, + tags: tags, + videoUrl: videoUrl, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $$ExercisesTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $ExercisesTable, + Exercise, + $$ExercisesTableFilterComposer, + $$ExercisesTableOrderingComposer, + $$ExercisesTableAnnotationComposer, + $$ExercisesTableCreateCompanionBuilder, + $$ExercisesTableUpdateCompanionBuilder, + (Exercise, BaseReferences<_$AppDatabase, $ExercisesTable, Exercise>), + Exercise, + PrefetchHooks Function() + >; +typedef $$TrainingPlansTableCreateCompanionBuilder = + TrainingPlansCompanion Function({ + required String id, + required String name, + Value sections, + Value rowid, + }); +typedef $$TrainingPlansTableUpdateCompanionBuilder = + TrainingPlansCompanion Function({ + Value id, + Value name, + Value sections, + Value rowid, + }); + +class $$TrainingPlansTableFilterComposer + extends Composer<_$AppDatabase, $TrainingPlansTable> { + $$TrainingPlansTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get sections => $composableBuilder( + column: $table.sections, + builder: (column) => ColumnFilters(column), + ); +} + +class $$TrainingPlansTableOrderingComposer + extends Composer<_$AppDatabase, $TrainingPlansTable> { + $$TrainingPlansTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get sections => $composableBuilder( + column: $table.sections, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$TrainingPlansTableAnnotationComposer + extends Composer<_$AppDatabase, $TrainingPlansTable> { + $$TrainingPlansTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + GeneratedColumn get sections => + $composableBuilder(column: $table.sections, builder: (column) => column); +} + +class $$TrainingPlansTableTableManager + extends + RootTableManager< + _$AppDatabase, + $TrainingPlansTable, + TrainingPlan, + $$TrainingPlansTableFilterComposer, + $$TrainingPlansTableOrderingComposer, + $$TrainingPlansTableAnnotationComposer, + $$TrainingPlansTableCreateCompanionBuilder, + $$TrainingPlansTableUpdateCompanionBuilder, + ( + TrainingPlan, + BaseReferences<_$AppDatabase, $TrainingPlansTable, TrainingPlan>, + ), + TrainingPlan, + PrefetchHooks Function() + > { + $$TrainingPlansTableTableManager(_$AppDatabase db, $TrainingPlansTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$TrainingPlansTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$TrainingPlansTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$TrainingPlansTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value name = const Value.absent(), + Value sections = const Value.absent(), + Value rowid = const Value.absent(), + }) => TrainingPlansCompanion( + id: id, + name: name, + sections: sections, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + required String name, + Value sections = const Value.absent(), + Value rowid = const Value.absent(), + }) => TrainingPlansCompanion.insert( + id: id, + name: name, + sections: sections, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $$TrainingPlansTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $TrainingPlansTable, + TrainingPlan, + $$TrainingPlansTableFilterComposer, + $$TrainingPlansTableOrderingComposer, + $$TrainingPlansTableAnnotationComposer, + $$TrainingPlansTableCreateCompanionBuilder, + $$TrainingPlansTableUpdateCompanionBuilder, + ( + TrainingPlan, + BaseReferences<_$AppDatabase, $TrainingPlansTable, TrainingPlan>, + ), + TrainingPlan, + PrefetchHooks Function() + >; +typedef $$ProgramsTableCreateCompanionBuilder = + ProgramsCompanion Function({ + required String id, + required String name, + required String createdAt, + Value rowid, + }); +typedef $$ProgramsTableUpdateCompanionBuilder = + ProgramsCompanion Function({ + Value id, + Value name, + Value createdAt, + Value rowid, + }); + +final class $$ProgramsTableReferences + extends BaseReferences<_$AppDatabase, $ProgramsTable, Program> { + $$ProgramsTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static MultiTypedResultKey<$ProgramWeeksTable, List> + _programWeeksRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.programWeeks, + aliasName: $_aliasNameGenerator(db.programs.id, db.programWeeks.programId), + ); + + $$ProgramWeeksTableProcessedTableManager get programWeeksRefs { + final manager = $$ProgramWeeksTableTableManager( + $_db, + $_db.programWeeks, + ).filter((f) => f.programId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull(_programWeeksRefsTable($_db)); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } + + static MultiTypedResultKey<$ProgramWorkoutsTable, List> + _programWorkoutsRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.programWorkouts, + aliasName: $_aliasNameGenerator( + db.programs.id, + db.programWorkouts.programId, + ), + ); + + $$ProgramWorkoutsTableProcessedTableManager get programWorkoutsRefs { + final manager = $$ProgramWorkoutsTableTableManager( + $_db, + $_db.programWorkouts, + ).filter((f) => f.programId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull( + _programWorkoutsRefsTable($_db), + ); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } +} + +class $$ProgramsTableFilterComposer + extends Composer<_$AppDatabase, $ProgramsTable> { + $$ProgramsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnFilters(column), + ); + + Expression programWeeksRefs( + Expression Function($$ProgramWeeksTableFilterComposer f) f, + ) { + final $$ProgramWeeksTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.programWeeks, + getReferencedColumn: (t) => t.programId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ProgramWeeksTableFilterComposer( + $db: $db, + $table: $db.programWeeks, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } + + Expression programWorkoutsRefs( + Expression Function($$ProgramWorkoutsTableFilterComposer f) f, + ) { + final $$ProgramWorkoutsTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.programWorkouts, + getReferencedColumn: (t) => t.programId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ProgramWorkoutsTableFilterComposer( + $db: $db, + $table: $db.programWorkouts, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$ProgramsTableOrderingComposer + extends Composer<_$AppDatabase, $ProgramsTable> { + $$ProgramsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$ProgramsTableAnnotationComposer + extends Composer<_$AppDatabase, $ProgramsTable> { + $$ProgramsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); + + Expression programWeeksRefs( + Expression Function($$ProgramWeeksTableAnnotationComposer a) f, + ) { + final $$ProgramWeeksTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.programWeeks, + getReferencedColumn: (t) => t.programId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ProgramWeeksTableAnnotationComposer( + $db: $db, + $table: $db.programWeeks, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } + + Expression programWorkoutsRefs( + Expression Function($$ProgramWorkoutsTableAnnotationComposer a) f, + ) { + final $$ProgramWorkoutsTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.programWorkouts, + getReferencedColumn: (t) => t.programId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ProgramWorkoutsTableAnnotationComposer( + $db: $db, + $table: $db.programWorkouts, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$ProgramsTableTableManager + extends + RootTableManager< + _$AppDatabase, + $ProgramsTable, + Program, + $$ProgramsTableFilterComposer, + $$ProgramsTableOrderingComposer, + $$ProgramsTableAnnotationComposer, + $$ProgramsTableCreateCompanionBuilder, + $$ProgramsTableUpdateCompanionBuilder, + (Program, $$ProgramsTableReferences), + Program, + PrefetchHooks Function({ + bool programWeeksRefs, + bool programWorkoutsRefs, + }) + > { + $$ProgramsTableTableManager(_$AppDatabase db, $ProgramsTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$ProgramsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$ProgramsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$ProgramsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value name = const Value.absent(), + Value createdAt = const Value.absent(), + Value rowid = const Value.absent(), + }) => ProgramsCompanion( + id: id, + name: name, + createdAt: createdAt, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + required String name, + required String createdAt, + Value rowid = const Value.absent(), + }) => ProgramsCompanion.insert( + id: id, + name: name, + createdAt: createdAt, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + $$ProgramsTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: + ({programWeeksRefs = false, programWorkoutsRefs = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [ + if (programWeeksRefs) db.programWeeks, + if (programWorkoutsRefs) db.programWorkouts, + ], + addJoins: null, + getPrefetchedDataCallback: (items) async { + return [ + if (programWeeksRefs) + await $_getPrefetchedData< + Program, + $ProgramsTable, + ProgramWeek + >( + currentTable: table, + referencedTable: $$ProgramsTableReferences + ._programWeeksRefsTable(db), + managerFromTypedResult: (p0) => + $$ProgramsTableReferences( + db, + table, + p0, + ).programWeeksRefs, + referencedItemsForCurrentItem: + (item, referencedItems) => referencedItems.where( + (e) => e.programId == item.id, + ), + typedResults: items, + ), + if (programWorkoutsRefs) + await $_getPrefetchedData< + Program, + $ProgramsTable, + ProgramWorkout + >( + currentTable: table, + referencedTable: $$ProgramsTableReferences + ._programWorkoutsRefsTable(db), + managerFromTypedResult: (p0) => + $$ProgramsTableReferences( + db, + table, + p0, + ).programWorkoutsRefs, + referencedItemsForCurrentItem: + (item, referencedItems) => referencedItems.where( + (e) => e.programId == item.id, + ), + typedResults: items, + ), + ]; + }, + ); + }, + ), + ); +} + +typedef $$ProgramsTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $ProgramsTable, + Program, + $$ProgramsTableFilterComposer, + $$ProgramsTableOrderingComposer, + $$ProgramsTableAnnotationComposer, + $$ProgramsTableCreateCompanionBuilder, + $$ProgramsTableUpdateCompanionBuilder, + (Program, $$ProgramsTableReferences), + Program, + PrefetchHooks Function({bool programWeeksRefs, bool programWorkoutsRefs}) + >; +typedef $$ProgramWeeksTableCreateCompanionBuilder = + ProgramWeeksCompanion Function({ + required String id, + required String programId, + required int position, + Value notes, + Value rowid, + }); +typedef $$ProgramWeeksTableUpdateCompanionBuilder = + ProgramWeeksCompanion Function({ + Value id, + Value programId, + Value position, + Value notes, + Value rowid, + }); + +final class $$ProgramWeeksTableReferences + extends BaseReferences<_$AppDatabase, $ProgramWeeksTable, ProgramWeek> { + $$ProgramWeeksTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static $ProgramsTable _programIdTable(_$AppDatabase db) => + db.programs.createAlias( + $_aliasNameGenerator(db.programWeeks.programId, db.programs.id), + ); + + $$ProgramsTableProcessedTableManager get programId { + final $_column = $_itemColumn('program_id')!; + + final manager = $$ProgramsTableTableManager( + $_db, + $_db.programs, + ).filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_programIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } + + static MultiTypedResultKey<$ProgramWorkoutsTable, List> + _programWorkoutsRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.programWorkouts, + aliasName: $_aliasNameGenerator( + db.programWeeks.id, + db.programWorkouts.weekId, + ), + ); + + $$ProgramWorkoutsTableProcessedTableManager get programWorkoutsRefs { + final manager = $$ProgramWorkoutsTableTableManager( + $_db, + $_db.programWorkouts, + ).filter((f) => f.weekId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull( + _programWorkoutsRefsTable($_db), + ); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } +} + +class $$ProgramWeeksTableFilterComposer + extends Composer<_$AppDatabase, $ProgramWeeksTable> { + $$ProgramWeeksTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get position => $composableBuilder( + column: $table.position, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get notes => $composableBuilder( + column: $table.notes, + builder: (column) => ColumnFilters(column), + ); + + $$ProgramsTableFilterComposer get programId { + final $$ProgramsTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.programId, + referencedTable: $db.programs, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ProgramsTableFilterComposer( + $db: $db, + $table: $db.programs, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + Expression programWorkoutsRefs( + Expression Function($$ProgramWorkoutsTableFilterComposer f) f, + ) { + final $$ProgramWorkoutsTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.programWorkouts, + getReferencedColumn: (t) => t.weekId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ProgramWorkoutsTableFilterComposer( + $db: $db, + $table: $db.programWorkouts, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$ProgramWeeksTableOrderingComposer + extends Composer<_$AppDatabase, $ProgramWeeksTable> { + $$ProgramWeeksTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get position => $composableBuilder( + column: $table.position, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get notes => $composableBuilder( + column: $table.notes, + builder: (column) => ColumnOrderings(column), + ); + + $$ProgramsTableOrderingComposer get programId { + final $$ProgramsTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.programId, + referencedTable: $db.programs, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ProgramsTableOrderingComposer( + $db: $db, + $table: $db.programs, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$ProgramWeeksTableAnnotationComposer + extends Composer<_$AppDatabase, $ProgramWeeksTable> { + $$ProgramWeeksTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get position => + $composableBuilder(column: $table.position, builder: (column) => column); + + GeneratedColumn get notes => + $composableBuilder(column: $table.notes, builder: (column) => column); + + $$ProgramsTableAnnotationComposer get programId { + final $$ProgramsTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.programId, + referencedTable: $db.programs, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ProgramsTableAnnotationComposer( + $db: $db, + $table: $db.programs, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + Expression programWorkoutsRefs( + Expression Function($$ProgramWorkoutsTableAnnotationComposer a) f, + ) { + final $$ProgramWorkoutsTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.programWorkouts, + getReferencedColumn: (t) => t.weekId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ProgramWorkoutsTableAnnotationComposer( + $db: $db, + $table: $db.programWorkouts, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$ProgramWeeksTableTableManager + extends + RootTableManager< + _$AppDatabase, + $ProgramWeeksTable, + ProgramWeek, + $$ProgramWeeksTableFilterComposer, + $$ProgramWeeksTableOrderingComposer, + $$ProgramWeeksTableAnnotationComposer, + $$ProgramWeeksTableCreateCompanionBuilder, + $$ProgramWeeksTableUpdateCompanionBuilder, + (ProgramWeek, $$ProgramWeeksTableReferences), + ProgramWeek, + PrefetchHooks Function({bool programId, bool programWorkoutsRefs}) + > { + $$ProgramWeeksTableTableManager(_$AppDatabase db, $ProgramWeeksTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$ProgramWeeksTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$ProgramWeeksTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$ProgramWeeksTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value programId = const Value.absent(), + Value position = const Value.absent(), + Value notes = const Value.absent(), + Value rowid = const Value.absent(), + }) => ProgramWeeksCompanion( + id: id, + programId: programId, + position: position, + notes: notes, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + required String programId, + required int position, + Value notes = const Value.absent(), + Value rowid = const Value.absent(), + }) => ProgramWeeksCompanion.insert( + id: id, + programId: programId, + position: position, + notes: notes, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + $$ProgramWeeksTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: + ({programId = false, programWorkoutsRefs = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [ + if (programWorkoutsRefs) db.programWorkouts, + ], + addJoins: + < + T extends TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (programId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.programId, + referencedTable: + $$ProgramWeeksTableReferences + ._programIdTable(db), + referencedColumn: + $$ProgramWeeksTableReferences + ._programIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return [ + if (programWorkoutsRefs) + await $_getPrefetchedData< + ProgramWeek, + $ProgramWeeksTable, + ProgramWorkout + >( + currentTable: table, + referencedTable: $$ProgramWeeksTableReferences + ._programWorkoutsRefsTable(db), + managerFromTypedResult: (p0) => + $$ProgramWeeksTableReferences( + db, + table, + p0, + ).programWorkoutsRefs, + referencedItemsForCurrentItem: + (item, referencedItems) => referencedItems.where( + (e) => e.weekId == item.id, + ), + typedResults: items, + ), + ]; + }, + ); + }, + ), + ); +} + +typedef $$ProgramWeeksTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $ProgramWeeksTable, + ProgramWeek, + $$ProgramWeeksTableFilterComposer, + $$ProgramWeeksTableOrderingComposer, + $$ProgramWeeksTableAnnotationComposer, + $$ProgramWeeksTableCreateCompanionBuilder, + $$ProgramWeeksTableUpdateCompanionBuilder, + (ProgramWeek, $$ProgramWeeksTableReferences), + ProgramWeek, + PrefetchHooks Function({bool programId, bool programWorkoutsRefs}) + >; +typedef $$ProgramWorkoutsTableCreateCompanionBuilder = + ProgramWorkoutsCompanion Function({ + required String id, + required String weekId, + required String programId, + required String day, + required String type, + Value refId, + Value name, + Value description, + Value completed, + Value rowid, + }); +typedef $$ProgramWorkoutsTableUpdateCompanionBuilder = + ProgramWorkoutsCompanion Function({ + Value id, + Value weekId, + Value programId, + Value day, + Value type, + Value refId, + Value name, + Value description, + Value completed, + Value rowid, + }); + +final class $$ProgramWorkoutsTableReferences + extends + BaseReferences<_$AppDatabase, $ProgramWorkoutsTable, ProgramWorkout> { + $$ProgramWorkoutsTableReferences( + super.$_db, + super.$_table, + super.$_typedResult, + ); + + static $ProgramWeeksTable _weekIdTable(_$AppDatabase db) => + db.programWeeks.createAlias( + $_aliasNameGenerator(db.programWorkouts.weekId, db.programWeeks.id), + ); + + $$ProgramWeeksTableProcessedTableManager get weekId { + final $_column = $_itemColumn('week_id')!; + + final manager = $$ProgramWeeksTableTableManager( + $_db, + $_db.programWeeks, + ).filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_weekIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } + + static $ProgramsTable _programIdTable(_$AppDatabase db) => + db.programs.createAlias( + $_aliasNameGenerator(db.programWorkouts.programId, db.programs.id), + ); + + $$ProgramsTableProcessedTableManager get programId { + final $_column = $_itemColumn('program_id')!; + + final manager = $$ProgramsTableTableManager( + $_db, + $_db.programs, + ).filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_programIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } +} + +class $$ProgramWorkoutsTableFilterComposer + extends Composer<_$AppDatabase, $ProgramWorkoutsTable> { + $$ProgramWorkoutsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get day => $composableBuilder( + column: $table.day, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get type => $composableBuilder( + column: $table.type, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get refId => $composableBuilder( + column: $table.refId, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get description => $composableBuilder( + column: $table.description, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get completed => $composableBuilder( + column: $table.completed, + builder: (column) => ColumnFilters(column), + ); + + $$ProgramWeeksTableFilterComposer get weekId { + final $$ProgramWeeksTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.weekId, + referencedTable: $db.programWeeks, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ProgramWeeksTableFilterComposer( + $db: $db, + $table: $db.programWeeks, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + $$ProgramsTableFilterComposer get programId { + final $$ProgramsTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.programId, + referencedTable: $db.programs, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ProgramsTableFilterComposer( + $db: $db, + $table: $db.programs, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$ProgramWorkoutsTableOrderingComposer + extends Composer<_$AppDatabase, $ProgramWorkoutsTable> { + $$ProgramWorkoutsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get day => $composableBuilder( + column: $table.day, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get type => $composableBuilder( + column: $table.type, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get refId => $composableBuilder( + column: $table.refId, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get description => $composableBuilder( + column: $table.description, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get completed => $composableBuilder( + column: $table.completed, + builder: (column) => ColumnOrderings(column), + ); + + $$ProgramWeeksTableOrderingComposer get weekId { + final $$ProgramWeeksTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.weekId, + referencedTable: $db.programWeeks, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ProgramWeeksTableOrderingComposer( + $db: $db, + $table: $db.programWeeks, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + $$ProgramsTableOrderingComposer get programId { + final $$ProgramsTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.programId, + referencedTable: $db.programs, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ProgramsTableOrderingComposer( + $db: $db, + $table: $db.programs, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$ProgramWorkoutsTableAnnotationComposer + extends Composer<_$AppDatabase, $ProgramWorkoutsTable> { + $$ProgramWorkoutsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get day => + $composableBuilder(column: $table.day, builder: (column) => column); + + GeneratedColumn get type => + $composableBuilder(column: $table.type, builder: (column) => column); + + GeneratedColumn get refId => + $composableBuilder(column: $table.refId, builder: (column) => column); + + GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + GeneratedColumn get description => $composableBuilder( + column: $table.description, + builder: (column) => column, + ); + + GeneratedColumn get completed => + $composableBuilder(column: $table.completed, builder: (column) => column); + + $$ProgramWeeksTableAnnotationComposer get weekId { + final $$ProgramWeeksTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.weekId, + referencedTable: $db.programWeeks, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ProgramWeeksTableAnnotationComposer( + $db: $db, + $table: $db.programWeeks, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + $$ProgramsTableAnnotationComposer get programId { + final $$ProgramsTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.programId, + referencedTable: $db.programs, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ProgramsTableAnnotationComposer( + $db: $db, + $table: $db.programs, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$ProgramWorkoutsTableTableManager + extends + RootTableManager< + _$AppDatabase, + $ProgramWorkoutsTable, + ProgramWorkout, + $$ProgramWorkoutsTableFilterComposer, + $$ProgramWorkoutsTableOrderingComposer, + $$ProgramWorkoutsTableAnnotationComposer, + $$ProgramWorkoutsTableCreateCompanionBuilder, + $$ProgramWorkoutsTableUpdateCompanionBuilder, + (ProgramWorkout, $$ProgramWorkoutsTableReferences), + ProgramWorkout, + PrefetchHooks Function({bool weekId, bool programId}) + > { + $$ProgramWorkoutsTableTableManager( + _$AppDatabase db, + $ProgramWorkoutsTable table, + ) : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$ProgramWorkoutsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$ProgramWorkoutsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$ProgramWorkoutsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value weekId = const Value.absent(), + Value programId = const Value.absent(), + Value day = const Value.absent(), + Value type = const Value.absent(), + Value refId = const Value.absent(), + Value name = const Value.absent(), + Value description = const Value.absent(), + Value completed = const Value.absent(), + Value rowid = const Value.absent(), + }) => ProgramWorkoutsCompanion( + id: id, + weekId: weekId, + programId: programId, + day: day, + type: type, + refId: refId, + name: name, + description: description, + completed: completed, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + required String weekId, + required String programId, + required String day, + required String type, + Value refId = const Value.absent(), + Value name = const Value.absent(), + Value description = const Value.absent(), + Value completed = const Value.absent(), + Value rowid = const Value.absent(), + }) => ProgramWorkoutsCompanion.insert( + id: id, + weekId: weekId, + programId: programId, + day: day, + type: type, + refId: refId, + name: name, + description: description, + completed: completed, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + $$ProgramWorkoutsTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({weekId = false, programId = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: + < + T extends TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (weekId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.weekId, + referencedTable: + $$ProgramWorkoutsTableReferences + ._weekIdTable(db), + referencedColumn: + $$ProgramWorkoutsTableReferences + ._weekIdTable(db) + .id, + ) + as T; + } + if (programId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.programId, + referencedTable: + $$ProgramWorkoutsTableReferences + ._programIdTable(db), + referencedColumn: + $$ProgramWorkoutsTableReferences + ._programIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + ), + ); +} + +typedef $$ProgramWorkoutsTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $ProgramWorkoutsTable, + ProgramWorkout, + $$ProgramWorkoutsTableFilterComposer, + $$ProgramWorkoutsTableOrderingComposer, + $$ProgramWorkoutsTableAnnotationComposer, + $$ProgramWorkoutsTableCreateCompanionBuilder, + $$ProgramWorkoutsTableUpdateCompanionBuilder, + (ProgramWorkout, $$ProgramWorkoutsTableReferences), + ProgramWorkout, + PrefetchHooks Function({bool weekId, bool programId}) + >; +typedef $$AnalysisSessionsTableCreateCompanionBuilder = + AnalysisSessionsCompanion Function({ + required String id, + required String name, + required String date, + Value videoPath, + Value rowid, + }); +typedef $$AnalysisSessionsTableUpdateCompanionBuilder = + AnalysisSessionsCompanion Function({ + Value id, + Value name, + Value date, + Value videoPath, + Value rowid, + }); + +final class $$AnalysisSessionsTableReferences + extends + BaseReferences<_$AppDatabase, $AnalysisSessionsTable, AnalysisSession> { + $$AnalysisSessionsTableReferences( + super.$_db, + super.$_table, + super.$_typedResult, + ); + + static MultiTypedResultKey<$AnnotationsTable, List> + _annotationsRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.annotations, + aliasName: $_aliasNameGenerator( + db.analysisSessions.id, + db.annotations.sessionId, + ), + ); + + $$AnnotationsTableProcessedTableManager get annotationsRefs { + final manager = $$AnnotationsTableTableManager( + $_db, + $_db.annotations, + ).filter((f) => f.sessionId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull(_annotationsRefsTable($_db)); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } +} + +class $$AnalysisSessionsTableFilterComposer + extends Composer<_$AppDatabase, $AnalysisSessionsTable> { + $$AnalysisSessionsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get date => $composableBuilder( + column: $table.date, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get videoPath => $composableBuilder( + column: $table.videoPath, + builder: (column) => ColumnFilters(column), + ); + + Expression annotationsRefs( + Expression Function($$AnnotationsTableFilterComposer f) f, + ) { + final $$AnnotationsTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.annotations, + getReferencedColumn: (t) => t.sessionId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$AnnotationsTableFilterComposer( + $db: $db, + $table: $db.annotations, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$AnalysisSessionsTableOrderingComposer + extends Composer<_$AppDatabase, $AnalysisSessionsTable> { + $$AnalysisSessionsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get date => $composableBuilder( + column: $table.date, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get videoPath => $composableBuilder( + column: $table.videoPath, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$AnalysisSessionsTableAnnotationComposer + extends Composer<_$AppDatabase, $AnalysisSessionsTable> { + $$AnalysisSessionsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + GeneratedColumn get date => + $composableBuilder(column: $table.date, builder: (column) => column); + + GeneratedColumn get videoPath => + $composableBuilder(column: $table.videoPath, builder: (column) => column); + + Expression annotationsRefs( + Expression Function($$AnnotationsTableAnnotationComposer a) f, + ) { + final $$AnnotationsTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.annotations, + getReferencedColumn: (t) => t.sessionId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$AnnotationsTableAnnotationComposer( + $db: $db, + $table: $db.annotations, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$AnalysisSessionsTableTableManager + extends + RootTableManager< + _$AppDatabase, + $AnalysisSessionsTable, + AnalysisSession, + $$AnalysisSessionsTableFilterComposer, + $$AnalysisSessionsTableOrderingComposer, + $$AnalysisSessionsTableAnnotationComposer, + $$AnalysisSessionsTableCreateCompanionBuilder, + $$AnalysisSessionsTableUpdateCompanionBuilder, + (AnalysisSession, $$AnalysisSessionsTableReferences), + AnalysisSession, + PrefetchHooks Function({bool annotationsRefs}) + > { + $$AnalysisSessionsTableTableManager( + _$AppDatabase db, + $AnalysisSessionsTable table, + ) : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$AnalysisSessionsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$AnalysisSessionsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$AnalysisSessionsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value name = const Value.absent(), + Value date = const Value.absent(), + Value videoPath = const Value.absent(), + Value rowid = const Value.absent(), + }) => AnalysisSessionsCompanion( + id: id, + name: name, + date: date, + videoPath: videoPath, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + required String name, + required String date, + Value videoPath = const Value.absent(), + Value rowid = const Value.absent(), + }) => AnalysisSessionsCompanion.insert( + id: id, + name: name, + date: date, + videoPath: videoPath, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + $$AnalysisSessionsTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({annotationsRefs = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [if (annotationsRefs) db.annotations], + addJoins: null, + getPrefetchedDataCallback: (items) async { + return [ + if (annotationsRefs) + await $_getPrefetchedData< + AnalysisSession, + $AnalysisSessionsTable, + Annotation + >( + currentTable: table, + referencedTable: $$AnalysisSessionsTableReferences + ._annotationsRefsTable(db), + managerFromTypedResult: (p0) => + $$AnalysisSessionsTableReferences( + db, + table, + p0, + ).annotationsRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.sessionId == item.id), + typedResults: items, + ), + ]; + }, + ); + }, + ), + ); +} + +typedef $$AnalysisSessionsTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $AnalysisSessionsTable, + AnalysisSession, + $$AnalysisSessionsTableFilterComposer, + $$AnalysisSessionsTableOrderingComposer, + $$AnalysisSessionsTableAnnotationComposer, + $$AnalysisSessionsTableCreateCompanionBuilder, + $$AnalysisSessionsTableUpdateCompanionBuilder, + (AnalysisSession, $$AnalysisSessionsTableReferences), + AnalysisSession, + PrefetchHooks Function({bool annotationsRefs}) + >; +typedef $$AnnotationsTableCreateCompanionBuilder = + AnnotationsCompanion Function({ + required String id, + required String sessionId, + required double startTime, + required double endTime, + Value name, + Value description, + Value color, + Value rowid, + }); +typedef $$AnnotationsTableUpdateCompanionBuilder = + AnnotationsCompanion Function({ + Value id, + Value sessionId, + Value startTime, + Value endTime, + Value name, + Value description, + Value color, + Value rowid, + }); + +final class $$AnnotationsTableReferences + extends BaseReferences<_$AppDatabase, $AnnotationsTable, Annotation> { + $$AnnotationsTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static $AnalysisSessionsTable _sessionIdTable(_$AppDatabase db) => + db.analysisSessions.createAlias( + $_aliasNameGenerator(db.annotations.sessionId, db.analysisSessions.id), + ); + + $$AnalysisSessionsTableProcessedTableManager get sessionId { + final $_column = $_itemColumn('session_id')!; + + final manager = $$AnalysisSessionsTableTableManager( + $_db, + $_db.analysisSessions, + ).filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_sessionIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } +} + +class $$AnnotationsTableFilterComposer + extends Composer<_$AppDatabase, $AnnotationsTable> { + $$AnnotationsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get startTime => $composableBuilder( + column: $table.startTime, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get endTime => $composableBuilder( + column: $table.endTime, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get description => $composableBuilder( + column: $table.description, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get color => $composableBuilder( + column: $table.color, + builder: (column) => ColumnFilters(column), + ); + + $$AnalysisSessionsTableFilterComposer get sessionId { + final $$AnalysisSessionsTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.sessionId, + referencedTable: $db.analysisSessions, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$AnalysisSessionsTableFilterComposer( + $db: $db, + $table: $db.analysisSessions, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$AnnotationsTableOrderingComposer + extends Composer<_$AppDatabase, $AnnotationsTable> { + $$AnnotationsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get startTime => $composableBuilder( + column: $table.startTime, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get endTime => $composableBuilder( + column: $table.endTime, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get description => $composableBuilder( + column: $table.description, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get color => $composableBuilder( + column: $table.color, + builder: (column) => ColumnOrderings(column), + ); + + $$AnalysisSessionsTableOrderingComposer get sessionId { + final $$AnalysisSessionsTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.sessionId, + referencedTable: $db.analysisSessions, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$AnalysisSessionsTableOrderingComposer( + $db: $db, + $table: $db.analysisSessions, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$AnnotationsTableAnnotationComposer + extends Composer<_$AppDatabase, $AnnotationsTable> { + $$AnnotationsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get startTime => + $composableBuilder(column: $table.startTime, builder: (column) => column); + + GeneratedColumn get endTime => + $composableBuilder(column: $table.endTime, builder: (column) => column); + + GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + GeneratedColumn get description => $composableBuilder( + column: $table.description, + builder: (column) => column, + ); + + GeneratedColumn get color => + $composableBuilder(column: $table.color, builder: (column) => column); + + $$AnalysisSessionsTableAnnotationComposer get sessionId { + final $$AnalysisSessionsTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.sessionId, + referencedTable: $db.analysisSessions, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$AnalysisSessionsTableAnnotationComposer( + $db: $db, + $table: $db.analysisSessions, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$AnnotationsTableTableManager + extends + RootTableManager< + _$AppDatabase, + $AnnotationsTable, + Annotation, + $$AnnotationsTableFilterComposer, + $$AnnotationsTableOrderingComposer, + $$AnnotationsTableAnnotationComposer, + $$AnnotationsTableCreateCompanionBuilder, + $$AnnotationsTableUpdateCompanionBuilder, + (Annotation, $$AnnotationsTableReferences), + Annotation, + PrefetchHooks Function({bool sessionId}) + > { + $$AnnotationsTableTableManager(_$AppDatabase db, $AnnotationsTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$AnnotationsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$AnnotationsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$AnnotationsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value sessionId = const Value.absent(), + Value startTime = const Value.absent(), + Value endTime = const Value.absent(), + Value name = const Value.absent(), + Value description = const Value.absent(), + Value color = const Value.absent(), + Value rowid = const Value.absent(), + }) => AnnotationsCompanion( + id: id, + sessionId: sessionId, + startTime: startTime, + endTime: endTime, + name: name, + description: description, + color: color, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + required String sessionId, + required double startTime, + required double endTime, + Value name = const Value.absent(), + Value description = const Value.absent(), + Value color = const Value.absent(), + Value rowid = const Value.absent(), + }) => AnnotationsCompanion.insert( + id: id, + sessionId: sessionId, + startTime: startTime, + endTime: endTime, + name: name, + description: description, + color: color, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + $$AnnotationsTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({sessionId = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: + < + T extends TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (sessionId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.sessionId, + referencedTable: $$AnnotationsTableReferences + ._sessionIdTable(db), + referencedColumn: $$AnnotationsTableReferences + ._sessionIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + ), + ); +} + +typedef $$AnnotationsTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $AnnotationsTable, + Annotation, + $$AnnotationsTableFilterComposer, + $$AnnotationsTableOrderingComposer, + $$AnnotationsTableAnnotationComposer, + $$AnnotationsTableCreateCompanionBuilder, + $$AnnotationsTableUpdateCompanionBuilder, + (Annotation, $$AnnotationsTableReferences), + Annotation, + PrefetchHooks Function({bool sessionId}) + >; +typedef $$ChatSessionsTableCreateCompanionBuilder = + ChatSessionsCompanion Function({ + required String id, + Value title, + required String createdAt, + required String updatedAt, + Value rowid, + }); +typedef $$ChatSessionsTableUpdateCompanionBuilder = + ChatSessionsCompanion Function({ + Value id, + Value title, + Value createdAt, + Value updatedAt, + Value rowid, + }); + +final class $$ChatSessionsTableReferences + extends BaseReferences<_$AppDatabase, $ChatSessionsTable, ChatSession> { + $$ChatSessionsTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static MultiTypedResultKey<$ChatMessagesTable, List> + _chatMessagesRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.chatMessages, + aliasName: $_aliasNameGenerator( + db.chatSessions.id, + db.chatMessages.sessionId, + ), + ); + + $$ChatMessagesTableProcessedTableManager get chatMessagesRefs { + final manager = $$ChatMessagesTableTableManager( + $_db, + $_db.chatMessages, + ).filter((f) => f.sessionId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull(_chatMessagesRefsTable($_db)); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } +} + +class $$ChatSessionsTableFilterComposer + extends Composer<_$AppDatabase, $ChatSessionsTable> { + $$ChatSessionsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get title => $composableBuilder( + column: $table.title, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get updatedAt => $composableBuilder( + column: $table.updatedAt, + builder: (column) => ColumnFilters(column), + ); + + Expression chatMessagesRefs( + Expression Function($$ChatMessagesTableFilterComposer f) f, + ) { + final $$ChatMessagesTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.chatMessages, + getReferencedColumn: (t) => t.sessionId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ChatMessagesTableFilterComposer( + $db: $db, + $table: $db.chatMessages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$ChatSessionsTableOrderingComposer + extends Composer<_$AppDatabase, $ChatSessionsTable> { + $$ChatSessionsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get title => $composableBuilder( + column: $table.title, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get updatedAt => $composableBuilder( + column: $table.updatedAt, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$ChatSessionsTableAnnotationComposer + extends Composer<_$AppDatabase, $ChatSessionsTable> { + $$ChatSessionsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get title => + $composableBuilder(column: $table.title, builder: (column) => column); + + GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); + + GeneratedColumn get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => column); + + Expression chatMessagesRefs( + Expression Function($$ChatMessagesTableAnnotationComposer a) f, + ) { + final $$ChatMessagesTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.chatMessages, + getReferencedColumn: (t) => t.sessionId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ChatMessagesTableAnnotationComposer( + $db: $db, + $table: $db.chatMessages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$ChatSessionsTableTableManager + extends + RootTableManager< + _$AppDatabase, + $ChatSessionsTable, + ChatSession, + $$ChatSessionsTableFilterComposer, + $$ChatSessionsTableOrderingComposer, + $$ChatSessionsTableAnnotationComposer, + $$ChatSessionsTableCreateCompanionBuilder, + $$ChatSessionsTableUpdateCompanionBuilder, + (ChatSession, $$ChatSessionsTableReferences), + ChatSession, + PrefetchHooks Function({bool chatMessagesRefs}) + > { + $$ChatSessionsTableTableManager(_$AppDatabase db, $ChatSessionsTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$ChatSessionsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$ChatSessionsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$ChatSessionsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value title = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value rowid = const Value.absent(), + }) => ChatSessionsCompanion( + id: id, + title: title, + createdAt: createdAt, + updatedAt: updatedAt, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + Value title = const Value.absent(), + required String createdAt, + required String updatedAt, + Value rowid = const Value.absent(), + }) => ChatSessionsCompanion.insert( + id: id, + title: title, + createdAt: createdAt, + updatedAt: updatedAt, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + $$ChatSessionsTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({chatMessagesRefs = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [if (chatMessagesRefs) db.chatMessages], + addJoins: null, + getPrefetchedDataCallback: (items) async { + return [ + if (chatMessagesRefs) + await $_getPrefetchedData< + ChatSession, + $ChatSessionsTable, + ChatMessage + >( + currentTable: table, + referencedTable: $$ChatSessionsTableReferences + ._chatMessagesRefsTable(db), + managerFromTypedResult: (p0) => + $$ChatSessionsTableReferences( + db, + table, + p0, + ).chatMessagesRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.sessionId == item.id), + typedResults: items, + ), + ]; + }, + ); + }, + ), + ); +} + +typedef $$ChatSessionsTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $ChatSessionsTable, + ChatSession, + $$ChatSessionsTableFilterComposer, + $$ChatSessionsTableOrderingComposer, + $$ChatSessionsTableAnnotationComposer, + $$ChatSessionsTableCreateCompanionBuilder, + $$ChatSessionsTableUpdateCompanionBuilder, + (ChatSession, $$ChatSessionsTableReferences), + ChatSession, + PrefetchHooks Function({bool chatMessagesRefs}) + >; +typedef $$ChatMessagesTableCreateCompanionBuilder = + ChatMessagesCompanion Function({ + required String id, + required String sessionId, + required String role, + required String content, + required String createdAt, + Value rowid, + }); +typedef $$ChatMessagesTableUpdateCompanionBuilder = + ChatMessagesCompanion Function({ + Value id, + Value sessionId, + Value role, + Value content, + Value createdAt, + Value rowid, + }); + +final class $$ChatMessagesTableReferences + extends BaseReferences<_$AppDatabase, $ChatMessagesTable, ChatMessage> { + $$ChatMessagesTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static $ChatSessionsTable _sessionIdTable(_$AppDatabase db) => + db.chatSessions.createAlias( + $_aliasNameGenerator(db.chatMessages.sessionId, db.chatSessions.id), + ); + + $$ChatSessionsTableProcessedTableManager get sessionId { + final $_column = $_itemColumn('session_id')!; + + final manager = $$ChatSessionsTableTableManager( + $_db, + $_db.chatSessions, + ).filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_sessionIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } +} + +class $$ChatMessagesTableFilterComposer + extends Composer<_$AppDatabase, $ChatMessagesTable> { + $$ChatMessagesTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get role => $composableBuilder( + column: $table.role, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get content => $composableBuilder( + column: $table.content, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnFilters(column), + ); + + $$ChatSessionsTableFilterComposer get sessionId { + final $$ChatSessionsTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.sessionId, + referencedTable: $db.chatSessions, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ChatSessionsTableFilterComposer( + $db: $db, + $table: $db.chatSessions, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$ChatMessagesTableOrderingComposer + extends Composer<_$AppDatabase, $ChatMessagesTable> { + $$ChatMessagesTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get role => $composableBuilder( + column: $table.role, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get content => $composableBuilder( + column: $table.content, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnOrderings(column), + ); + + $$ChatSessionsTableOrderingComposer get sessionId { + final $$ChatSessionsTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.sessionId, + referencedTable: $db.chatSessions, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ChatSessionsTableOrderingComposer( + $db: $db, + $table: $db.chatSessions, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$ChatMessagesTableAnnotationComposer + extends Composer<_$AppDatabase, $ChatMessagesTable> { + $$ChatMessagesTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get role => + $composableBuilder(column: $table.role, builder: (column) => column); + + GeneratedColumn get content => + $composableBuilder(column: $table.content, builder: (column) => column); + + GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); + + $$ChatSessionsTableAnnotationComposer get sessionId { + final $$ChatSessionsTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.sessionId, + referencedTable: $db.chatSessions, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ChatSessionsTableAnnotationComposer( + $db: $db, + $table: $db.chatSessions, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$ChatMessagesTableTableManager + extends + RootTableManager< + _$AppDatabase, + $ChatMessagesTable, + ChatMessage, + $$ChatMessagesTableFilterComposer, + $$ChatMessagesTableOrderingComposer, + $$ChatMessagesTableAnnotationComposer, + $$ChatMessagesTableCreateCompanionBuilder, + $$ChatMessagesTableUpdateCompanionBuilder, + (ChatMessage, $$ChatMessagesTableReferences), + ChatMessage, + PrefetchHooks Function({bool sessionId}) + > { + $$ChatMessagesTableTableManager(_$AppDatabase db, $ChatMessagesTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$ChatMessagesTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$ChatMessagesTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$ChatMessagesTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value sessionId = const Value.absent(), + Value role = const Value.absent(), + Value content = const Value.absent(), + Value createdAt = const Value.absent(), + Value rowid = const Value.absent(), + }) => ChatMessagesCompanion( + id: id, + sessionId: sessionId, + role: role, + content: content, + createdAt: createdAt, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + required String sessionId, + required String role, + required String content, + required String createdAt, + Value rowid = const Value.absent(), + }) => ChatMessagesCompanion.insert( + id: id, + sessionId: sessionId, + role: role, + content: content, + createdAt: createdAt, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + $$ChatMessagesTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({sessionId = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: + < + T extends TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (sessionId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.sessionId, + referencedTable: $$ChatMessagesTableReferences + ._sessionIdTable(db), + referencedColumn: $$ChatMessagesTableReferences + ._sessionIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + ), + ); +} + +typedef $$ChatMessagesTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $ChatMessagesTable, + ChatMessage, + $$ChatMessagesTableFilterComposer, + $$ChatMessagesTableOrderingComposer, + $$ChatMessagesTableAnnotationComposer, + $$ChatMessagesTableCreateCompanionBuilder, + $$ChatMessagesTableUpdateCompanionBuilder, + (ChatMessage, $$ChatMessagesTableReferences), + ChatMessage, + PrefetchHooks Function({bool sessionId}) + >; + +class $AppDatabaseManager { + final _$AppDatabase _db; + $AppDatabaseManager(this._db); + $$ExercisesTableTableManager get exercises => + $$ExercisesTableTableManager(_db, _db.exercises); + $$TrainingPlansTableTableManager get trainingPlans => + $$TrainingPlansTableTableManager(_db, _db.trainingPlans); + $$ProgramsTableTableManager get programs => + $$ProgramsTableTableManager(_db, _db.programs); + $$ProgramWeeksTableTableManager get programWeeks => + $$ProgramWeeksTableTableManager(_db, _db.programWeeks); + $$ProgramWorkoutsTableTableManager get programWorkouts => + $$ProgramWorkoutsTableTableManager(_db, _db.programWorkouts); + $$AnalysisSessionsTableTableManager get analysisSessions => + $$AnalysisSessionsTableTableManager(_db, _db.analysisSessions); + $$AnnotationsTableTableManager get annotations => + $$AnnotationsTableTableManager(_db, _db.annotations); + $$ChatSessionsTableTableManager get chatSessions => + $$ChatSessionsTableTableManager(_db, _db.chatSessions); + $$ChatMessagesTableTableManager get chatMessages => + $$ChatMessagesTableTableManager(_db, _db.chatMessages); +} diff --git a/lib/data/database/daos/analysis_dao.dart b/lib/data/database/daos/analysis_dao.dart new file mode 100644 index 0000000..3cbb718 --- /dev/null +++ b/lib/data/database/daos/analysis_dao.dart @@ -0,0 +1,32 @@ +import 'package:drift/drift.dart'; +import 'package:trainhub_flutter/data/database/app_database.dart'; + +part 'analysis_dao.g.dart'; + +@DriftAccessor(tables: [AnalysisSessions, Annotations]) +class AnalysisDao extends DatabaseAccessor + with _$AnalysisDaoMixin { + AnalysisDao(super.db); + + Future> getAllSessions() => + select(analysisSessions).get(); + + Future getSession(String id) => + (select(analysisSessions)..where((t) => t.id.equals(id))) + .getSingleOrNull(); + + Future insertSession(AnalysisSessionsCompanion entry) => + into(analysisSessions).insert(entry); + + Future deleteSession(String id) => + (delete(analysisSessions)..where((t) => t.id.equals(id))).go(); + + Future> getAnnotations(String sessionId) => + (select(annotations)..where((t) => t.sessionId.equals(sessionId))).get(); + + Future insertAnnotation(AnnotationsCompanion entry) => + into(annotations).insert(entry); + + Future deleteAnnotation(String id) => + (delete(annotations)..where((t) => t.id.equals(id))).go(); +} diff --git a/lib/data/database/daos/analysis_dao.g.dart b/lib/data/database/daos/analysis_dao.g.dart new file mode 100644 index 0000000..abad598 --- /dev/null +++ b/lib/data/database/daos/analysis_dao.g.dart @@ -0,0 +1,10 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'analysis_dao.dart'; + +// ignore_for_file: type=lint +mixin _$AnalysisDaoMixin on DatabaseAccessor { + $AnalysisSessionsTable get analysisSessions => + attachedDatabase.analysisSessions; + $AnnotationsTable get annotations => attachedDatabase.annotations; +} diff --git a/lib/data/database/daos/chat_dao.dart b/lib/data/database/daos/chat_dao.dart new file mode 100644 index 0000000..3981bc3 --- /dev/null +++ b/lib/data/database/daos/chat_dao.dart @@ -0,0 +1,43 @@ +import 'package:drift/drift.dart'; +import 'package:trainhub_flutter/data/database/app_database.dart'; + +part 'chat_dao.g.dart'; + +@DriftAccessor(tables: [ChatSessions, ChatMessages]) +class ChatDao extends DatabaseAccessor with _$ChatDaoMixin { + ChatDao(super.db); + + Future> getAllSessions() => + (select(chatSessions) + ..orderBy([ + (t) => OrderingTerm( + expression: t.createdAt, mode: OrderingMode.desc) + ])) + .get(); + + Future getSession(String id) => + (select(chatSessions)..where((t) => t.id.equals(id))) + .getSingleOrNull(); + + Future insertSession(ChatSessionsCompanion entry) => + into(chatSessions).insert(entry); + + Future deleteSession(String id) => + (delete(chatSessions)..where((t) => t.id.equals(id))).go(); + + Future updateSessionTitle(String id, String title) => + (update(chatSessions)..where((t) => t.id.equals(id))) + .write(ChatSessionsCompanion(title: Value(title))); + + Future> getMessages(String sessionId) => + (select(chatMessages) + ..where((t) => t.sessionId.equals(sessionId)) + ..orderBy([ + (t) => + OrderingTerm(expression: t.createdAt, mode: OrderingMode.asc) + ])) + .get(); + + Future insertMessage(ChatMessagesCompanion entry) => + into(chatMessages).insert(entry); +} diff --git a/lib/data/database/daos/chat_dao.g.dart b/lib/data/database/daos/chat_dao.g.dart new file mode 100644 index 0000000..79d0505 --- /dev/null +++ b/lib/data/database/daos/chat_dao.g.dart @@ -0,0 +1,9 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'chat_dao.dart'; + +// ignore_for_file: type=lint +mixin _$ChatDaoMixin on DatabaseAccessor { + $ChatSessionsTable get chatSessions => attachedDatabase.chatSessions; + $ChatMessagesTable get chatMessages => attachedDatabase.chatMessages; +} diff --git a/lib/data/database/daos/exercise_dao.dart b/lib/data/database/daos/exercise_dao.dart new file mode 100644 index 0000000..1671ef5 --- /dev/null +++ b/lib/data/database/daos/exercise_dao.dart @@ -0,0 +1,24 @@ +import 'package:drift/drift.dart'; +import 'package:trainhub_flutter/data/database/app_database.dart'; + +part 'exercise_dao.g.dart'; + +@DriftAccessor(tables: [Exercises]) +class ExerciseDao extends DatabaseAccessor + with _$ExerciseDaoMixin { + ExerciseDao(super.db); + + Future> getAllExercises() => select(exercises).get(); + + Future getExerciseById(String id) => + (select(exercises)..where((t) => t.id.equals(id))).getSingle(); + + Future insertExercise(ExercisesCompanion entry) => + into(exercises).insert(entry); + + Future updateExercise(String id, ExercisesCompanion entry) => + (update(exercises)..where((t) => t.id.equals(id))).write(entry); + + Future deleteExercise(String id) => + (delete(exercises)..where((t) => t.id.equals(id))).go(); +} diff --git a/lib/data/database/daos/exercise_dao.g.dart b/lib/data/database/daos/exercise_dao.g.dart new file mode 100644 index 0000000..c3a7255 --- /dev/null +++ b/lib/data/database/daos/exercise_dao.g.dart @@ -0,0 +1,8 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'exercise_dao.dart'; + +// ignore_for_file: type=lint +mixin _$ExerciseDaoMixin on DatabaseAccessor { + $ExercisesTable get exercises => attachedDatabase.exercises; +} diff --git a/lib/data/database/daos/program_dao.dart b/lib/data/database/daos/program_dao.dart new file mode 100644 index 0000000..b607207 --- /dev/null +++ b/lib/data/database/daos/program_dao.dart @@ -0,0 +1,60 @@ +import 'package:drift/drift.dart'; +import 'package:trainhub_flutter/data/database/app_database.dart'; + +part 'program_dao.g.dart'; + +@DriftAccessor(tables: [Programs, ProgramWeeks, ProgramWorkouts]) +class ProgramDao extends DatabaseAccessor + with _$ProgramDaoMixin { + ProgramDao(super.db); + + Future> getAllPrograms() => + (select(programs) + ..orderBy([ + (t) => + OrderingTerm(expression: t.createdAt, mode: OrderingMode.desc) + ])) + .get(); + + Future getProgram(String id) => + (select(programs)..where((t) => t.id.equals(id))).getSingleOrNull(); + + Future insertProgram(ProgramsCompanion entry) => + into(programs).insert(entry); + + Future deleteProgram(String id) => + (delete(programs)..where((t) => t.id.equals(id))).go(); + + Future> getWeeks(String programId) => + (select(programWeeks) + ..where((t) => t.programId.equals(programId)) + ..orderBy([(t) => OrderingTerm(expression: t.position)])) + .get(); + + Future insertWeek(ProgramWeeksCompanion entry) => + into(programWeeks).insert(entry); + + Future deleteWeek(String id) => + (delete(programWeeks)..where((t) => t.id.equals(id))).go(); + + Future updateWeekNote(String weekId, String note) => + (update(programWeeks)..where((t) => t.id.equals(weekId))) + .write(ProgramWeeksCompanion(notes: Value(note))); + + Future> getWorkouts(String programId) => + (select(programWorkouts)..where((t) => t.programId.equals(programId))) + .get(); + + Future insertWorkout(ProgramWorkoutsCompanion entry) => + into(programWorkouts).insert(entry); + + Future updateWorkout(String id, ProgramWorkoutsCompanion entry) => + (update(programWorkouts)..where((t) => t.id.equals(id))).write(entry); + + Future deleteWorkout(String id) => + (delete(programWorkouts)..where((t) => t.id.equals(id))).go(); + + Future toggleWorkoutComplete(String id, bool currentStatus) => + (update(programWorkouts)..where((t) => t.id.equals(id))) + .write(ProgramWorkoutsCompanion(completed: Value(!currentStatus))); +} diff --git a/lib/data/database/daos/program_dao.g.dart b/lib/data/database/daos/program_dao.g.dart new file mode 100644 index 0000000..5852b3e --- /dev/null +++ b/lib/data/database/daos/program_dao.g.dart @@ -0,0 +1,10 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'program_dao.dart'; + +// ignore_for_file: type=lint +mixin _$ProgramDaoMixin on DatabaseAccessor { + $ProgramsTable get programs => attachedDatabase.programs; + $ProgramWeeksTable get programWeeks => attachedDatabase.programWeeks; + $ProgramWorkoutsTable get programWorkouts => attachedDatabase.programWorkouts; +} diff --git a/lib/data/database/daos/training_plan_dao.dart b/lib/data/database/daos/training_plan_dao.dart new file mode 100644 index 0000000..178b2d5 --- /dev/null +++ b/lib/data/database/daos/training_plan_dao.dart @@ -0,0 +1,24 @@ +import 'package:drift/drift.dart'; +import 'package:trainhub_flutter/data/database/app_database.dart'; + +part 'training_plan_dao.g.dart'; + +@DriftAccessor(tables: [TrainingPlans]) +class TrainingPlanDao extends DatabaseAccessor + with _$TrainingPlanDaoMixin { + TrainingPlanDao(super.db); + + Future> getAllPlans() => select(trainingPlans).get(); + + Future getPlanById(String id) => + (select(trainingPlans)..where((t) => t.id.equals(id))).getSingle(); + + Future insertPlan(TrainingPlansCompanion entry) => + into(trainingPlans).insert(entry); + + Future updatePlan(String id, TrainingPlansCompanion entry) => + (update(trainingPlans)..where((t) => t.id.equals(id))).write(entry); + + Future deletePlan(String id) => + (delete(trainingPlans)..where((t) => t.id.equals(id))).go(); +} diff --git a/lib/data/database/daos/training_plan_dao.g.dart b/lib/data/database/daos/training_plan_dao.g.dart new file mode 100644 index 0000000..fad5837 --- /dev/null +++ b/lib/data/database/daos/training_plan_dao.g.dart @@ -0,0 +1,8 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'training_plan_dao.dart'; + +// ignore_for_file: type=lint +mixin _$TrainingPlanDaoMixin on DatabaseAccessor { + $TrainingPlansTable get trainingPlans => attachedDatabase.trainingPlans; +} diff --git a/lib/data/mappers/analysis_mapper.dart b/lib/data/mappers/analysis_mapper.dart new file mode 100644 index 0000000..b8e1fb2 --- /dev/null +++ b/lib/data/mappers/analysis_mapper.dart @@ -0,0 +1,28 @@ +import 'package:trainhub_flutter/data/database/app_database.dart'; +import 'package:trainhub_flutter/domain/entities/analysis_session.dart'; +import 'package:trainhub_flutter/domain/entities/annotation.dart'; + +class AnalysisMapper { + AnalysisMapper._(); + + static AnalysisSessionEntity sessionToEntity(AnalysisSession row) { + return AnalysisSessionEntity( + id: row.id, + name: row.name, + date: row.date, + videoPath: row.videoPath, + ); + } + + static AnnotationEntity annotationToEntity(Annotation row) { + return AnnotationEntity( + id: row.id, + sessionId: row.sessionId, + startTime: row.startTime, + endTime: row.endTime, + name: row.name, + description: row.description, + color: row.color, + ); + } +} diff --git a/lib/data/mappers/chat_mapper.dart b/lib/data/mappers/chat_mapper.dart new file mode 100644 index 0000000..a812e56 --- /dev/null +++ b/lib/data/mappers/chat_mapper.dart @@ -0,0 +1,26 @@ +import 'package:trainhub_flutter/data/database/app_database.dart'; +import 'package:trainhub_flutter/domain/entities/chat_session.dart'; +import 'package:trainhub_flutter/domain/entities/chat_message.dart'; + +class ChatMapper { + ChatMapper._(); + + static ChatSessionEntity sessionToEntity(ChatSession row) { + return ChatSessionEntity( + id: row.id, + title: row.title, + createdAt: row.createdAt, + updatedAt: row.updatedAt, + ); + } + + static ChatMessageEntity messageToEntity(ChatMessage row) { + return ChatMessageEntity( + id: row.id, + sessionId: row.sessionId, + role: row.role, + content: row.content, + createdAt: row.createdAt, + ); + } +} diff --git a/lib/data/mappers/exercise_mapper.dart b/lib/data/mappers/exercise_mapper.dart new file mode 100644 index 0000000..72bee14 --- /dev/null +++ b/lib/data/mappers/exercise_mapper.dart @@ -0,0 +1,39 @@ +import 'package:drift/drift.dart'; +import 'package:trainhub_flutter/data/database/app_database.dart'; +import 'package:trainhub_flutter/domain/entities/exercise.dart'; + +class ExerciseMapper { + ExerciseMapper._(); + + static ExerciseEntity toEntity(Exercise row) { + return ExerciseEntity( + id: row.id, + name: row.name, + instructions: row.instructions, + enrichment: row.enrichment, + tags: row.tags, + videoUrl: row.videoUrl, + ); + } + + static ExercisesCompanion toCompanion(ExerciseEntity entity) { + return ExercisesCompanion( + id: Value(entity.id), + name: Value(entity.name), + instructions: Value(entity.instructions), + enrichment: Value(entity.enrichment), + tags: Value(entity.tags), + videoUrl: Value(entity.videoUrl), + ); + } + + static ExercisesCompanion toUpdateCompanion(ExerciseEntity entity) { + return ExercisesCompanion( + name: Value(entity.name), + instructions: Value(entity.instructions), + enrichment: Value(entity.enrichment), + tags: Value(entity.tags), + videoUrl: Value(entity.videoUrl), + ); + } +} diff --git a/lib/data/mappers/program_mapper.dart b/lib/data/mappers/program_mapper.dart new file mode 100644 index 0000000..86175cc --- /dev/null +++ b/lib/data/mappers/program_mapper.dart @@ -0,0 +1,68 @@ +import 'package:drift/drift.dart'; +import 'package:trainhub_flutter/data/database/app_database.dart'; +import 'package:trainhub_flutter/domain/entities/program.dart'; +import 'package:trainhub_flutter/domain/entities/program_week.dart'; +import 'package:trainhub_flutter/domain/entities/program_workout.dart'; + +class ProgramMapper { + ProgramMapper._(); + + static ProgramEntity toEntity(Program row) { + return ProgramEntity( + id: row.id, + name: row.name, + createdAt: row.createdAt, + ); + } + + static ProgramWeekEntity weekToEntity(ProgramWeek row) { + return ProgramWeekEntity( + id: row.id, + programId: row.programId, + position: row.position, + notes: row.notes, + ); + } + + static ProgramWorkoutEntity workoutToEntity(ProgramWorkout row) { + return ProgramWorkoutEntity( + id: row.id, + weekId: row.weekId, + programId: row.programId, + day: row.day, + type: row.type, + refId: row.refId, + name: row.name, + description: row.description, + completed: row.completed, + ); + } + + static ProgramWorkoutsCompanion workoutToCompanion( + ProgramWorkoutEntity entity) { + return ProgramWorkoutsCompanion( + id: Value(entity.id), + weekId: Value(entity.weekId), + programId: Value(entity.programId), + day: Value(entity.day), + type: Value(entity.type), + refId: Value(entity.refId), + name: Value(entity.name), + description: Value(entity.description), + completed: Value(entity.completed), + ); + } + + static ProgramWorkoutsCompanion workoutToUpdateCompanion( + ProgramWorkoutEntity entity) { + return ProgramWorkoutsCompanion( + day: Value(entity.day), + type: Value(entity.type), + refId: Value(entity.refId), + name: Value(entity.name), + description: Value(entity.description), + completed: Value(entity.completed), + weekId: Value(entity.weekId), + ); + } +} diff --git a/lib/data/mappers/training_plan_mapper.dart b/lib/data/mappers/training_plan_mapper.dart new file mode 100644 index 0000000..c8bbe7d --- /dev/null +++ b/lib/data/mappers/training_plan_mapper.dart @@ -0,0 +1,87 @@ +import 'dart:convert'; +import 'package:drift/drift.dart'; +import 'package:trainhub_flutter/data/database/app_database.dart'; +import 'package:trainhub_flutter/domain/entities/training_plan.dart'; +import 'package:trainhub_flutter/domain/entities/training_section.dart'; +import 'package:trainhub_flutter/domain/entities/training_exercise.dart'; + +class TrainingPlanMapper { + TrainingPlanMapper._(); + + static TrainingPlanEntity toEntity(TrainingPlan row) { + final List sectionsJson = + row.sections != null && row.sections!.isNotEmpty + ? jsonDecode(row.sections!) as List + : []; + return TrainingPlanEntity( + id: row.id, + name: row.name, + sections: sectionsJson + .map((s) => _mapSection(s as Map)) + .toList(), + ); + } + + static TrainingSectionEntity _mapSection(Map json) { + final exercisesJson = json['exercises'] as List? ?? []; + return TrainingSectionEntity( + id: json['id'] as String? ?? '', + name: json['name'] as String? ?? '', + exercises: exercisesJson + .map((e) => _mapExercise(e as Map)) + .toList(), + ); + } + + static TrainingExerciseEntity _mapExercise(Map json) { + return TrainingExerciseEntity( + instanceId: json['instanceId'] as String? ?? '', + exerciseId: json['exerciseId'] as String? ?? '', + name: json['name'] as String? ?? '', + sets: json['sets'] as int? ?? 3, + value: json['value'] as int? ?? 10, + isTime: json['isTime'] as bool? ?? false, + rest: json['rest'] as int? ?? 60, + ); + } + + static String sectionsToJson(List sections) { + return jsonEncode(sections.map((s) => _sectionToMap(s)).toList()); + } + + static Map _sectionToMap(TrainingSectionEntity section) { + return { + 'id': section.id, + 'name': section.name, + 'exercises': section.exercises.map((e) => _exerciseToMap(e)).toList(), + }; + } + + static Map _exerciseToMap(TrainingExerciseEntity exercise) { + return { + 'instanceId': exercise.instanceId, + 'exerciseId': exercise.exerciseId, + 'name': exercise.name, + 'sets': exercise.sets, + 'value': exercise.value, + 'isTime': exercise.isTime, + 'rest': exercise.rest, + }; + } + + static TrainingPlansCompanion toInsertCompanion( + String id, String name) { + return TrainingPlansCompanion.insert( + id: id, + name: name, + sections: const Value('[]'), + ); + } + + static TrainingPlansCompanion toUpdateCompanion(TrainingPlanEntity entity) { + return TrainingPlansCompanion( + name: Value(entity.name), + sections: Value(sectionsToJson(entity.sections)), + ); + } +} diff --git a/lib/data/repositories/analysis_repository_impl.dart b/lib/data/repositories/analysis_repository_impl.dart new file mode 100644 index 0000000..35a1c6a --- /dev/null +++ b/lib/data/repositories/analysis_repository_impl.dart @@ -0,0 +1,90 @@ +import 'package:drift/drift.dart'; +import 'package:trainhub_flutter/core/utils/id_generator.dart'; +import 'package:trainhub_flutter/data/database/app_database.dart'; +import 'package:trainhub_flutter/data/database/daos/analysis_dao.dart'; +import 'package:trainhub_flutter/data/mappers/analysis_mapper.dart'; +import 'package:trainhub_flutter/domain/entities/analysis_session.dart'; +import 'package:trainhub_flutter/domain/entities/annotation.dart'; +import 'package:trainhub_flutter/domain/repositories/analysis_repository.dart'; + +class AnalysisRepositoryImpl implements AnalysisRepository { + final AnalysisDao _dao; + + AnalysisRepositoryImpl(this._dao); + + @override + Future> getAllSessions() async { + final rows = await _dao.getAllSessions(); + return rows.map(AnalysisMapper.sessionToEntity).toList(); + } + + @override + Future getSession(String id) async { + final row = await _dao.getSession(id); + return row == null ? null : AnalysisMapper.sessionToEntity(row); + } + + @override + Future createSession( + String name, String videoPath) async { + final String id = IdGenerator.generate(); + await _dao.insertSession( + AnalysisSessionsCompanion.insert( + id: id, + name: name, + videoPath: Value(videoPath), + date: DateTime.now().toIso8601String(), + ), + ); + final row = await _dao.getSession(id); + return AnalysisMapper.sessionToEntity(row!); + } + + @override + Future deleteSession(String id) async { + await _dao.deleteSession(id); + } + + @override + Future> getAnnotations(String sessionId) async { + final rows = await _dao.getAnnotations(sessionId); + return rows.map(AnalysisMapper.annotationToEntity).toList(); + } + + @override + Future addAnnotation({ + required String sessionId, + required String name, + required String description, + required double startTime, + required double endTime, + required String color, + }) async { + final String id = IdGenerator.generate(); + await _dao.insertAnnotation( + AnnotationsCompanion.insert( + id: id, + sessionId: sessionId, + name: Value(name), + description: Value(description), + startTime: startTime, + endTime: endTime, + color: Value(color), + ), + ); + return AnnotationEntity( + id: id, + sessionId: sessionId, + startTime: startTime, + endTime: endTime, + name: name, + description: description, + color: color, + ); + } + + @override + Future deleteAnnotation(String id) async { + await _dao.deleteAnnotation(id); + } +} diff --git a/lib/data/repositories/chat_repository_impl.dart b/lib/data/repositories/chat_repository_impl.dart new file mode 100644 index 0000000..421136e --- /dev/null +++ b/lib/data/repositories/chat_repository_impl.dart @@ -0,0 +1,84 @@ +import 'package:drift/drift.dart'; +import 'package:trainhub_flutter/core/utils/id_generator.dart'; +import 'package:trainhub_flutter/data/database/app_database.dart'; +import 'package:trainhub_flutter/data/database/daos/chat_dao.dart'; +import 'package:trainhub_flutter/data/mappers/chat_mapper.dart'; +import 'package:trainhub_flutter/domain/entities/chat_session.dart'; +import 'package:trainhub_flutter/domain/entities/chat_message.dart'; +import 'package:trainhub_flutter/domain/repositories/chat_repository.dart'; + +class ChatRepositoryImpl implements ChatRepository { + final ChatDao _dao; + + ChatRepositoryImpl(this._dao); + + @override + Future> getAllSessions() async { + final rows = await _dao.getAllSessions(); + return rows.map(ChatMapper.sessionToEntity).toList(); + } + + @override + Future getSession(String id) async { + final row = await _dao.getSession(id); + return row == null ? null : ChatMapper.sessionToEntity(row); + } + + @override + Future createSession() async { + final String id = IdGenerator.generate(); + final String now = DateTime.now().toIso8601String(); + await _dao.insertSession( + ChatSessionsCompanion.insert( + id: id, + title: const Value('New Chat'), + createdAt: now, + updatedAt: now, + ), + ); + final row = await _dao.getSession(id); + return ChatMapper.sessionToEntity(row!); + } + + @override + Future deleteSession(String id) async { + await _dao.deleteSession(id); + } + + @override + Future> getMessages(String sessionId) async { + final rows = await _dao.getMessages(sessionId); + return rows.map(ChatMapper.messageToEntity).toList(); + } + + @override + Future addMessage({ + required String sessionId, + required String role, + required String content, + }) async { + final String id = IdGenerator.generate(); + final String now = DateTime.now().toIso8601String(); + await _dao.insertMessage( + ChatMessagesCompanion.insert( + id: id, + sessionId: sessionId, + role: role, + content: content, + createdAt: now, + ), + ); + return ChatMessageEntity( + id: id, + sessionId: sessionId, + role: role, + content: content, + createdAt: now, + ); + } + + @override + Future updateSessionTitle(String sessionId, String title) async { + await _dao.updateSessionTitle(sessionId, title); + } +} diff --git a/lib/data/repositories/exercise_repository_impl.dart b/lib/data/repositories/exercise_repository_impl.dart new file mode 100644 index 0000000..6a6758d --- /dev/null +++ b/lib/data/repositories/exercise_repository_impl.dart @@ -0,0 +1,53 @@ +import 'package:drift/drift.dart'; +import 'package:trainhub_flutter/core/utils/id_generator.dart'; +import 'package:trainhub_flutter/data/database/daos/exercise_dao.dart'; +import 'package:trainhub_flutter/data/mappers/exercise_mapper.dart'; +import 'package:trainhub_flutter/domain/entities/exercise.dart'; +import 'package:trainhub_flutter/domain/repositories/exercise_repository.dart'; +import 'package:trainhub_flutter/data/database/app_database.dart'; + +class ExerciseRepositoryImpl implements ExerciseRepository { + final ExerciseDao _dao; + + ExerciseRepositoryImpl(this._dao); + + @override + Future> getAll() async { + final rows = await _dao.getAllExercises(); + return rows.map(ExerciseMapper.toEntity).toList(); + } + + @override + Future create({ + required String name, + String? instructions, + String? tags, + String? videoUrl, + }) async { + final String id = IdGenerator.generate(); + await _dao.insertExercise( + ExercisesCompanion.insert( + id: id, + name: name, + instructions: Value(instructions), + tags: Value(tags), + videoUrl: Value(videoUrl), + ), + ); + final row = await _dao.getExerciseById(id); + return ExerciseMapper.toEntity(row); + } + + @override + Future update(ExerciseEntity exercise) async { + await _dao.updateExercise( + exercise.id, + ExerciseMapper.toUpdateCompanion(exercise), + ); + } + + @override + Future delete(String id) async { + await _dao.deleteExercise(id); + } +} diff --git a/lib/data/repositories/program_repository_impl.dart b/lib/data/repositories/program_repository_impl.dart new file mode 100644 index 0000000..03c7719 --- /dev/null +++ b/lib/data/repositories/program_repository_impl.dart @@ -0,0 +1,151 @@ +import 'package:drift/drift.dart'; +import 'package:trainhub_flutter/core/utils/id_generator.dart'; +import 'package:trainhub_flutter/data/database/app_database.dart'; +import 'package:trainhub_flutter/data/database/daos/program_dao.dart'; +import 'package:trainhub_flutter/data/mappers/program_mapper.dart'; +import 'package:trainhub_flutter/domain/entities/program.dart'; +import 'package:trainhub_flutter/domain/entities/program_week.dart'; +import 'package:trainhub_flutter/domain/entities/program_workout.dart'; +import 'package:trainhub_flutter/domain/repositories/program_repository.dart'; + +class ProgramRepositoryImpl implements ProgramRepository { + final ProgramDao _dao; + + ProgramRepositoryImpl(this._dao); + + @override + Future> getAllPrograms() async { + final rows = await _dao.getAllPrograms(); + return rows.map(ProgramMapper.toEntity).toList(); + } + + @override + Future getProgram(String id) async { + final row = await _dao.getProgram(id); + return row == null ? null : ProgramMapper.toEntity(row); + } + + @override + Future> getWeeks(String programId) async { + final rows = await _dao.getWeeks(programId); + return rows.map(ProgramMapper.weekToEntity).toList(); + } + + @override + Future> getWorkouts(String programId) async { + final rows = await _dao.getWorkouts(programId); + return rows.map(ProgramMapper.workoutToEntity).toList(); + } + + @override + Future createProgram(String name) async { + final String id = IdGenerator.generate(); + await _dao.insertProgram( + ProgramsCompanion.insert( + id: id, + name: name, + createdAt: DateTime.now().toIso8601String(), + ), + ); + final row = await _dao.getProgram(id); + return ProgramMapper.toEntity(row!); + } + + @override + Future deleteProgram(String id) async { + await _dao.deleteProgram(id); + } + + @override + Future duplicateProgram(String sourceId) async { + final sourceProgram = await _dao.getProgram(sourceId); + if (sourceProgram == null) return; + final String newId = IdGenerator.generate(); + await _dao.insertProgram( + ProgramsCompanion.insert( + id: newId, + name: '${sourceProgram.name} (Copy)', + createdAt: DateTime.now().toIso8601String(), + ), + ); + final weeks = await _dao.getWeeks(sourceId); + final workouts = await _dao.getWorkouts(sourceId); + for (final week in weeks) { + final String newWeekId = IdGenerator.generate(); + await _dao.insertWeek( + ProgramWeeksCompanion.insert( + id: newWeekId, + programId: newId, + position: week.position, + notes: Value(week.notes), + ), + ); + final weekWorkouts = workouts.where((w) => w.weekId == week.id); + for (final workout in weekWorkouts) { + await _dao.insertWorkout( + ProgramWorkoutsCompanion.insert( + id: IdGenerator.generate(), + weekId: newWeekId, + programId: newId, + day: workout.day, + type: workout.type, + refId: Value(workout.refId), + name: Value(workout.name), + description: Value(workout.description), + completed: const Value(false), + ), + ); + } + } + } + + @override + Future addWeek(String programId, int position) async { + final String id = IdGenerator.generate(); + await _dao.insertWeek( + ProgramWeeksCompanion.insert( + id: id, + programId: programId, + position: position, + ), + ); + final weeks = await _dao.getWeeks(programId); + final week = weeks.firstWhere((w) => w.id == id); + return ProgramMapper.weekToEntity(week); + } + + @override + Future deleteWeek(String id) async { + await _dao.deleteWeek(id); + } + + @override + Future updateWeekNote(String weekId, String note) async { + await _dao.updateWeekNote(weekId, note); + } + + @override + Future addWorkout( + ProgramWorkoutEntity workout) async { + await _dao.insertWorkout(ProgramMapper.workoutToCompanion(workout)); + return workout; + } + + @override + Future updateWorkout(ProgramWorkoutEntity workout) async { + await _dao.updateWorkout( + workout.id, + ProgramMapper.workoutToUpdateCompanion(workout), + ); + } + + @override + Future deleteWorkout(String id) async { + await _dao.deleteWorkout(id); + } + + @override + Future toggleWorkoutComplete(String id, bool currentStatus) async { + await _dao.toggleWorkoutComplete(id, currentStatus); + } +} diff --git a/lib/data/repositories/training_plan_repository_impl.dart b/lib/data/repositories/training_plan_repository_impl.dart new file mode 100644 index 0000000..53ea837 --- /dev/null +++ b/lib/data/repositories/training_plan_repository_impl.dart @@ -0,0 +1,40 @@ +import 'package:trainhub_flutter/core/utils/id_generator.dart'; +import 'package:trainhub_flutter/data/database/daos/training_plan_dao.dart'; +import 'package:trainhub_flutter/data/mappers/training_plan_mapper.dart'; +import 'package:trainhub_flutter/domain/entities/training_plan.dart'; +import 'package:trainhub_flutter/domain/repositories/training_plan_repository.dart'; + +class TrainingPlanRepositoryImpl implements TrainingPlanRepository { + final TrainingPlanDao _dao; + + TrainingPlanRepositoryImpl(this._dao); + + @override + Future> getAll() async { + final rows = await _dao.getAllPlans(); + return rows.map(TrainingPlanMapper.toEntity).toList(); + } + + @override + Future getById(String id) async { + final row = await _dao.getPlanById(id); + return TrainingPlanMapper.toEntity(row); + } + + @override + Future create(String name) async { + final String id = IdGenerator.generate(); + await _dao.insertPlan(TrainingPlanMapper.toInsertCompanion(id, name)); + return getById(id); + } + + @override + Future update(TrainingPlanEntity plan) async { + await _dao.updatePlan(plan.id, TrainingPlanMapper.toUpdateCompanion(plan)); + } + + @override + Future delete(String id) async { + await _dao.deletePlan(id); + } +} diff --git a/lib/database/database.dart b/lib/database/database.dart new file mode 100644 index 0000000..abd6524 --- /dev/null +++ b/lib/database/database.dart @@ -0,0 +1,148 @@ +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:path/path.dart' as p; +import 'dart:io'; + +part 'database.g.dart'; + +// Exercises +class Exercises extends Table { + TextColumn get id => text()(); + TextColumn get name => text()(); + TextColumn get instructions => text().nullable()(); + TextColumn get enrichment => text().nullable()(); + TextColumn get tags => text().nullable()(); // JSON string + TextColumn get videoUrl => text().nullable()(); + + @override + Set get primaryKey => {id}; +} + +// TrainingPlans +class TrainingPlans extends Table { + TextColumn get id => text()(); + TextColumn get name => text()(); + TextColumn get sections => text().nullable()(); // JSON string + + @override + Set get primaryKey => {id}; +} + +// Programs +class Programs extends Table { + TextColumn get id => text()(); + TextColumn get name => text()(); + TextColumn get createdAt => text()(); + + @override + Set get primaryKey => {id}; +} + +// ProgramWeeks +class ProgramWeeks extends Table { + TextColumn get id => text()(); + TextColumn get programId => + text().references(Programs, #id, onDelete: KeyAction.cascade)(); + IntColumn get position => integer()(); + TextColumn get notes => text().nullable()(); + + @override + Set get primaryKey => {id}; +} + +// ProgramWorkouts +class ProgramWorkouts extends Table { + TextColumn get id => text()(); + TextColumn get weekId => + text().references(ProgramWeeks, #id, onDelete: KeyAction.cascade)(); + TextColumn get programId => + text().references(Programs, #id, onDelete: KeyAction.cascade)(); + TextColumn get day => text()(); + TextColumn get type => text()(); // "exercise" | "plan" + TextColumn get refId => text().nullable()(); + TextColumn get name => text().nullable()(); + TextColumn get description => text().nullable()(); + BoolColumn get completed => boolean().withDefault(const Constant(false))(); + + @override + Set get primaryKey => {id}; +} + +// AnalysisSessions +class AnalysisSessions extends Table { + TextColumn get id => text()(); + TextColumn get name => text()(); + TextColumn get date => text()(); + TextColumn get videoPath => text().nullable()(); + + @override + Set get primaryKey => {id}; +} + +// Annotations +class Annotations extends Table { + TextColumn get id => text()(); + TextColumn get sessionId => + text().references(AnalysisSessions, #id, onDelete: KeyAction.cascade)(); + RealColumn get startTime => real()(); + RealColumn get endTime => real()(); + TextColumn get name => text().nullable()(); + TextColumn get description => text().nullable()(); + TextColumn get color => text().nullable()(); + + @override + Set get primaryKey => {id}; +} + +// ChatSessions +class ChatSessions extends Table { + TextColumn get id => text()(); + TextColumn get title => text().nullable()(); + TextColumn get createdAt => text()(); + TextColumn get updatedAt => text()(); + + @override + Set get primaryKey => {id}; +} + +// ChatMessages +class ChatMessages extends Table { + TextColumn get id => text()(); + TextColumn get sessionId => + text().references(ChatSessions, #id, onDelete: KeyAction.cascade)(); + TextColumn get role => text()(); // 'user' | 'assistant' + TextColumn get content => text()(); + TextColumn get createdAt => text()(); + + @override + Set get primaryKey => {id}; +} + +@DriftDatabase( + tables: [ + Exercises, + TrainingPlans, + Programs, + ProgramWeeks, + ProgramWorkouts, + AnalysisSessions, + Annotations, + ChatSessions, + ChatMessages, + ], +) +class AppDatabase extends _$AppDatabase { + AppDatabase() : super(_openConnection()); + + @override + int get schemaVersion => 1; +} + +LazyDatabase _openConnection() { + return LazyDatabase(() async { + final dbFolder = await getApplicationDocumentsDirectory(); + final file = File(p.join(dbFolder.path, 'trainhub.sqlite')); + return NativeDatabase.createInBackground(file); + }); +} diff --git a/lib/database/database.g.dart b/lib/database/database.g.dart new file mode 100644 index 0000000..4c726d4 --- /dev/null +++ b/lib/database/database.g.dart @@ -0,0 +1,6242 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'database.dart'; + +// ignore_for_file: type=lint +class $ExercisesTable extends Exercises + with TableInfo<$ExercisesTable, Exercise> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $ExercisesTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _instructionsMeta = const VerificationMeta( + 'instructions', + ); + @override + late final GeneratedColumn instructions = GeneratedColumn( + 'instructions', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _enrichmentMeta = const VerificationMeta( + 'enrichment', + ); + @override + late final GeneratedColumn enrichment = GeneratedColumn( + 'enrichment', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _tagsMeta = const VerificationMeta('tags'); + @override + late final GeneratedColumn tags = GeneratedColumn( + 'tags', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _videoUrlMeta = const VerificationMeta( + 'videoUrl', + ); + @override + late final GeneratedColumn videoUrl = GeneratedColumn( + 'video_url', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + name, + instructions, + enrichment, + tags, + videoUrl, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'exercises'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, + name.isAcceptableOrUnknown(data['name']!, _nameMeta), + ); + } else if (isInserting) { + context.missing(_nameMeta); + } + if (data.containsKey('instructions')) { + context.handle( + _instructionsMeta, + instructions.isAcceptableOrUnknown( + data['instructions']!, + _instructionsMeta, + ), + ); + } + if (data.containsKey('enrichment')) { + context.handle( + _enrichmentMeta, + enrichment.isAcceptableOrUnknown(data['enrichment']!, _enrichmentMeta), + ); + } + if (data.containsKey('tags')) { + context.handle( + _tagsMeta, + tags.isAcceptableOrUnknown(data['tags']!, _tagsMeta), + ); + } + if (data.containsKey('video_url')) { + context.handle( + _videoUrlMeta, + videoUrl.isAcceptableOrUnknown(data['video_url']!, _videoUrlMeta), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + Exercise map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return Exercise( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + instructions: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}instructions'], + ), + enrichment: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}enrichment'], + ), + tags: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}tags'], + ), + videoUrl: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}video_url'], + ), + ); + } + + @override + $ExercisesTable createAlias(String alias) { + return $ExercisesTable(attachedDatabase, alias); + } +} + +class Exercise extends DataClass implements Insertable { + final String id; + final String name; + final String? instructions; + final String? enrichment; + final String? tags; + final String? videoUrl; + const Exercise({ + required this.id, + required this.name, + this.instructions, + this.enrichment, + this.tags, + this.videoUrl, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + if (!nullToAbsent || instructions != null) { + map['instructions'] = Variable(instructions); + } + if (!nullToAbsent || enrichment != null) { + map['enrichment'] = Variable(enrichment); + } + if (!nullToAbsent || tags != null) { + map['tags'] = Variable(tags); + } + if (!nullToAbsent || videoUrl != null) { + map['video_url'] = Variable(videoUrl); + } + return map; + } + + ExercisesCompanion toCompanion(bool nullToAbsent) { + return ExercisesCompanion( + id: Value(id), + name: Value(name), + instructions: instructions == null && nullToAbsent + ? const Value.absent() + : Value(instructions), + enrichment: enrichment == null && nullToAbsent + ? const Value.absent() + : Value(enrichment), + tags: tags == null && nullToAbsent ? const Value.absent() : Value(tags), + videoUrl: videoUrl == null && nullToAbsent + ? const Value.absent() + : Value(videoUrl), + ); + } + + factory Exercise.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return Exercise( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + instructions: serializer.fromJson(json['instructions']), + enrichment: serializer.fromJson(json['enrichment']), + tags: serializer.fromJson(json['tags']), + videoUrl: serializer.fromJson(json['videoUrl']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'instructions': serializer.toJson(instructions), + 'enrichment': serializer.toJson(enrichment), + 'tags': serializer.toJson(tags), + 'videoUrl': serializer.toJson(videoUrl), + }; + } + + Exercise copyWith({ + String? id, + String? name, + Value instructions = const Value.absent(), + Value enrichment = const Value.absent(), + Value tags = const Value.absent(), + Value videoUrl = const Value.absent(), + }) => Exercise( + id: id ?? this.id, + name: name ?? this.name, + instructions: instructions.present ? instructions.value : this.instructions, + enrichment: enrichment.present ? enrichment.value : this.enrichment, + tags: tags.present ? tags.value : this.tags, + videoUrl: videoUrl.present ? videoUrl.value : this.videoUrl, + ); + Exercise copyWithCompanion(ExercisesCompanion data) { + return Exercise( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + instructions: data.instructions.present + ? data.instructions.value + : this.instructions, + enrichment: data.enrichment.present + ? data.enrichment.value + : this.enrichment, + tags: data.tags.present ? data.tags.value : this.tags, + videoUrl: data.videoUrl.present ? data.videoUrl.value : this.videoUrl, + ); + } + + @override + String toString() { + return (StringBuffer('Exercise(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('instructions: $instructions, ') + ..write('enrichment: $enrichment, ') + ..write('tags: $tags, ') + ..write('videoUrl: $videoUrl') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, name, instructions, enrichment, tags, videoUrl); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is Exercise && + other.id == this.id && + other.name == this.name && + other.instructions == this.instructions && + other.enrichment == this.enrichment && + other.tags == this.tags && + other.videoUrl == this.videoUrl); +} + +class ExercisesCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value instructions; + final Value enrichment; + final Value tags; + final Value videoUrl; + final Value rowid; + const ExercisesCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.instructions = const Value.absent(), + this.enrichment = const Value.absent(), + this.tags = const Value.absent(), + this.videoUrl = const Value.absent(), + this.rowid = const Value.absent(), + }); + ExercisesCompanion.insert({ + required String id, + required String name, + this.instructions = const Value.absent(), + this.enrichment = const Value.absent(), + this.tags = const Value.absent(), + this.videoUrl = const Value.absent(), + this.rowid = const Value.absent(), + }) : id = Value(id), + name = Value(name); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? instructions, + Expression? enrichment, + Expression? tags, + Expression? videoUrl, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (instructions != null) 'instructions': instructions, + if (enrichment != null) 'enrichment': enrichment, + if (tags != null) 'tags': tags, + if (videoUrl != null) 'video_url': videoUrl, + if (rowid != null) 'rowid': rowid, + }); + } + + ExercisesCompanion copyWith({ + Value? id, + Value? name, + Value? instructions, + Value? enrichment, + Value? tags, + Value? videoUrl, + Value? rowid, + }) { + return ExercisesCompanion( + id: id ?? this.id, + name: name ?? this.name, + instructions: instructions ?? this.instructions, + enrichment: enrichment ?? this.enrichment, + tags: tags ?? this.tags, + videoUrl: videoUrl ?? this.videoUrl, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (instructions.present) { + map['instructions'] = Variable(instructions.value); + } + if (enrichment.present) { + map['enrichment'] = Variable(enrichment.value); + } + if (tags.present) { + map['tags'] = Variable(tags.value); + } + if (videoUrl.present) { + map['video_url'] = Variable(videoUrl.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ExercisesCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('instructions: $instructions, ') + ..write('enrichment: $enrichment, ') + ..write('tags: $tags, ') + ..write('videoUrl: $videoUrl, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $TrainingPlansTable extends TrainingPlans + with TableInfo<$TrainingPlansTable, TrainingPlan> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $TrainingPlansTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _sectionsMeta = const VerificationMeta( + 'sections', + ); + @override + late final GeneratedColumn sections = GeneratedColumn( + 'sections', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [id, name, sections]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'training_plans'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, + name.isAcceptableOrUnknown(data['name']!, _nameMeta), + ); + } else if (isInserting) { + context.missing(_nameMeta); + } + if (data.containsKey('sections')) { + context.handle( + _sectionsMeta, + sections.isAcceptableOrUnknown(data['sections']!, _sectionsMeta), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + TrainingPlan map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return TrainingPlan( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + sections: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}sections'], + ), + ); + } + + @override + $TrainingPlansTable createAlias(String alias) { + return $TrainingPlansTable(attachedDatabase, alias); + } +} + +class TrainingPlan extends DataClass implements Insertable { + final String id; + final String name; + final String? sections; + const TrainingPlan({required this.id, required this.name, this.sections}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + if (!nullToAbsent || sections != null) { + map['sections'] = Variable(sections); + } + return map; + } + + TrainingPlansCompanion toCompanion(bool nullToAbsent) { + return TrainingPlansCompanion( + id: Value(id), + name: Value(name), + sections: sections == null && nullToAbsent + ? const Value.absent() + : Value(sections), + ); + } + + factory TrainingPlan.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return TrainingPlan( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + sections: serializer.fromJson(json['sections']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'sections': serializer.toJson(sections), + }; + } + + TrainingPlan copyWith({ + String? id, + String? name, + Value sections = const Value.absent(), + }) => TrainingPlan( + id: id ?? this.id, + name: name ?? this.name, + sections: sections.present ? sections.value : this.sections, + ); + TrainingPlan copyWithCompanion(TrainingPlansCompanion data) { + return TrainingPlan( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + sections: data.sections.present ? data.sections.value : this.sections, + ); + } + + @override + String toString() { + return (StringBuffer('TrainingPlan(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('sections: $sections') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, name, sections); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TrainingPlan && + other.id == this.id && + other.name == this.name && + other.sections == this.sections); +} + +class TrainingPlansCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value sections; + final Value rowid; + const TrainingPlansCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.sections = const Value.absent(), + this.rowid = const Value.absent(), + }); + TrainingPlansCompanion.insert({ + required String id, + required String name, + this.sections = const Value.absent(), + this.rowid = const Value.absent(), + }) : id = Value(id), + name = Value(name); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? sections, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (sections != null) 'sections': sections, + if (rowid != null) 'rowid': rowid, + }); + } + + TrainingPlansCompanion copyWith({ + Value? id, + Value? name, + Value? sections, + Value? rowid, + }) { + return TrainingPlansCompanion( + id: id ?? this.id, + name: name ?? this.name, + sections: sections ?? this.sections, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (sections.present) { + map['sections'] = Variable(sections.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TrainingPlansCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('sections: $sections, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $ProgramsTable extends Programs with TableInfo<$ProgramsTable, Program> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $ProgramsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _createdAtMeta = const VerificationMeta( + 'createdAt', + ); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [id, name, createdAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'programs'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, + name.isAcceptableOrUnknown(data['name']!, _nameMeta), + ); + } else if (isInserting) { + context.missing(_nameMeta); + } + if (data.containsKey('created_at')) { + context.handle( + _createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta), + ); + } else if (isInserting) { + context.missing(_createdAtMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + Program map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return Program( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + ); + } + + @override + $ProgramsTable createAlias(String alias) { + return $ProgramsTable(attachedDatabase, alias); + } +} + +class Program extends DataClass implements Insertable { + final String id; + final String name; + final String createdAt; + const Program({ + required this.id, + required this.name, + required this.createdAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['created_at'] = Variable(createdAt); + return map; + } + + ProgramsCompanion toCompanion(bool nullToAbsent) { + return ProgramsCompanion( + id: Value(id), + name: Value(name), + createdAt: Value(createdAt), + ); + } + + factory Program.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return Program( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + createdAt: serializer.fromJson(json['createdAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'createdAt': serializer.toJson(createdAt), + }; + } + + Program copyWith({String? id, String? name, String? createdAt}) => Program( + id: id ?? this.id, + name: name ?? this.name, + createdAt: createdAt ?? this.createdAt, + ); + Program copyWithCompanion(ProgramsCompanion data) { + return Program( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('Program(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, name, createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is Program && + other.id == this.id && + other.name == this.name && + other.createdAt == this.createdAt); +} + +class ProgramsCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value createdAt; + final Value rowid; + const ProgramsCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.createdAt = const Value.absent(), + this.rowid = const Value.absent(), + }); + ProgramsCompanion.insert({ + required String id, + required String name, + required String createdAt, + this.rowid = const Value.absent(), + }) : id = Value(id), + name = Value(name), + createdAt = Value(createdAt); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? createdAt, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (createdAt != null) 'created_at': createdAt, + if (rowid != null) 'rowid': rowid, + }); + } + + ProgramsCompanion copyWith({ + Value? id, + Value? name, + Value? createdAt, + Value? rowid, + }) { + return ProgramsCompanion( + id: id ?? this.id, + name: name ?? this.name, + createdAt: createdAt ?? this.createdAt, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ProgramsCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('createdAt: $createdAt, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $ProgramWeeksTable extends ProgramWeeks + with TableInfo<$ProgramWeeksTable, ProgramWeek> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $ProgramWeeksTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _programIdMeta = const VerificationMeta( + 'programId', + ); + @override + late final GeneratedColumn programId = GeneratedColumn( + 'program_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES programs (id) ON DELETE CASCADE', + ), + ); + static const VerificationMeta _positionMeta = const VerificationMeta( + 'position', + ); + @override + late final GeneratedColumn position = GeneratedColumn( + 'position', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + static const VerificationMeta _notesMeta = const VerificationMeta('notes'); + @override + late final GeneratedColumn notes = GeneratedColumn( + 'notes', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [id, programId, position, notes]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'program_weeks'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('program_id')) { + context.handle( + _programIdMeta, + programId.isAcceptableOrUnknown(data['program_id']!, _programIdMeta), + ); + } else if (isInserting) { + context.missing(_programIdMeta); + } + if (data.containsKey('position')) { + context.handle( + _positionMeta, + position.isAcceptableOrUnknown(data['position']!, _positionMeta), + ); + } else if (isInserting) { + context.missing(_positionMeta); + } + if (data.containsKey('notes')) { + context.handle( + _notesMeta, + notes.isAcceptableOrUnknown(data['notes']!, _notesMeta), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + ProgramWeek map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return ProgramWeek( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + programId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}program_id'], + )!, + position: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}position'], + )!, + notes: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}notes'], + ), + ); + } + + @override + $ProgramWeeksTable createAlias(String alias) { + return $ProgramWeeksTable(attachedDatabase, alias); + } +} + +class ProgramWeek extends DataClass implements Insertable { + final String id; + final String programId; + final int position; + final String? notes; + const ProgramWeek({ + required this.id, + required this.programId, + required this.position, + this.notes, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['program_id'] = Variable(programId); + map['position'] = Variable(position); + if (!nullToAbsent || notes != null) { + map['notes'] = Variable(notes); + } + return map; + } + + ProgramWeeksCompanion toCompanion(bool nullToAbsent) { + return ProgramWeeksCompanion( + id: Value(id), + programId: Value(programId), + position: Value(position), + notes: notes == null && nullToAbsent + ? const Value.absent() + : Value(notes), + ); + } + + factory ProgramWeek.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return ProgramWeek( + id: serializer.fromJson(json['id']), + programId: serializer.fromJson(json['programId']), + position: serializer.fromJson(json['position']), + notes: serializer.fromJson(json['notes']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'programId': serializer.toJson(programId), + 'position': serializer.toJson(position), + 'notes': serializer.toJson(notes), + }; + } + + ProgramWeek copyWith({ + String? id, + String? programId, + int? position, + Value notes = const Value.absent(), + }) => ProgramWeek( + id: id ?? this.id, + programId: programId ?? this.programId, + position: position ?? this.position, + notes: notes.present ? notes.value : this.notes, + ); + ProgramWeek copyWithCompanion(ProgramWeeksCompanion data) { + return ProgramWeek( + id: data.id.present ? data.id.value : this.id, + programId: data.programId.present ? data.programId.value : this.programId, + position: data.position.present ? data.position.value : this.position, + notes: data.notes.present ? data.notes.value : this.notes, + ); + } + + @override + String toString() { + return (StringBuffer('ProgramWeek(') + ..write('id: $id, ') + ..write('programId: $programId, ') + ..write('position: $position, ') + ..write('notes: $notes') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, programId, position, notes); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is ProgramWeek && + other.id == this.id && + other.programId == this.programId && + other.position == this.position && + other.notes == this.notes); +} + +class ProgramWeeksCompanion extends UpdateCompanion { + final Value id; + final Value programId; + final Value position; + final Value notes; + final Value rowid; + const ProgramWeeksCompanion({ + this.id = const Value.absent(), + this.programId = const Value.absent(), + this.position = const Value.absent(), + this.notes = const Value.absent(), + this.rowid = const Value.absent(), + }); + ProgramWeeksCompanion.insert({ + required String id, + required String programId, + required int position, + this.notes = const Value.absent(), + this.rowid = const Value.absent(), + }) : id = Value(id), + programId = Value(programId), + position = Value(position); + static Insertable custom({ + Expression? id, + Expression? programId, + Expression? position, + Expression? notes, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (programId != null) 'program_id': programId, + if (position != null) 'position': position, + if (notes != null) 'notes': notes, + if (rowid != null) 'rowid': rowid, + }); + } + + ProgramWeeksCompanion copyWith({ + Value? id, + Value? programId, + Value? position, + Value? notes, + Value? rowid, + }) { + return ProgramWeeksCompanion( + id: id ?? this.id, + programId: programId ?? this.programId, + position: position ?? this.position, + notes: notes ?? this.notes, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (programId.present) { + map['program_id'] = Variable(programId.value); + } + if (position.present) { + map['position'] = Variable(position.value); + } + if (notes.present) { + map['notes'] = Variable(notes.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ProgramWeeksCompanion(') + ..write('id: $id, ') + ..write('programId: $programId, ') + ..write('position: $position, ') + ..write('notes: $notes, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $ProgramWorkoutsTable extends ProgramWorkouts + with TableInfo<$ProgramWorkoutsTable, ProgramWorkout> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $ProgramWorkoutsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _weekIdMeta = const VerificationMeta('weekId'); + @override + late final GeneratedColumn weekId = GeneratedColumn( + 'week_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES program_weeks (id) ON DELETE CASCADE', + ), + ); + static const VerificationMeta _programIdMeta = const VerificationMeta( + 'programId', + ); + @override + late final GeneratedColumn programId = GeneratedColumn( + 'program_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES programs (id) ON DELETE CASCADE', + ), + ); + static const VerificationMeta _dayMeta = const VerificationMeta('day'); + @override + late final GeneratedColumn day = GeneratedColumn( + 'day', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _typeMeta = const VerificationMeta('type'); + @override + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _refIdMeta = const VerificationMeta('refId'); + @override + late final GeneratedColumn refId = GeneratedColumn( + 'ref_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _descriptionMeta = const VerificationMeta( + 'description', + ); + @override + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _completedMeta = const VerificationMeta( + 'completed', + ); + @override + late final GeneratedColumn completed = GeneratedColumn( + 'completed', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("completed" IN (0, 1))', + ), + defaultValue: const Constant(false), + ); + @override + List get $columns => [ + id, + weekId, + programId, + day, + type, + refId, + name, + description, + completed, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'program_workouts'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('week_id')) { + context.handle( + _weekIdMeta, + weekId.isAcceptableOrUnknown(data['week_id']!, _weekIdMeta), + ); + } else if (isInserting) { + context.missing(_weekIdMeta); + } + if (data.containsKey('program_id')) { + context.handle( + _programIdMeta, + programId.isAcceptableOrUnknown(data['program_id']!, _programIdMeta), + ); + } else if (isInserting) { + context.missing(_programIdMeta); + } + if (data.containsKey('day')) { + context.handle( + _dayMeta, + day.isAcceptableOrUnknown(data['day']!, _dayMeta), + ); + } else if (isInserting) { + context.missing(_dayMeta); + } + if (data.containsKey('type')) { + context.handle( + _typeMeta, + type.isAcceptableOrUnknown(data['type']!, _typeMeta), + ); + } else if (isInserting) { + context.missing(_typeMeta); + } + if (data.containsKey('ref_id')) { + context.handle( + _refIdMeta, + refId.isAcceptableOrUnknown(data['ref_id']!, _refIdMeta), + ); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, + name.isAcceptableOrUnknown(data['name']!, _nameMeta), + ); + } + if (data.containsKey('description')) { + context.handle( + _descriptionMeta, + description.isAcceptableOrUnknown( + data['description']!, + _descriptionMeta, + ), + ); + } + if (data.containsKey('completed')) { + context.handle( + _completedMeta, + completed.isAcceptableOrUnknown(data['completed']!, _completedMeta), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + ProgramWorkout map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return ProgramWorkout( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + weekId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}week_id'], + )!, + programId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}program_id'], + )!, + day: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}day'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}type'], + )!, + refId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}ref_id'], + ), + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + ), + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + completed: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}completed'], + )!, + ); + } + + @override + $ProgramWorkoutsTable createAlias(String alias) { + return $ProgramWorkoutsTable(attachedDatabase, alias); + } +} + +class ProgramWorkout extends DataClass implements Insertable { + final String id; + final String weekId; + final String programId; + final String day; + final String type; + final String? refId; + final String? name; + final String? description; + final bool completed; + const ProgramWorkout({ + required this.id, + required this.weekId, + required this.programId, + required this.day, + required this.type, + this.refId, + this.name, + this.description, + required this.completed, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['week_id'] = Variable(weekId); + map['program_id'] = Variable(programId); + map['day'] = Variable(day); + map['type'] = Variable(type); + if (!nullToAbsent || refId != null) { + map['ref_id'] = Variable(refId); + } + if (!nullToAbsent || name != null) { + map['name'] = Variable(name); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + map['completed'] = Variable(completed); + return map; + } + + ProgramWorkoutsCompanion toCompanion(bool nullToAbsent) { + return ProgramWorkoutsCompanion( + id: Value(id), + weekId: Value(weekId), + programId: Value(programId), + day: Value(day), + type: Value(type), + refId: refId == null && nullToAbsent + ? const Value.absent() + : Value(refId), + name: name == null && nullToAbsent ? const Value.absent() : Value(name), + description: description == null && nullToAbsent + ? const Value.absent() + : Value(description), + completed: Value(completed), + ); + } + + factory ProgramWorkout.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return ProgramWorkout( + id: serializer.fromJson(json['id']), + weekId: serializer.fromJson(json['weekId']), + programId: serializer.fromJson(json['programId']), + day: serializer.fromJson(json['day']), + type: serializer.fromJson(json['type']), + refId: serializer.fromJson(json['refId']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + completed: serializer.fromJson(json['completed']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'weekId': serializer.toJson(weekId), + 'programId': serializer.toJson(programId), + 'day': serializer.toJson(day), + 'type': serializer.toJson(type), + 'refId': serializer.toJson(refId), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'completed': serializer.toJson(completed), + }; + } + + ProgramWorkout copyWith({ + String? id, + String? weekId, + String? programId, + String? day, + String? type, + Value refId = const Value.absent(), + Value name = const Value.absent(), + Value description = const Value.absent(), + bool? completed, + }) => ProgramWorkout( + id: id ?? this.id, + weekId: weekId ?? this.weekId, + programId: programId ?? this.programId, + day: day ?? this.day, + type: type ?? this.type, + refId: refId.present ? refId.value : this.refId, + name: name.present ? name.value : this.name, + description: description.present ? description.value : this.description, + completed: completed ?? this.completed, + ); + ProgramWorkout copyWithCompanion(ProgramWorkoutsCompanion data) { + return ProgramWorkout( + id: data.id.present ? data.id.value : this.id, + weekId: data.weekId.present ? data.weekId.value : this.weekId, + programId: data.programId.present ? data.programId.value : this.programId, + day: data.day.present ? data.day.value : this.day, + type: data.type.present ? data.type.value : this.type, + refId: data.refId.present ? data.refId.value : this.refId, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + completed: data.completed.present ? data.completed.value : this.completed, + ); + } + + @override + String toString() { + return (StringBuffer('ProgramWorkout(') + ..write('id: $id, ') + ..write('weekId: $weekId, ') + ..write('programId: $programId, ') + ..write('day: $day, ') + ..write('type: $type, ') + ..write('refId: $refId, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('completed: $completed') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + weekId, + programId, + day, + type, + refId, + name, + description, + completed, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is ProgramWorkout && + other.id == this.id && + other.weekId == this.weekId && + other.programId == this.programId && + other.day == this.day && + other.type == this.type && + other.refId == this.refId && + other.name == this.name && + other.description == this.description && + other.completed == this.completed); +} + +class ProgramWorkoutsCompanion extends UpdateCompanion { + final Value id; + final Value weekId; + final Value programId; + final Value day; + final Value type; + final Value refId; + final Value name; + final Value description; + final Value completed; + final Value rowid; + const ProgramWorkoutsCompanion({ + this.id = const Value.absent(), + this.weekId = const Value.absent(), + this.programId = const Value.absent(), + this.day = const Value.absent(), + this.type = const Value.absent(), + this.refId = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.completed = const Value.absent(), + this.rowid = const Value.absent(), + }); + ProgramWorkoutsCompanion.insert({ + required String id, + required String weekId, + required String programId, + required String day, + required String type, + this.refId = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.completed = const Value.absent(), + this.rowid = const Value.absent(), + }) : id = Value(id), + weekId = Value(weekId), + programId = Value(programId), + day = Value(day), + type = Value(type); + static Insertable custom({ + Expression? id, + Expression? weekId, + Expression? programId, + Expression? day, + Expression? type, + Expression? refId, + Expression? name, + Expression? description, + Expression? completed, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (weekId != null) 'week_id': weekId, + if (programId != null) 'program_id': programId, + if (day != null) 'day': day, + if (type != null) 'type': type, + if (refId != null) 'ref_id': refId, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (completed != null) 'completed': completed, + if (rowid != null) 'rowid': rowid, + }); + } + + ProgramWorkoutsCompanion copyWith({ + Value? id, + Value? weekId, + Value? programId, + Value? day, + Value? type, + Value? refId, + Value? name, + Value? description, + Value? completed, + Value? rowid, + }) { + return ProgramWorkoutsCompanion( + id: id ?? this.id, + weekId: weekId ?? this.weekId, + programId: programId ?? this.programId, + day: day ?? this.day, + type: type ?? this.type, + refId: refId ?? this.refId, + name: name ?? this.name, + description: description ?? this.description, + completed: completed ?? this.completed, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (weekId.present) { + map['week_id'] = Variable(weekId.value); + } + if (programId.present) { + map['program_id'] = Variable(programId.value); + } + if (day.present) { + map['day'] = Variable(day.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (refId.present) { + map['ref_id'] = Variable(refId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (completed.present) { + map['completed'] = Variable(completed.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ProgramWorkoutsCompanion(') + ..write('id: $id, ') + ..write('weekId: $weekId, ') + ..write('programId: $programId, ') + ..write('day: $day, ') + ..write('type: $type, ') + ..write('refId: $refId, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('completed: $completed, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $AnalysisSessionsTable extends AnalysisSessions + with TableInfo<$AnalysisSessionsTable, AnalysisSession> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $AnalysisSessionsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _dateMeta = const VerificationMeta('date'); + @override + late final GeneratedColumn date = GeneratedColumn( + 'date', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _videoPathMeta = const VerificationMeta( + 'videoPath', + ); + @override + late final GeneratedColumn videoPath = GeneratedColumn( + 'video_path', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [id, name, date, videoPath]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'analysis_sessions'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, + name.isAcceptableOrUnknown(data['name']!, _nameMeta), + ); + } else if (isInserting) { + context.missing(_nameMeta); + } + if (data.containsKey('date')) { + context.handle( + _dateMeta, + date.isAcceptableOrUnknown(data['date']!, _dateMeta), + ); + } else if (isInserting) { + context.missing(_dateMeta); + } + if (data.containsKey('video_path')) { + context.handle( + _videoPathMeta, + videoPath.isAcceptableOrUnknown(data['video_path']!, _videoPathMeta), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + AnalysisSession map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AnalysisSession( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + date: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}date'], + )!, + videoPath: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}video_path'], + ), + ); + } + + @override + $AnalysisSessionsTable createAlias(String alias) { + return $AnalysisSessionsTable(attachedDatabase, alias); + } +} + +class AnalysisSession extends DataClass implements Insertable { + final String id; + final String name; + final String date; + final String? videoPath; + const AnalysisSession({ + required this.id, + required this.name, + required this.date, + this.videoPath, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['date'] = Variable(date); + if (!nullToAbsent || videoPath != null) { + map['video_path'] = Variable(videoPath); + } + return map; + } + + AnalysisSessionsCompanion toCompanion(bool nullToAbsent) { + return AnalysisSessionsCompanion( + id: Value(id), + name: Value(name), + date: Value(date), + videoPath: videoPath == null && nullToAbsent + ? const Value.absent() + : Value(videoPath), + ); + } + + factory AnalysisSession.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AnalysisSession( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + date: serializer.fromJson(json['date']), + videoPath: serializer.fromJson(json['videoPath']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'date': serializer.toJson(date), + 'videoPath': serializer.toJson(videoPath), + }; + } + + AnalysisSession copyWith({ + String? id, + String? name, + String? date, + Value videoPath = const Value.absent(), + }) => AnalysisSession( + id: id ?? this.id, + name: name ?? this.name, + date: date ?? this.date, + videoPath: videoPath.present ? videoPath.value : this.videoPath, + ); + AnalysisSession copyWithCompanion(AnalysisSessionsCompanion data) { + return AnalysisSession( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + date: data.date.present ? data.date.value : this.date, + videoPath: data.videoPath.present ? data.videoPath.value : this.videoPath, + ); + } + + @override + String toString() { + return (StringBuffer('AnalysisSession(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('date: $date, ') + ..write('videoPath: $videoPath') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, name, date, videoPath); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AnalysisSession && + other.id == this.id && + other.name == this.name && + other.date == this.date && + other.videoPath == this.videoPath); +} + +class AnalysisSessionsCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value date; + final Value videoPath; + final Value rowid; + const AnalysisSessionsCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.date = const Value.absent(), + this.videoPath = const Value.absent(), + this.rowid = const Value.absent(), + }); + AnalysisSessionsCompanion.insert({ + required String id, + required String name, + required String date, + this.videoPath = const Value.absent(), + this.rowid = const Value.absent(), + }) : id = Value(id), + name = Value(name), + date = Value(date); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? date, + Expression? videoPath, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (date != null) 'date': date, + if (videoPath != null) 'video_path': videoPath, + if (rowid != null) 'rowid': rowid, + }); + } + + AnalysisSessionsCompanion copyWith({ + Value? id, + Value? name, + Value? date, + Value? videoPath, + Value? rowid, + }) { + return AnalysisSessionsCompanion( + id: id ?? this.id, + name: name ?? this.name, + date: date ?? this.date, + videoPath: videoPath ?? this.videoPath, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (date.present) { + map['date'] = Variable(date.value); + } + if (videoPath.present) { + map['video_path'] = Variable(videoPath.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AnalysisSessionsCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('date: $date, ') + ..write('videoPath: $videoPath, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $AnnotationsTable extends Annotations + with TableInfo<$AnnotationsTable, Annotation> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $AnnotationsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _sessionIdMeta = const VerificationMeta( + 'sessionId', + ); + @override + late final GeneratedColumn sessionId = GeneratedColumn( + 'session_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES analysis_sessions (id) ON DELETE CASCADE', + ), + ); + static const VerificationMeta _startTimeMeta = const VerificationMeta( + 'startTime', + ); + @override + late final GeneratedColumn startTime = GeneratedColumn( + 'start_time', + aliasedName, + false, + type: DriftSqlType.double, + requiredDuringInsert: true, + ); + static const VerificationMeta _endTimeMeta = const VerificationMeta( + 'endTime', + ); + @override + late final GeneratedColumn endTime = GeneratedColumn( + 'end_time', + aliasedName, + false, + type: DriftSqlType.double, + requiredDuringInsert: true, + ); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _descriptionMeta = const VerificationMeta( + 'description', + ); + @override + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _colorMeta = const VerificationMeta('color'); + @override + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + sessionId, + startTime, + endTime, + name, + description, + color, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'annotations'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('session_id')) { + context.handle( + _sessionIdMeta, + sessionId.isAcceptableOrUnknown(data['session_id']!, _sessionIdMeta), + ); + } else if (isInserting) { + context.missing(_sessionIdMeta); + } + if (data.containsKey('start_time')) { + context.handle( + _startTimeMeta, + startTime.isAcceptableOrUnknown(data['start_time']!, _startTimeMeta), + ); + } else if (isInserting) { + context.missing(_startTimeMeta); + } + if (data.containsKey('end_time')) { + context.handle( + _endTimeMeta, + endTime.isAcceptableOrUnknown(data['end_time']!, _endTimeMeta), + ); + } else if (isInserting) { + context.missing(_endTimeMeta); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, + name.isAcceptableOrUnknown(data['name']!, _nameMeta), + ); + } + if (data.containsKey('description')) { + context.handle( + _descriptionMeta, + description.isAcceptableOrUnknown( + data['description']!, + _descriptionMeta, + ), + ); + } + if (data.containsKey('color')) { + context.handle( + _colorMeta, + color.isAcceptableOrUnknown(data['color']!, _colorMeta), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + Annotation map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return Annotation( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + sessionId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}session_id'], + )!, + startTime: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}start_time'], + )!, + endTime: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}end_time'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + ), + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + ); + } + + @override + $AnnotationsTable createAlias(String alias) { + return $AnnotationsTable(attachedDatabase, alias); + } +} + +class Annotation extends DataClass implements Insertable { + final String id; + final String sessionId; + final double startTime; + final double endTime; + final String? name; + final String? description; + final String? color; + const Annotation({ + required this.id, + required this.sessionId, + required this.startTime, + required this.endTime, + this.name, + this.description, + this.color, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['session_id'] = Variable(sessionId); + map['start_time'] = Variable(startTime); + map['end_time'] = Variable(endTime); + if (!nullToAbsent || name != null) { + map['name'] = Variable(name); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + return map; + } + + AnnotationsCompanion toCompanion(bool nullToAbsent) { + return AnnotationsCompanion( + id: Value(id), + sessionId: Value(sessionId), + startTime: Value(startTime), + endTime: Value(endTime), + name: name == null && nullToAbsent ? const Value.absent() : Value(name), + description: description == null && nullToAbsent + ? const Value.absent() + : Value(description), + color: color == null && nullToAbsent + ? const Value.absent() + : Value(color), + ); + } + + factory Annotation.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return Annotation( + id: serializer.fromJson(json['id']), + sessionId: serializer.fromJson(json['sessionId']), + startTime: serializer.fromJson(json['startTime']), + endTime: serializer.fromJson(json['endTime']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + color: serializer.fromJson(json['color']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'sessionId': serializer.toJson(sessionId), + 'startTime': serializer.toJson(startTime), + 'endTime': serializer.toJson(endTime), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'color': serializer.toJson(color), + }; + } + + Annotation copyWith({ + String? id, + String? sessionId, + double? startTime, + double? endTime, + Value name = const Value.absent(), + Value description = const Value.absent(), + Value color = const Value.absent(), + }) => Annotation( + id: id ?? this.id, + sessionId: sessionId ?? this.sessionId, + startTime: startTime ?? this.startTime, + endTime: endTime ?? this.endTime, + name: name.present ? name.value : this.name, + description: description.present ? description.value : this.description, + color: color.present ? color.value : this.color, + ); + Annotation copyWithCompanion(AnnotationsCompanion data) { + return Annotation( + id: data.id.present ? data.id.value : this.id, + sessionId: data.sessionId.present ? data.sessionId.value : this.sessionId, + startTime: data.startTime.present ? data.startTime.value : this.startTime, + endTime: data.endTime.present ? data.endTime.value : this.endTime, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + color: data.color.present ? data.color.value : this.color, + ); + } + + @override + String toString() { + return (StringBuffer('Annotation(') + ..write('id: $id, ') + ..write('sessionId: $sessionId, ') + ..write('startTime: $startTime, ') + ..write('endTime: $endTime, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('color: $color') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, sessionId, startTime, endTime, name, description, color); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is Annotation && + other.id == this.id && + other.sessionId == this.sessionId && + other.startTime == this.startTime && + other.endTime == this.endTime && + other.name == this.name && + other.description == this.description && + other.color == this.color); +} + +class AnnotationsCompanion extends UpdateCompanion { + final Value id; + final Value sessionId; + final Value startTime; + final Value endTime; + final Value name; + final Value description; + final Value color; + final Value rowid; + const AnnotationsCompanion({ + this.id = const Value.absent(), + this.sessionId = const Value.absent(), + this.startTime = const Value.absent(), + this.endTime = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.color = const Value.absent(), + this.rowid = const Value.absent(), + }); + AnnotationsCompanion.insert({ + required String id, + required String sessionId, + required double startTime, + required double endTime, + this.name = const Value.absent(), + this.description = const Value.absent(), + this.color = const Value.absent(), + this.rowid = const Value.absent(), + }) : id = Value(id), + sessionId = Value(sessionId), + startTime = Value(startTime), + endTime = Value(endTime); + static Insertable custom({ + Expression? id, + Expression? sessionId, + Expression? startTime, + Expression? endTime, + Expression? name, + Expression? description, + Expression? color, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (sessionId != null) 'session_id': sessionId, + if (startTime != null) 'start_time': startTime, + if (endTime != null) 'end_time': endTime, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (color != null) 'color': color, + if (rowid != null) 'rowid': rowid, + }); + } + + AnnotationsCompanion copyWith({ + Value? id, + Value? sessionId, + Value? startTime, + Value? endTime, + Value? name, + Value? description, + Value? color, + Value? rowid, + }) { + return AnnotationsCompanion( + id: id ?? this.id, + sessionId: sessionId ?? this.sessionId, + startTime: startTime ?? this.startTime, + endTime: endTime ?? this.endTime, + name: name ?? this.name, + description: description ?? this.description, + color: color ?? this.color, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (sessionId.present) { + map['session_id'] = Variable(sessionId.value); + } + if (startTime.present) { + map['start_time'] = Variable(startTime.value); + } + if (endTime.present) { + map['end_time'] = Variable(endTime.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AnnotationsCompanion(') + ..write('id: $id, ') + ..write('sessionId: $sessionId, ') + ..write('startTime: $startTime, ') + ..write('endTime: $endTime, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('color: $color, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $ChatSessionsTable extends ChatSessions + with TableInfo<$ChatSessionsTable, ChatSession> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $ChatSessionsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _titleMeta = const VerificationMeta('title'); + @override + late final GeneratedColumn title = GeneratedColumn( + 'title', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _createdAtMeta = const VerificationMeta( + 'createdAt', + ); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _updatedAtMeta = const VerificationMeta( + 'updatedAt', + ); + @override + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [id, title, createdAt, updatedAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'chat_sessions'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('title')) { + context.handle( + _titleMeta, + title.isAcceptableOrUnknown(data['title']!, _titleMeta), + ); + } + if (data.containsKey('created_at')) { + context.handle( + _createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta), + ); + } else if (isInserting) { + context.missing(_createdAtMeta); + } + if (data.containsKey('updated_at')) { + context.handle( + _updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta), + ); + } else if (isInserting) { + context.missing(_updatedAtMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + ChatSession map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return ChatSession( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + title: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}title'], + ), + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + ); + } + + @override + $ChatSessionsTable createAlias(String alias) { + return $ChatSessionsTable(attachedDatabase, alias); + } +} + +class ChatSession extends DataClass implements Insertable { + final String id; + final String? title; + final String createdAt; + final String updatedAt; + const ChatSession({ + required this.id, + this.title, + required this.createdAt, + required this.updatedAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + if (!nullToAbsent || title != null) { + map['title'] = Variable(title); + } + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + return map; + } + + ChatSessionsCompanion toCompanion(bool nullToAbsent) { + return ChatSessionsCompanion( + id: Value(id), + title: title == null && nullToAbsent + ? const Value.absent() + : Value(title), + createdAt: Value(createdAt), + updatedAt: Value(updatedAt), + ); + } + + factory ChatSession.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return ChatSession( + id: serializer.fromJson(json['id']), + title: serializer.fromJson(json['title']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'title': serializer.toJson(title), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + ChatSession copyWith({ + String? id, + Value title = const Value.absent(), + String? createdAt, + String? updatedAt, + }) => ChatSession( + id: id ?? this.id, + title: title.present ? title.value : this.title, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + ChatSession copyWithCompanion(ChatSessionsCompanion data) { + return ChatSession( + id: data.id.present ? data.id.value : this.id, + title: data.title.present ? data.title.value : this.title, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ); + } + + @override + String toString() { + return (StringBuffer('ChatSession(') + ..write('id: $id, ') + ..write('title: $title, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, title, createdAt, updatedAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is ChatSession && + other.id == this.id && + other.title == this.title && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt); +} + +class ChatSessionsCompanion extends UpdateCompanion { + final Value id; + final Value title; + final Value createdAt; + final Value updatedAt; + final Value rowid; + const ChatSessionsCompanion({ + this.id = const Value.absent(), + this.title = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.rowid = const Value.absent(), + }); + ChatSessionsCompanion.insert({ + required String id, + this.title = const Value.absent(), + required String createdAt, + required String updatedAt, + this.rowid = const Value.absent(), + }) : id = Value(id), + createdAt = Value(createdAt), + updatedAt = Value(updatedAt); + static Insertable custom({ + Expression? id, + Expression? title, + Expression? createdAt, + Expression? updatedAt, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (title != null) 'title': title, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (rowid != null) 'rowid': rowid, + }); + } + + ChatSessionsCompanion copyWith({ + Value? id, + Value? title, + Value? createdAt, + Value? updatedAt, + Value? rowid, + }) { + return ChatSessionsCompanion( + id: id ?? this.id, + title: title ?? this.title, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (title.present) { + map['title'] = Variable(title.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ChatSessionsCompanion(') + ..write('id: $id, ') + ..write('title: $title, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $ChatMessagesTable extends ChatMessages + with TableInfo<$ChatMessagesTable, ChatMessage> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $ChatMessagesTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _sessionIdMeta = const VerificationMeta( + 'sessionId', + ); + @override + late final GeneratedColumn sessionId = GeneratedColumn( + 'session_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES chat_sessions (id) ON DELETE CASCADE', + ), + ); + static const VerificationMeta _roleMeta = const VerificationMeta('role'); + @override + late final GeneratedColumn role = GeneratedColumn( + 'role', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _contentMeta = const VerificationMeta( + 'content', + ); + @override + late final GeneratedColumn content = GeneratedColumn( + 'content', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _createdAtMeta = const VerificationMeta( + 'createdAt', + ); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + sessionId, + role, + content, + createdAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'chat_messages'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('session_id')) { + context.handle( + _sessionIdMeta, + sessionId.isAcceptableOrUnknown(data['session_id']!, _sessionIdMeta), + ); + } else if (isInserting) { + context.missing(_sessionIdMeta); + } + if (data.containsKey('role')) { + context.handle( + _roleMeta, + role.isAcceptableOrUnknown(data['role']!, _roleMeta), + ); + } else if (isInserting) { + context.missing(_roleMeta); + } + if (data.containsKey('content')) { + context.handle( + _contentMeta, + content.isAcceptableOrUnknown(data['content']!, _contentMeta), + ); + } else if (isInserting) { + context.missing(_contentMeta); + } + if (data.containsKey('created_at')) { + context.handle( + _createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta), + ); + } else if (isInserting) { + context.missing(_createdAtMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + ChatMessage map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return ChatMessage( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + sessionId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}session_id'], + )!, + role: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}role'], + )!, + content: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}content'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + ); + } + + @override + $ChatMessagesTable createAlias(String alias) { + return $ChatMessagesTable(attachedDatabase, alias); + } +} + +class ChatMessage extends DataClass implements Insertable { + final String id; + final String sessionId; + final String role; + final String content; + final String createdAt; + const ChatMessage({ + required this.id, + required this.sessionId, + required this.role, + required this.content, + required this.createdAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['session_id'] = Variable(sessionId); + map['role'] = Variable(role); + map['content'] = Variable(content); + map['created_at'] = Variable(createdAt); + return map; + } + + ChatMessagesCompanion toCompanion(bool nullToAbsent) { + return ChatMessagesCompanion( + id: Value(id), + sessionId: Value(sessionId), + role: Value(role), + content: Value(content), + createdAt: Value(createdAt), + ); + } + + factory ChatMessage.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return ChatMessage( + id: serializer.fromJson(json['id']), + sessionId: serializer.fromJson(json['sessionId']), + role: serializer.fromJson(json['role']), + content: serializer.fromJson(json['content']), + createdAt: serializer.fromJson(json['createdAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'sessionId': serializer.toJson(sessionId), + 'role': serializer.toJson(role), + 'content': serializer.toJson(content), + 'createdAt': serializer.toJson(createdAt), + }; + } + + ChatMessage copyWith({ + String? id, + String? sessionId, + String? role, + String? content, + String? createdAt, + }) => ChatMessage( + id: id ?? this.id, + sessionId: sessionId ?? this.sessionId, + role: role ?? this.role, + content: content ?? this.content, + createdAt: createdAt ?? this.createdAt, + ); + ChatMessage copyWithCompanion(ChatMessagesCompanion data) { + return ChatMessage( + id: data.id.present ? data.id.value : this.id, + sessionId: data.sessionId.present ? data.sessionId.value : this.sessionId, + role: data.role.present ? data.role.value : this.role, + content: data.content.present ? data.content.value : this.content, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('ChatMessage(') + ..write('id: $id, ') + ..write('sessionId: $sessionId, ') + ..write('role: $role, ') + ..write('content: $content, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, sessionId, role, content, createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is ChatMessage && + other.id == this.id && + other.sessionId == this.sessionId && + other.role == this.role && + other.content == this.content && + other.createdAt == this.createdAt); +} + +class ChatMessagesCompanion extends UpdateCompanion { + final Value id; + final Value sessionId; + final Value role; + final Value content; + final Value createdAt; + final Value rowid; + const ChatMessagesCompanion({ + this.id = const Value.absent(), + this.sessionId = const Value.absent(), + this.role = const Value.absent(), + this.content = const Value.absent(), + this.createdAt = const Value.absent(), + this.rowid = const Value.absent(), + }); + ChatMessagesCompanion.insert({ + required String id, + required String sessionId, + required String role, + required String content, + required String createdAt, + this.rowid = const Value.absent(), + }) : id = Value(id), + sessionId = Value(sessionId), + role = Value(role), + content = Value(content), + createdAt = Value(createdAt); + static Insertable custom({ + Expression? id, + Expression? sessionId, + Expression? role, + Expression? content, + Expression? createdAt, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (sessionId != null) 'session_id': sessionId, + if (role != null) 'role': role, + if (content != null) 'content': content, + if (createdAt != null) 'created_at': createdAt, + if (rowid != null) 'rowid': rowid, + }); + } + + ChatMessagesCompanion copyWith({ + Value? id, + Value? sessionId, + Value? role, + Value? content, + Value? createdAt, + Value? rowid, + }) { + return ChatMessagesCompanion( + id: id ?? this.id, + sessionId: sessionId ?? this.sessionId, + role: role ?? this.role, + content: content ?? this.content, + createdAt: createdAt ?? this.createdAt, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (sessionId.present) { + map['session_id'] = Variable(sessionId.value); + } + if (role.present) { + map['role'] = Variable(role.value); + } + if (content.present) { + map['content'] = Variable(content.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ChatMessagesCompanion(') + ..write('id: $id, ') + ..write('sessionId: $sessionId, ') + ..write('role: $role, ') + ..write('content: $content, ') + ..write('createdAt: $createdAt, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +abstract class _$AppDatabase extends GeneratedDatabase { + _$AppDatabase(QueryExecutor e) : super(e); + $AppDatabaseManager get managers => $AppDatabaseManager(this); + late final $ExercisesTable exercises = $ExercisesTable(this); + late final $TrainingPlansTable trainingPlans = $TrainingPlansTable(this); + late final $ProgramsTable programs = $ProgramsTable(this); + late final $ProgramWeeksTable programWeeks = $ProgramWeeksTable(this); + late final $ProgramWorkoutsTable programWorkouts = $ProgramWorkoutsTable( + this, + ); + late final $AnalysisSessionsTable analysisSessions = $AnalysisSessionsTable( + this, + ); + late final $AnnotationsTable annotations = $AnnotationsTable(this); + late final $ChatSessionsTable chatSessions = $ChatSessionsTable(this); + late final $ChatMessagesTable chatMessages = $ChatMessagesTable(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + exercises, + trainingPlans, + programs, + programWeeks, + programWorkouts, + analysisSessions, + annotations, + chatSessions, + chatMessages, + ]; + @override + StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules([ + WritePropagation( + on: TableUpdateQuery.onTableName( + 'programs', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('program_weeks', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'program_weeks', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('program_workouts', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'programs', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('program_workouts', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'analysis_sessions', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('annotations', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'chat_sessions', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('chat_messages', kind: UpdateKind.delete)], + ), + ]); +} + +typedef $$ExercisesTableCreateCompanionBuilder = + ExercisesCompanion Function({ + required String id, + required String name, + Value instructions, + Value enrichment, + Value tags, + Value videoUrl, + Value rowid, + }); +typedef $$ExercisesTableUpdateCompanionBuilder = + ExercisesCompanion Function({ + Value id, + Value name, + Value instructions, + Value enrichment, + Value tags, + Value videoUrl, + Value rowid, + }); + +class $$ExercisesTableFilterComposer + extends Composer<_$AppDatabase, $ExercisesTable> { + $$ExercisesTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get instructions => $composableBuilder( + column: $table.instructions, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get enrichment => $composableBuilder( + column: $table.enrichment, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get tags => $composableBuilder( + column: $table.tags, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get videoUrl => $composableBuilder( + column: $table.videoUrl, + builder: (column) => ColumnFilters(column), + ); +} + +class $$ExercisesTableOrderingComposer + extends Composer<_$AppDatabase, $ExercisesTable> { + $$ExercisesTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get instructions => $composableBuilder( + column: $table.instructions, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get enrichment => $composableBuilder( + column: $table.enrichment, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get tags => $composableBuilder( + column: $table.tags, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get videoUrl => $composableBuilder( + column: $table.videoUrl, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$ExercisesTableAnnotationComposer + extends Composer<_$AppDatabase, $ExercisesTable> { + $$ExercisesTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + GeneratedColumn get instructions => $composableBuilder( + column: $table.instructions, + builder: (column) => column, + ); + + GeneratedColumn get enrichment => $composableBuilder( + column: $table.enrichment, + builder: (column) => column, + ); + + GeneratedColumn get tags => + $composableBuilder(column: $table.tags, builder: (column) => column); + + GeneratedColumn get videoUrl => + $composableBuilder(column: $table.videoUrl, builder: (column) => column); +} + +class $$ExercisesTableTableManager + extends + RootTableManager< + _$AppDatabase, + $ExercisesTable, + Exercise, + $$ExercisesTableFilterComposer, + $$ExercisesTableOrderingComposer, + $$ExercisesTableAnnotationComposer, + $$ExercisesTableCreateCompanionBuilder, + $$ExercisesTableUpdateCompanionBuilder, + (Exercise, BaseReferences<_$AppDatabase, $ExercisesTable, Exercise>), + Exercise, + PrefetchHooks Function() + > { + $$ExercisesTableTableManager(_$AppDatabase db, $ExercisesTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$ExercisesTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$ExercisesTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$ExercisesTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value name = const Value.absent(), + Value instructions = const Value.absent(), + Value enrichment = const Value.absent(), + Value tags = const Value.absent(), + Value videoUrl = const Value.absent(), + Value rowid = const Value.absent(), + }) => ExercisesCompanion( + id: id, + name: name, + instructions: instructions, + enrichment: enrichment, + tags: tags, + videoUrl: videoUrl, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + required String name, + Value instructions = const Value.absent(), + Value enrichment = const Value.absent(), + Value tags = const Value.absent(), + Value videoUrl = const Value.absent(), + Value rowid = const Value.absent(), + }) => ExercisesCompanion.insert( + id: id, + name: name, + instructions: instructions, + enrichment: enrichment, + tags: tags, + videoUrl: videoUrl, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $$ExercisesTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $ExercisesTable, + Exercise, + $$ExercisesTableFilterComposer, + $$ExercisesTableOrderingComposer, + $$ExercisesTableAnnotationComposer, + $$ExercisesTableCreateCompanionBuilder, + $$ExercisesTableUpdateCompanionBuilder, + (Exercise, BaseReferences<_$AppDatabase, $ExercisesTable, Exercise>), + Exercise, + PrefetchHooks Function() + >; +typedef $$TrainingPlansTableCreateCompanionBuilder = + TrainingPlansCompanion Function({ + required String id, + required String name, + Value sections, + Value rowid, + }); +typedef $$TrainingPlansTableUpdateCompanionBuilder = + TrainingPlansCompanion Function({ + Value id, + Value name, + Value sections, + Value rowid, + }); + +class $$TrainingPlansTableFilterComposer + extends Composer<_$AppDatabase, $TrainingPlansTable> { + $$TrainingPlansTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get sections => $composableBuilder( + column: $table.sections, + builder: (column) => ColumnFilters(column), + ); +} + +class $$TrainingPlansTableOrderingComposer + extends Composer<_$AppDatabase, $TrainingPlansTable> { + $$TrainingPlansTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get sections => $composableBuilder( + column: $table.sections, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$TrainingPlansTableAnnotationComposer + extends Composer<_$AppDatabase, $TrainingPlansTable> { + $$TrainingPlansTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + GeneratedColumn get sections => + $composableBuilder(column: $table.sections, builder: (column) => column); +} + +class $$TrainingPlansTableTableManager + extends + RootTableManager< + _$AppDatabase, + $TrainingPlansTable, + TrainingPlan, + $$TrainingPlansTableFilterComposer, + $$TrainingPlansTableOrderingComposer, + $$TrainingPlansTableAnnotationComposer, + $$TrainingPlansTableCreateCompanionBuilder, + $$TrainingPlansTableUpdateCompanionBuilder, + ( + TrainingPlan, + BaseReferences<_$AppDatabase, $TrainingPlansTable, TrainingPlan>, + ), + TrainingPlan, + PrefetchHooks Function() + > { + $$TrainingPlansTableTableManager(_$AppDatabase db, $TrainingPlansTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$TrainingPlansTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$TrainingPlansTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$TrainingPlansTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value name = const Value.absent(), + Value sections = const Value.absent(), + Value rowid = const Value.absent(), + }) => TrainingPlansCompanion( + id: id, + name: name, + sections: sections, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + required String name, + Value sections = const Value.absent(), + Value rowid = const Value.absent(), + }) => TrainingPlansCompanion.insert( + id: id, + name: name, + sections: sections, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $$TrainingPlansTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $TrainingPlansTable, + TrainingPlan, + $$TrainingPlansTableFilterComposer, + $$TrainingPlansTableOrderingComposer, + $$TrainingPlansTableAnnotationComposer, + $$TrainingPlansTableCreateCompanionBuilder, + $$TrainingPlansTableUpdateCompanionBuilder, + ( + TrainingPlan, + BaseReferences<_$AppDatabase, $TrainingPlansTable, TrainingPlan>, + ), + TrainingPlan, + PrefetchHooks Function() + >; +typedef $$ProgramsTableCreateCompanionBuilder = + ProgramsCompanion Function({ + required String id, + required String name, + required String createdAt, + Value rowid, + }); +typedef $$ProgramsTableUpdateCompanionBuilder = + ProgramsCompanion Function({ + Value id, + Value name, + Value createdAt, + Value rowid, + }); + +final class $$ProgramsTableReferences + extends BaseReferences<_$AppDatabase, $ProgramsTable, Program> { + $$ProgramsTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static MultiTypedResultKey<$ProgramWeeksTable, List> + _programWeeksRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.programWeeks, + aliasName: $_aliasNameGenerator(db.programs.id, db.programWeeks.programId), + ); + + $$ProgramWeeksTableProcessedTableManager get programWeeksRefs { + final manager = $$ProgramWeeksTableTableManager( + $_db, + $_db.programWeeks, + ).filter((f) => f.programId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull(_programWeeksRefsTable($_db)); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } + + static MultiTypedResultKey<$ProgramWorkoutsTable, List> + _programWorkoutsRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.programWorkouts, + aliasName: $_aliasNameGenerator( + db.programs.id, + db.programWorkouts.programId, + ), + ); + + $$ProgramWorkoutsTableProcessedTableManager get programWorkoutsRefs { + final manager = $$ProgramWorkoutsTableTableManager( + $_db, + $_db.programWorkouts, + ).filter((f) => f.programId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull( + _programWorkoutsRefsTable($_db), + ); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } +} + +class $$ProgramsTableFilterComposer + extends Composer<_$AppDatabase, $ProgramsTable> { + $$ProgramsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnFilters(column), + ); + + Expression programWeeksRefs( + Expression Function($$ProgramWeeksTableFilterComposer f) f, + ) { + final $$ProgramWeeksTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.programWeeks, + getReferencedColumn: (t) => t.programId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ProgramWeeksTableFilterComposer( + $db: $db, + $table: $db.programWeeks, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } + + Expression programWorkoutsRefs( + Expression Function($$ProgramWorkoutsTableFilterComposer f) f, + ) { + final $$ProgramWorkoutsTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.programWorkouts, + getReferencedColumn: (t) => t.programId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ProgramWorkoutsTableFilterComposer( + $db: $db, + $table: $db.programWorkouts, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$ProgramsTableOrderingComposer + extends Composer<_$AppDatabase, $ProgramsTable> { + $$ProgramsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$ProgramsTableAnnotationComposer + extends Composer<_$AppDatabase, $ProgramsTable> { + $$ProgramsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); + + Expression programWeeksRefs( + Expression Function($$ProgramWeeksTableAnnotationComposer a) f, + ) { + final $$ProgramWeeksTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.programWeeks, + getReferencedColumn: (t) => t.programId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ProgramWeeksTableAnnotationComposer( + $db: $db, + $table: $db.programWeeks, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } + + Expression programWorkoutsRefs( + Expression Function($$ProgramWorkoutsTableAnnotationComposer a) f, + ) { + final $$ProgramWorkoutsTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.programWorkouts, + getReferencedColumn: (t) => t.programId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ProgramWorkoutsTableAnnotationComposer( + $db: $db, + $table: $db.programWorkouts, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$ProgramsTableTableManager + extends + RootTableManager< + _$AppDatabase, + $ProgramsTable, + Program, + $$ProgramsTableFilterComposer, + $$ProgramsTableOrderingComposer, + $$ProgramsTableAnnotationComposer, + $$ProgramsTableCreateCompanionBuilder, + $$ProgramsTableUpdateCompanionBuilder, + (Program, $$ProgramsTableReferences), + Program, + PrefetchHooks Function({ + bool programWeeksRefs, + bool programWorkoutsRefs, + }) + > { + $$ProgramsTableTableManager(_$AppDatabase db, $ProgramsTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$ProgramsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$ProgramsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$ProgramsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value name = const Value.absent(), + Value createdAt = const Value.absent(), + Value rowid = const Value.absent(), + }) => ProgramsCompanion( + id: id, + name: name, + createdAt: createdAt, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + required String name, + required String createdAt, + Value rowid = const Value.absent(), + }) => ProgramsCompanion.insert( + id: id, + name: name, + createdAt: createdAt, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + $$ProgramsTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: + ({programWeeksRefs = false, programWorkoutsRefs = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [ + if (programWeeksRefs) db.programWeeks, + if (programWorkoutsRefs) db.programWorkouts, + ], + addJoins: null, + getPrefetchedDataCallback: (items) async { + return [ + if (programWeeksRefs) + await $_getPrefetchedData< + Program, + $ProgramsTable, + ProgramWeek + >( + currentTable: table, + referencedTable: $$ProgramsTableReferences + ._programWeeksRefsTable(db), + managerFromTypedResult: (p0) => + $$ProgramsTableReferences( + db, + table, + p0, + ).programWeeksRefs, + referencedItemsForCurrentItem: + (item, referencedItems) => referencedItems.where( + (e) => e.programId == item.id, + ), + typedResults: items, + ), + if (programWorkoutsRefs) + await $_getPrefetchedData< + Program, + $ProgramsTable, + ProgramWorkout + >( + currentTable: table, + referencedTable: $$ProgramsTableReferences + ._programWorkoutsRefsTable(db), + managerFromTypedResult: (p0) => + $$ProgramsTableReferences( + db, + table, + p0, + ).programWorkoutsRefs, + referencedItemsForCurrentItem: + (item, referencedItems) => referencedItems.where( + (e) => e.programId == item.id, + ), + typedResults: items, + ), + ]; + }, + ); + }, + ), + ); +} + +typedef $$ProgramsTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $ProgramsTable, + Program, + $$ProgramsTableFilterComposer, + $$ProgramsTableOrderingComposer, + $$ProgramsTableAnnotationComposer, + $$ProgramsTableCreateCompanionBuilder, + $$ProgramsTableUpdateCompanionBuilder, + (Program, $$ProgramsTableReferences), + Program, + PrefetchHooks Function({bool programWeeksRefs, bool programWorkoutsRefs}) + >; +typedef $$ProgramWeeksTableCreateCompanionBuilder = + ProgramWeeksCompanion Function({ + required String id, + required String programId, + required int position, + Value notes, + Value rowid, + }); +typedef $$ProgramWeeksTableUpdateCompanionBuilder = + ProgramWeeksCompanion Function({ + Value id, + Value programId, + Value position, + Value notes, + Value rowid, + }); + +final class $$ProgramWeeksTableReferences + extends BaseReferences<_$AppDatabase, $ProgramWeeksTable, ProgramWeek> { + $$ProgramWeeksTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static $ProgramsTable _programIdTable(_$AppDatabase db) => + db.programs.createAlias( + $_aliasNameGenerator(db.programWeeks.programId, db.programs.id), + ); + + $$ProgramsTableProcessedTableManager get programId { + final $_column = $_itemColumn('program_id')!; + + final manager = $$ProgramsTableTableManager( + $_db, + $_db.programs, + ).filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_programIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } + + static MultiTypedResultKey<$ProgramWorkoutsTable, List> + _programWorkoutsRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.programWorkouts, + aliasName: $_aliasNameGenerator( + db.programWeeks.id, + db.programWorkouts.weekId, + ), + ); + + $$ProgramWorkoutsTableProcessedTableManager get programWorkoutsRefs { + final manager = $$ProgramWorkoutsTableTableManager( + $_db, + $_db.programWorkouts, + ).filter((f) => f.weekId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull( + _programWorkoutsRefsTable($_db), + ); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } +} + +class $$ProgramWeeksTableFilterComposer + extends Composer<_$AppDatabase, $ProgramWeeksTable> { + $$ProgramWeeksTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get position => $composableBuilder( + column: $table.position, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get notes => $composableBuilder( + column: $table.notes, + builder: (column) => ColumnFilters(column), + ); + + $$ProgramsTableFilterComposer get programId { + final $$ProgramsTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.programId, + referencedTable: $db.programs, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ProgramsTableFilterComposer( + $db: $db, + $table: $db.programs, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + Expression programWorkoutsRefs( + Expression Function($$ProgramWorkoutsTableFilterComposer f) f, + ) { + final $$ProgramWorkoutsTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.programWorkouts, + getReferencedColumn: (t) => t.weekId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ProgramWorkoutsTableFilterComposer( + $db: $db, + $table: $db.programWorkouts, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$ProgramWeeksTableOrderingComposer + extends Composer<_$AppDatabase, $ProgramWeeksTable> { + $$ProgramWeeksTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get position => $composableBuilder( + column: $table.position, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get notes => $composableBuilder( + column: $table.notes, + builder: (column) => ColumnOrderings(column), + ); + + $$ProgramsTableOrderingComposer get programId { + final $$ProgramsTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.programId, + referencedTable: $db.programs, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ProgramsTableOrderingComposer( + $db: $db, + $table: $db.programs, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$ProgramWeeksTableAnnotationComposer + extends Composer<_$AppDatabase, $ProgramWeeksTable> { + $$ProgramWeeksTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get position => + $composableBuilder(column: $table.position, builder: (column) => column); + + GeneratedColumn get notes => + $composableBuilder(column: $table.notes, builder: (column) => column); + + $$ProgramsTableAnnotationComposer get programId { + final $$ProgramsTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.programId, + referencedTable: $db.programs, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ProgramsTableAnnotationComposer( + $db: $db, + $table: $db.programs, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + Expression programWorkoutsRefs( + Expression Function($$ProgramWorkoutsTableAnnotationComposer a) f, + ) { + final $$ProgramWorkoutsTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.programWorkouts, + getReferencedColumn: (t) => t.weekId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ProgramWorkoutsTableAnnotationComposer( + $db: $db, + $table: $db.programWorkouts, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$ProgramWeeksTableTableManager + extends + RootTableManager< + _$AppDatabase, + $ProgramWeeksTable, + ProgramWeek, + $$ProgramWeeksTableFilterComposer, + $$ProgramWeeksTableOrderingComposer, + $$ProgramWeeksTableAnnotationComposer, + $$ProgramWeeksTableCreateCompanionBuilder, + $$ProgramWeeksTableUpdateCompanionBuilder, + (ProgramWeek, $$ProgramWeeksTableReferences), + ProgramWeek, + PrefetchHooks Function({bool programId, bool programWorkoutsRefs}) + > { + $$ProgramWeeksTableTableManager(_$AppDatabase db, $ProgramWeeksTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$ProgramWeeksTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$ProgramWeeksTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$ProgramWeeksTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value programId = const Value.absent(), + Value position = const Value.absent(), + Value notes = const Value.absent(), + Value rowid = const Value.absent(), + }) => ProgramWeeksCompanion( + id: id, + programId: programId, + position: position, + notes: notes, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + required String programId, + required int position, + Value notes = const Value.absent(), + Value rowid = const Value.absent(), + }) => ProgramWeeksCompanion.insert( + id: id, + programId: programId, + position: position, + notes: notes, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + $$ProgramWeeksTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: + ({programId = false, programWorkoutsRefs = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [ + if (programWorkoutsRefs) db.programWorkouts, + ], + addJoins: + < + T extends TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (programId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.programId, + referencedTable: + $$ProgramWeeksTableReferences + ._programIdTable(db), + referencedColumn: + $$ProgramWeeksTableReferences + ._programIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return [ + if (programWorkoutsRefs) + await $_getPrefetchedData< + ProgramWeek, + $ProgramWeeksTable, + ProgramWorkout + >( + currentTable: table, + referencedTable: $$ProgramWeeksTableReferences + ._programWorkoutsRefsTable(db), + managerFromTypedResult: (p0) => + $$ProgramWeeksTableReferences( + db, + table, + p0, + ).programWorkoutsRefs, + referencedItemsForCurrentItem: + (item, referencedItems) => referencedItems.where( + (e) => e.weekId == item.id, + ), + typedResults: items, + ), + ]; + }, + ); + }, + ), + ); +} + +typedef $$ProgramWeeksTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $ProgramWeeksTable, + ProgramWeek, + $$ProgramWeeksTableFilterComposer, + $$ProgramWeeksTableOrderingComposer, + $$ProgramWeeksTableAnnotationComposer, + $$ProgramWeeksTableCreateCompanionBuilder, + $$ProgramWeeksTableUpdateCompanionBuilder, + (ProgramWeek, $$ProgramWeeksTableReferences), + ProgramWeek, + PrefetchHooks Function({bool programId, bool programWorkoutsRefs}) + >; +typedef $$ProgramWorkoutsTableCreateCompanionBuilder = + ProgramWorkoutsCompanion Function({ + required String id, + required String weekId, + required String programId, + required String day, + required String type, + Value refId, + Value name, + Value description, + Value completed, + Value rowid, + }); +typedef $$ProgramWorkoutsTableUpdateCompanionBuilder = + ProgramWorkoutsCompanion Function({ + Value id, + Value weekId, + Value programId, + Value day, + Value type, + Value refId, + Value name, + Value description, + Value completed, + Value rowid, + }); + +final class $$ProgramWorkoutsTableReferences + extends + BaseReferences<_$AppDatabase, $ProgramWorkoutsTable, ProgramWorkout> { + $$ProgramWorkoutsTableReferences( + super.$_db, + super.$_table, + super.$_typedResult, + ); + + static $ProgramWeeksTable _weekIdTable(_$AppDatabase db) => + db.programWeeks.createAlias( + $_aliasNameGenerator(db.programWorkouts.weekId, db.programWeeks.id), + ); + + $$ProgramWeeksTableProcessedTableManager get weekId { + final $_column = $_itemColumn('week_id')!; + + final manager = $$ProgramWeeksTableTableManager( + $_db, + $_db.programWeeks, + ).filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_weekIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } + + static $ProgramsTable _programIdTable(_$AppDatabase db) => + db.programs.createAlias( + $_aliasNameGenerator(db.programWorkouts.programId, db.programs.id), + ); + + $$ProgramsTableProcessedTableManager get programId { + final $_column = $_itemColumn('program_id')!; + + final manager = $$ProgramsTableTableManager( + $_db, + $_db.programs, + ).filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_programIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } +} + +class $$ProgramWorkoutsTableFilterComposer + extends Composer<_$AppDatabase, $ProgramWorkoutsTable> { + $$ProgramWorkoutsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get day => $composableBuilder( + column: $table.day, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get type => $composableBuilder( + column: $table.type, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get refId => $composableBuilder( + column: $table.refId, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get description => $composableBuilder( + column: $table.description, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get completed => $composableBuilder( + column: $table.completed, + builder: (column) => ColumnFilters(column), + ); + + $$ProgramWeeksTableFilterComposer get weekId { + final $$ProgramWeeksTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.weekId, + referencedTable: $db.programWeeks, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ProgramWeeksTableFilterComposer( + $db: $db, + $table: $db.programWeeks, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + $$ProgramsTableFilterComposer get programId { + final $$ProgramsTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.programId, + referencedTable: $db.programs, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ProgramsTableFilterComposer( + $db: $db, + $table: $db.programs, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$ProgramWorkoutsTableOrderingComposer + extends Composer<_$AppDatabase, $ProgramWorkoutsTable> { + $$ProgramWorkoutsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get day => $composableBuilder( + column: $table.day, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get type => $composableBuilder( + column: $table.type, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get refId => $composableBuilder( + column: $table.refId, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get description => $composableBuilder( + column: $table.description, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get completed => $composableBuilder( + column: $table.completed, + builder: (column) => ColumnOrderings(column), + ); + + $$ProgramWeeksTableOrderingComposer get weekId { + final $$ProgramWeeksTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.weekId, + referencedTable: $db.programWeeks, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ProgramWeeksTableOrderingComposer( + $db: $db, + $table: $db.programWeeks, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + $$ProgramsTableOrderingComposer get programId { + final $$ProgramsTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.programId, + referencedTable: $db.programs, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ProgramsTableOrderingComposer( + $db: $db, + $table: $db.programs, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$ProgramWorkoutsTableAnnotationComposer + extends Composer<_$AppDatabase, $ProgramWorkoutsTable> { + $$ProgramWorkoutsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get day => + $composableBuilder(column: $table.day, builder: (column) => column); + + GeneratedColumn get type => + $composableBuilder(column: $table.type, builder: (column) => column); + + GeneratedColumn get refId => + $composableBuilder(column: $table.refId, builder: (column) => column); + + GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + GeneratedColumn get description => $composableBuilder( + column: $table.description, + builder: (column) => column, + ); + + GeneratedColumn get completed => + $composableBuilder(column: $table.completed, builder: (column) => column); + + $$ProgramWeeksTableAnnotationComposer get weekId { + final $$ProgramWeeksTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.weekId, + referencedTable: $db.programWeeks, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ProgramWeeksTableAnnotationComposer( + $db: $db, + $table: $db.programWeeks, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + $$ProgramsTableAnnotationComposer get programId { + final $$ProgramsTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.programId, + referencedTable: $db.programs, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ProgramsTableAnnotationComposer( + $db: $db, + $table: $db.programs, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$ProgramWorkoutsTableTableManager + extends + RootTableManager< + _$AppDatabase, + $ProgramWorkoutsTable, + ProgramWorkout, + $$ProgramWorkoutsTableFilterComposer, + $$ProgramWorkoutsTableOrderingComposer, + $$ProgramWorkoutsTableAnnotationComposer, + $$ProgramWorkoutsTableCreateCompanionBuilder, + $$ProgramWorkoutsTableUpdateCompanionBuilder, + (ProgramWorkout, $$ProgramWorkoutsTableReferences), + ProgramWorkout, + PrefetchHooks Function({bool weekId, bool programId}) + > { + $$ProgramWorkoutsTableTableManager( + _$AppDatabase db, + $ProgramWorkoutsTable table, + ) : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$ProgramWorkoutsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$ProgramWorkoutsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$ProgramWorkoutsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value weekId = const Value.absent(), + Value programId = const Value.absent(), + Value day = const Value.absent(), + Value type = const Value.absent(), + Value refId = const Value.absent(), + Value name = const Value.absent(), + Value description = const Value.absent(), + Value completed = const Value.absent(), + Value rowid = const Value.absent(), + }) => ProgramWorkoutsCompanion( + id: id, + weekId: weekId, + programId: programId, + day: day, + type: type, + refId: refId, + name: name, + description: description, + completed: completed, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + required String weekId, + required String programId, + required String day, + required String type, + Value refId = const Value.absent(), + Value name = const Value.absent(), + Value description = const Value.absent(), + Value completed = const Value.absent(), + Value rowid = const Value.absent(), + }) => ProgramWorkoutsCompanion.insert( + id: id, + weekId: weekId, + programId: programId, + day: day, + type: type, + refId: refId, + name: name, + description: description, + completed: completed, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + $$ProgramWorkoutsTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({weekId = false, programId = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: + < + T extends TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (weekId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.weekId, + referencedTable: + $$ProgramWorkoutsTableReferences + ._weekIdTable(db), + referencedColumn: + $$ProgramWorkoutsTableReferences + ._weekIdTable(db) + .id, + ) + as T; + } + if (programId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.programId, + referencedTable: + $$ProgramWorkoutsTableReferences + ._programIdTable(db), + referencedColumn: + $$ProgramWorkoutsTableReferences + ._programIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + ), + ); +} + +typedef $$ProgramWorkoutsTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $ProgramWorkoutsTable, + ProgramWorkout, + $$ProgramWorkoutsTableFilterComposer, + $$ProgramWorkoutsTableOrderingComposer, + $$ProgramWorkoutsTableAnnotationComposer, + $$ProgramWorkoutsTableCreateCompanionBuilder, + $$ProgramWorkoutsTableUpdateCompanionBuilder, + (ProgramWorkout, $$ProgramWorkoutsTableReferences), + ProgramWorkout, + PrefetchHooks Function({bool weekId, bool programId}) + >; +typedef $$AnalysisSessionsTableCreateCompanionBuilder = + AnalysisSessionsCompanion Function({ + required String id, + required String name, + required String date, + Value videoPath, + Value rowid, + }); +typedef $$AnalysisSessionsTableUpdateCompanionBuilder = + AnalysisSessionsCompanion Function({ + Value id, + Value name, + Value date, + Value videoPath, + Value rowid, + }); + +final class $$AnalysisSessionsTableReferences + extends + BaseReferences<_$AppDatabase, $AnalysisSessionsTable, AnalysisSession> { + $$AnalysisSessionsTableReferences( + super.$_db, + super.$_table, + super.$_typedResult, + ); + + static MultiTypedResultKey<$AnnotationsTable, List> + _annotationsRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.annotations, + aliasName: $_aliasNameGenerator( + db.analysisSessions.id, + db.annotations.sessionId, + ), + ); + + $$AnnotationsTableProcessedTableManager get annotationsRefs { + final manager = $$AnnotationsTableTableManager( + $_db, + $_db.annotations, + ).filter((f) => f.sessionId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull(_annotationsRefsTable($_db)); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } +} + +class $$AnalysisSessionsTableFilterComposer + extends Composer<_$AppDatabase, $AnalysisSessionsTable> { + $$AnalysisSessionsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get date => $composableBuilder( + column: $table.date, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get videoPath => $composableBuilder( + column: $table.videoPath, + builder: (column) => ColumnFilters(column), + ); + + Expression annotationsRefs( + Expression Function($$AnnotationsTableFilterComposer f) f, + ) { + final $$AnnotationsTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.annotations, + getReferencedColumn: (t) => t.sessionId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$AnnotationsTableFilterComposer( + $db: $db, + $table: $db.annotations, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$AnalysisSessionsTableOrderingComposer + extends Composer<_$AppDatabase, $AnalysisSessionsTable> { + $$AnalysisSessionsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get date => $composableBuilder( + column: $table.date, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get videoPath => $composableBuilder( + column: $table.videoPath, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$AnalysisSessionsTableAnnotationComposer + extends Composer<_$AppDatabase, $AnalysisSessionsTable> { + $$AnalysisSessionsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + GeneratedColumn get date => + $composableBuilder(column: $table.date, builder: (column) => column); + + GeneratedColumn get videoPath => + $composableBuilder(column: $table.videoPath, builder: (column) => column); + + Expression annotationsRefs( + Expression Function($$AnnotationsTableAnnotationComposer a) f, + ) { + final $$AnnotationsTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.annotations, + getReferencedColumn: (t) => t.sessionId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$AnnotationsTableAnnotationComposer( + $db: $db, + $table: $db.annotations, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$AnalysisSessionsTableTableManager + extends + RootTableManager< + _$AppDatabase, + $AnalysisSessionsTable, + AnalysisSession, + $$AnalysisSessionsTableFilterComposer, + $$AnalysisSessionsTableOrderingComposer, + $$AnalysisSessionsTableAnnotationComposer, + $$AnalysisSessionsTableCreateCompanionBuilder, + $$AnalysisSessionsTableUpdateCompanionBuilder, + (AnalysisSession, $$AnalysisSessionsTableReferences), + AnalysisSession, + PrefetchHooks Function({bool annotationsRefs}) + > { + $$AnalysisSessionsTableTableManager( + _$AppDatabase db, + $AnalysisSessionsTable table, + ) : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$AnalysisSessionsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$AnalysisSessionsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$AnalysisSessionsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value name = const Value.absent(), + Value date = const Value.absent(), + Value videoPath = const Value.absent(), + Value rowid = const Value.absent(), + }) => AnalysisSessionsCompanion( + id: id, + name: name, + date: date, + videoPath: videoPath, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + required String name, + required String date, + Value videoPath = const Value.absent(), + Value rowid = const Value.absent(), + }) => AnalysisSessionsCompanion.insert( + id: id, + name: name, + date: date, + videoPath: videoPath, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + $$AnalysisSessionsTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({annotationsRefs = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [if (annotationsRefs) db.annotations], + addJoins: null, + getPrefetchedDataCallback: (items) async { + return [ + if (annotationsRefs) + await $_getPrefetchedData< + AnalysisSession, + $AnalysisSessionsTable, + Annotation + >( + currentTable: table, + referencedTable: $$AnalysisSessionsTableReferences + ._annotationsRefsTable(db), + managerFromTypedResult: (p0) => + $$AnalysisSessionsTableReferences( + db, + table, + p0, + ).annotationsRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.sessionId == item.id), + typedResults: items, + ), + ]; + }, + ); + }, + ), + ); +} + +typedef $$AnalysisSessionsTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $AnalysisSessionsTable, + AnalysisSession, + $$AnalysisSessionsTableFilterComposer, + $$AnalysisSessionsTableOrderingComposer, + $$AnalysisSessionsTableAnnotationComposer, + $$AnalysisSessionsTableCreateCompanionBuilder, + $$AnalysisSessionsTableUpdateCompanionBuilder, + (AnalysisSession, $$AnalysisSessionsTableReferences), + AnalysisSession, + PrefetchHooks Function({bool annotationsRefs}) + >; +typedef $$AnnotationsTableCreateCompanionBuilder = + AnnotationsCompanion Function({ + required String id, + required String sessionId, + required double startTime, + required double endTime, + Value name, + Value description, + Value color, + Value rowid, + }); +typedef $$AnnotationsTableUpdateCompanionBuilder = + AnnotationsCompanion Function({ + Value id, + Value sessionId, + Value startTime, + Value endTime, + Value name, + Value description, + Value color, + Value rowid, + }); + +final class $$AnnotationsTableReferences + extends BaseReferences<_$AppDatabase, $AnnotationsTable, Annotation> { + $$AnnotationsTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static $AnalysisSessionsTable _sessionIdTable(_$AppDatabase db) => + db.analysisSessions.createAlias( + $_aliasNameGenerator(db.annotations.sessionId, db.analysisSessions.id), + ); + + $$AnalysisSessionsTableProcessedTableManager get sessionId { + final $_column = $_itemColumn('session_id')!; + + final manager = $$AnalysisSessionsTableTableManager( + $_db, + $_db.analysisSessions, + ).filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_sessionIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } +} + +class $$AnnotationsTableFilterComposer + extends Composer<_$AppDatabase, $AnnotationsTable> { + $$AnnotationsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get startTime => $composableBuilder( + column: $table.startTime, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get endTime => $composableBuilder( + column: $table.endTime, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get description => $composableBuilder( + column: $table.description, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get color => $composableBuilder( + column: $table.color, + builder: (column) => ColumnFilters(column), + ); + + $$AnalysisSessionsTableFilterComposer get sessionId { + final $$AnalysisSessionsTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.sessionId, + referencedTable: $db.analysisSessions, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$AnalysisSessionsTableFilterComposer( + $db: $db, + $table: $db.analysisSessions, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$AnnotationsTableOrderingComposer + extends Composer<_$AppDatabase, $AnnotationsTable> { + $$AnnotationsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get startTime => $composableBuilder( + column: $table.startTime, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get endTime => $composableBuilder( + column: $table.endTime, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get description => $composableBuilder( + column: $table.description, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get color => $composableBuilder( + column: $table.color, + builder: (column) => ColumnOrderings(column), + ); + + $$AnalysisSessionsTableOrderingComposer get sessionId { + final $$AnalysisSessionsTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.sessionId, + referencedTable: $db.analysisSessions, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$AnalysisSessionsTableOrderingComposer( + $db: $db, + $table: $db.analysisSessions, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$AnnotationsTableAnnotationComposer + extends Composer<_$AppDatabase, $AnnotationsTable> { + $$AnnotationsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get startTime => + $composableBuilder(column: $table.startTime, builder: (column) => column); + + GeneratedColumn get endTime => + $composableBuilder(column: $table.endTime, builder: (column) => column); + + GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + GeneratedColumn get description => $composableBuilder( + column: $table.description, + builder: (column) => column, + ); + + GeneratedColumn get color => + $composableBuilder(column: $table.color, builder: (column) => column); + + $$AnalysisSessionsTableAnnotationComposer get sessionId { + final $$AnalysisSessionsTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.sessionId, + referencedTable: $db.analysisSessions, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$AnalysisSessionsTableAnnotationComposer( + $db: $db, + $table: $db.analysisSessions, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$AnnotationsTableTableManager + extends + RootTableManager< + _$AppDatabase, + $AnnotationsTable, + Annotation, + $$AnnotationsTableFilterComposer, + $$AnnotationsTableOrderingComposer, + $$AnnotationsTableAnnotationComposer, + $$AnnotationsTableCreateCompanionBuilder, + $$AnnotationsTableUpdateCompanionBuilder, + (Annotation, $$AnnotationsTableReferences), + Annotation, + PrefetchHooks Function({bool sessionId}) + > { + $$AnnotationsTableTableManager(_$AppDatabase db, $AnnotationsTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$AnnotationsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$AnnotationsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$AnnotationsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value sessionId = const Value.absent(), + Value startTime = const Value.absent(), + Value endTime = const Value.absent(), + Value name = const Value.absent(), + Value description = const Value.absent(), + Value color = const Value.absent(), + Value rowid = const Value.absent(), + }) => AnnotationsCompanion( + id: id, + sessionId: sessionId, + startTime: startTime, + endTime: endTime, + name: name, + description: description, + color: color, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + required String sessionId, + required double startTime, + required double endTime, + Value name = const Value.absent(), + Value description = const Value.absent(), + Value color = const Value.absent(), + Value rowid = const Value.absent(), + }) => AnnotationsCompanion.insert( + id: id, + sessionId: sessionId, + startTime: startTime, + endTime: endTime, + name: name, + description: description, + color: color, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + $$AnnotationsTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({sessionId = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: + < + T extends TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (sessionId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.sessionId, + referencedTable: $$AnnotationsTableReferences + ._sessionIdTable(db), + referencedColumn: $$AnnotationsTableReferences + ._sessionIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + ), + ); +} + +typedef $$AnnotationsTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $AnnotationsTable, + Annotation, + $$AnnotationsTableFilterComposer, + $$AnnotationsTableOrderingComposer, + $$AnnotationsTableAnnotationComposer, + $$AnnotationsTableCreateCompanionBuilder, + $$AnnotationsTableUpdateCompanionBuilder, + (Annotation, $$AnnotationsTableReferences), + Annotation, + PrefetchHooks Function({bool sessionId}) + >; +typedef $$ChatSessionsTableCreateCompanionBuilder = + ChatSessionsCompanion Function({ + required String id, + Value title, + required String createdAt, + required String updatedAt, + Value rowid, + }); +typedef $$ChatSessionsTableUpdateCompanionBuilder = + ChatSessionsCompanion Function({ + Value id, + Value title, + Value createdAt, + Value updatedAt, + Value rowid, + }); + +final class $$ChatSessionsTableReferences + extends BaseReferences<_$AppDatabase, $ChatSessionsTable, ChatSession> { + $$ChatSessionsTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static MultiTypedResultKey<$ChatMessagesTable, List> + _chatMessagesRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.chatMessages, + aliasName: $_aliasNameGenerator( + db.chatSessions.id, + db.chatMessages.sessionId, + ), + ); + + $$ChatMessagesTableProcessedTableManager get chatMessagesRefs { + final manager = $$ChatMessagesTableTableManager( + $_db, + $_db.chatMessages, + ).filter((f) => f.sessionId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull(_chatMessagesRefsTable($_db)); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } +} + +class $$ChatSessionsTableFilterComposer + extends Composer<_$AppDatabase, $ChatSessionsTable> { + $$ChatSessionsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get title => $composableBuilder( + column: $table.title, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get updatedAt => $composableBuilder( + column: $table.updatedAt, + builder: (column) => ColumnFilters(column), + ); + + Expression chatMessagesRefs( + Expression Function($$ChatMessagesTableFilterComposer f) f, + ) { + final $$ChatMessagesTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.chatMessages, + getReferencedColumn: (t) => t.sessionId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ChatMessagesTableFilterComposer( + $db: $db, + $table: $db.chatMessages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$ChatSessionsTableOrderingComposer + extends Composer<_$AppDatabase, $ChatSessionsTable> { + $$ChatSessionsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get title => $composableBuilder( + column: $table.title, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get updatedAt => $composableBuilder( + column: $table.updatedAt, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$ChatSessionsTableAnnotationComposer + extends Composer<_$AppDatabase, $ChatSessionsTable> { + $$ChatSessionsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get title => + $composableBuilder(column: $table.title, builder: (column) => column); + + GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); + + GeneratedColumn get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => column); + + Expression chatMessagesRefs( + Expression Function($$ChatMessagesTableAnnotationComposer a) f, + ) { + final $$ChatMessagesTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.chatMessages, + getReferencedColumn: (t) => t.sessionId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ChatMessagesTableAnnotationComposer( + $db: $db, + $table: $db.chatMessages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$ChatSessionsTableTableManager + extends + RootTableManager< + _$AppDatabase, + $ChatSessionsTable, + ChatSession, + $$ChatSessionsTableFilterComposer, + $$ChatSessionsTableOrderingComposer, + $$ChatSessionsTableAnnotationComposer, + $$ChatSessionsTableCreateCompanionBuilder, + $$ChatSessionsTableUpdateCompanionBuilder, + (ChatSession, $$ChatSessionsTableReferences), + ChatSession, + PrefetchHooks Function({bool chatMessagesRefs}) + > { + $$ChatSessionsTableTableManager(_$AppDatabase db, $ChatSessionsTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$ChatSessionsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$ChatSessionsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$ChatSessionsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value title = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value rowid = const Value.absent(), + }) => ChatSessionsCompanion( + id: id, + title: title, + createdAt: createdAt, + updatedAt: updatedAt, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + Value title = const Value.absent(), + required String createdAt, + required String updatedAt, + Value rowid = const Value.absent(), + }) => ChatSessionsCompanion.insert( + id: id, + title: title, + createdAt: createdAt, + updatedAt: updatedAt, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + $$ChatSessionsTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({chatMessagesRefs = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [if (chatMessagesRefs) db.chatMessages], + addJoins: null, + getPrefetchedDataCallback: (items) async { + return [ + if (chatMessagesRefs) + await $_getPrefetchedData< + ChatSession, + $ChatSessionsTable, + ChatMessage + >( + currentTable: table, + referencedTable: $$ChatSessionsTableReferences + ._chatMessagesRefsTable(db), + managerFromTypedResult: (p0) => + $$ChatSessionsTableReferences( + db, + table, + p0, + ).chatMessagesRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.sessionId == item.id), + typedResults: items, + ), + ]; + }, + ); + }, + ), + ); +} + +typedef $$ChatSessionsTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $ChatSessionsTable, + ChatSession, + $$ChatSessionsTableFilterComposer, + $$ChatSessionsTableOrderingComposer, + $$ChatSessionsTableAnnotationComposer, + $$ChatSessionsTableCreateCompanionBuilder, + $$ChatSessionsTableUpdateCompanionBuilder, + (ChatSession, $$ChatSessionsTableReferences), + ChatSession, + PrefetchHooks Function({bool chatMessagesRefs}) + >; +typedef $$ChatMessagesTableCreateCompanionBuilder = + ChatMessagesCompanion Function({ + required String id, + required String sessionId, + required String role, + required String content, + required String createdAt, + Value rowid, + }); +typedef $$ChatMessagesTableUpdateCompanionBuilder = + ChatMessagesCompanion Function({ + Value id, + Value sessionId, + Value role, + Value content, + Value createdAt, + Value rowid, + }); + +final class $$ChatMessagesTableReferences + extends BaseReferences<_$AppDatabase, $ChatMessagesTable, ChatMessage> { + $$ChatMessagesTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static $ChatSessionsTable _sessionIdTable(_$AppDatabase db) => + db.chatSessions.createAlias( + $_aliasNameGenerator(db.chatMessages.sessionId, db.chatSessions.id), + ); + + $$ChatSessionsTableProcessedTableManager get sessionId { + final $_column = $_itemColumn('session_id')!; + + final manager = $$ChatSessionsTableTableManager( + $_db, + $_db.chatSessions, + ).filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_sessionIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } +} + +class $$ChatMessagesTableFilterComposer + extends Composer<_$AppDatabase, $ChatMessagesTable> { + $$ChatMessagesTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get role => $composableBuilder( + column: $table.role, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get content => $composableBuilder( + column: $table.content, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnFilters(column), + ); + + $$ChatSessionsTableFilterComposer get sessionId { + final $$ChatSessionsTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.sessionId, + referencedTable: $db.chatSessions, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ChatSessionsTableFilterComposer( + $db: $db, + $table: $db.chatSessions, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$ChatMessagesTableOrderingComposer + extends Composer<_$AppDatabase, $ChatMessagesTable> { + $$ChatMessagesTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get role => $composableBuilder( + column: $table.role, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get content => $composableBuilder( + column: $table.content, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnOrderings(column), + ); + + $$ChatSessionsTableOrderingComposer get sessionId { + final $$ChatSessionsTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.sessionId, + referencedTable: $db.chatSessions, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ChatSessionsTableOrderingComposer( + $db: $db, + $table: $db.chatSessions, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$ChatMessagesTableAnnotationComposer + extends Composer<_$AppDatabase, $ChatMessagesTable> { + $$ChatMessagesTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get role => + $composableBuilder(column: $table.role, builder: (column) => column); + + GeneratedColumn get content => + $composableBuilder(column: $table.content, builder: (column) => column); + + GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); + + $$ChatSessionsTableAnnotationComposer get sessionId { + final $$ChatSessionsTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.sessionId, + referencedTable: $db.chatSessions, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ChatSessionsTableAnnotationComposer( + $db: $db, + $table: $db.chatSessions, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$ChatMessagesTableTableManager + extends + RootTableManager< + _$AppDatabase, + $ChatMessagesTable, + ChatMessage, + $$ChatMessagesTableFilterComposer, + $$ChatMessagesTableOrderingComposer, + $$ChatMessagesTableAnnotationComposer, + $$ChatMessagesTableCreateCompanionBuilder, + $$ChatMessagesTableUpdateCompanionBuilder, + (ChatMessage, $$ChatMessagesTableReferences), + ChatMessage, + PrefetchHooks Function({bool sessionId}) + > { + $$ChatMessagesTableTableManager(_$AppDatabase db, $ChatMessagesTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$ChatMessagesTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$ChatMessagesTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$ChatMessagesTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value sessionId = const Value.absent(), + Value role = const Value.absent(), + Value content = const Value.absent(), + Value createdAt = const Value.absent(), + Value rowid = const Value.absent(), + }) => ChatMessagesCompanion( + id: id, + sessionId: sessionId, + role: role, + content: content, + createdAt: createdAt, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + required String sessionId, + required String role, + required String content, + required String createdAt, + Value rowid = const Value.absent(), + }) => ChatMessagesCompanion.insert( + id: id, + sessionId: sessionId, + role: role, + content: content, + createdAt: createdAt, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + $$ChatMessagesTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({sessionId = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: + < + T extends TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (sessionId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.sessionId, + referencedTable: $$ChatMessagesTableReferences + ._sessionIdTable(db), + referencedColumn: $$ChatMessagesTableReferences + ._sessionIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + ), + ); +} + +typedef $$ChatMessagesTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $ChatMessagesTable, + ChatMessage, + $$ChatMessagesTableFilterComposer, + $$ChatMessagesTableOrderingComposer, + $$ChatMessagesTableAnnotationComposer, + $$ChatMessagesTableCreateCompanionBuilder, + $$ChatMessagesTableUpdateCompanionBuilder, + (ChatMessage, $$ChatMessagesTableReferences), + ChatMessage, + PrefetchHooks Function({bool sessionId}) + >; + +class $AppDatabaseManager { + final _$AppDatabase _db; + $AppDatabaseManager(this._db); + $$ExercisesTableTableManager get exercises => + $$ExercisesTableTableManager(_db, _db.exercises); + $$TrainingPlansTableTableManager get trainingPlans => + $$TrainingPlansTableTableManager(_db, _db.trainingPlans); + $$ProgramsTableTableManager get programs => + $$ProgramsTableTableManager(_db, _db.programs); + $$ProgramWeeksTableTableManager get programWeeks => + $$ProgramWeeksTableTableManager(_db, _db.programWeeks); + $$ProgramWorkoutsTableTableManager get programWorkouts => + $$ProgramWorkoutsTableTableManager(_db, _db.programWorkouts); + $$AnalysisSessionsTableTableManager get analysisSessions => + $$AnalysisSessionsTableTableManager(_db, _db.analysisSessions); + $$AnnotationsTableTableManager get annotations => + $$AnnotationsTableTableManager(_db, _db.annotations); + $$ChatSessionsTableTableManager get chatSessions => + $$ChatSessionsTableTableManager(_db, _db.chatSessions); + $$ChatMessagesTableTableManager get chatMessages => + $$ChatMessagesTableTableManager(_db, _db.chatMessages); +} diff --git a/lib/domain/entities/analysis_session.dart b/lib/domain/entities/analysis_session.dart new file mode 100644 index 0000000..d410584 --- /dev/null +++ b/lib/domain/entities/analysis_session.dart @@ -0,0 +1,13 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'analysis_session.freezed.dart'; + +@freezed +class AnalysisSessionEntity with _$AnalysisSessionEntity { + const factory AnalysisSessionEntity({ + required String id, + required String name, + required String date, + String? videoPath, + }) = _AnalysisSessionEntity; +} diff --git a/lib/domain/entities/analysis_session.freezed.dart b/lib/domain/entities/analysis_session.freezed.dart new file mode 100644 index 0000000..8b8c54f --- /dev/null +++ b/lib/domain/entities/analysis_session.freezed.dart @@ -0,0 +1,219 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'analysis_session.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$AnalysisSessionEntity { + String get id => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + String get date => throw _privateConstructorUsedError; + String? get videoPath => throw _privateConstructorUsedError; + + /// Create a copy of AnalysisSessionEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $AnalysisSessionEntityCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AnalysisSessionEntityCopyWith<$Res> { + factory $AnalysisSessionEntityCopyWith( + AnalysisSessionEntity value, + $Res Function(AnalysisSessionEntity) then, + ) = _$AnalysisSessionEntityCopyWithImpl<$Res, AnalysisSessionEntity>; + @useResult + $Res call({String id, String name, String date, String? videoPath}); +} + +/// @nodoc +class _$AnalysisSessionEntityCopyWithImpl< + $Res, + $Val extends AnalysisSessionEntity +> + implements $AnalysisSessionEntityCopyWith<$Res> { + _$AnalysisSessionEntityCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of AnalysisSessionEntity + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? name = null, + Object? date = null, + Object? videoPath = freezed, + }) { + return _then( + _value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + date: null == date + ? _value.date + : date // ignore: cast_nullable_to_non_nullable + as String, + videoPath: freezed == videoPath + ? _value.videoPath + : videoPath // ignore: cast_nullable_to_non_nullable + as String?, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$AnalysisSessionEntityImplCopyWith<$Res> + implements $AnalysisSessionEntityCopyWith<$Res> { + factory _$$AnalysisSessionEntityImplCopyWith( + _$AnalysisSessionEntityImpl value, + $Res Function(_$AnalysisSessionEntityImpl) then, + ) = __$$AnalysisSessionEntityImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String id, String name, String date, String? videoPath}); +} + +/// @nodoc +class __$$AnalysisSessionEntityImplCopyWithImpl<$Res> + extends + _$AnalysisSessionEntityCopyWithImpl<$Res, _$AnalysisSessionEntityImpl> + implements _$$AnalysisSessionEntityImplCopyWith<$Res> { + __$$AnalysisSessionEntityImplCopyWithImpl( + _$AnalysisSessionEntityImpl _value, + $Res Function(_$AnalysisSessionEntityImpl) _then, + ) : super(_value, _then); + + /// Create a copy of AnalysisSessionEntity + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? name = null, + Object? date = null, + Object? videoPath = freezed, + }) { + return _then( + _$AnalysisSessionEntityImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + date: null == date + ? _value.date + : date // ignore: cast_nullable_to_non_nullable + as String, + videoPath: freezed == videoPath + ? _value.videoPath + : videoPath // ignore: cast_nullable_to_non_nullable + as String?, + ), + ); + } +} + +/// @nodoc + +class _$AnalysisSessionEntityImpl implements _AnalysisSessionEntity { + const _$AnalysisSessionEntityImpl({ + required this.id, + required this.name, + required this.date, + this.videoPath, + }); + + @override + final String id; + @override + final String name; + @override + final String date; + @override + final String? videoPath; + + @override + String toString() { + return 'AnalysisSessionEntity(id: $id, name: $name, date: $date, videoPath: $videoPath)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AnalysisSessionEntityImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.name, name) || other.name == name) && + (identical(other.date, date) || other.date == date) && + (identical(other.videoPath, videoPath) || + other.videoPath == videoPath)); + } + + @override + int get hashCode => Object.hash(runtimeType, id, name, date, videoPath); + + /// Create a copy of AnalysisSessionEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$AnalysisSessionEntityImplCopyWith<_$AnalysisSessionEntityImpl> + get copyWith => + __$$AnalysisSessionEntityImplCopyWithImpl<_$AnalysisSessionEntityImpl>( + this, + _$identity, + ); +} + +abstract class _AnalysisSessionEntity implements AnalysisSessionEntity { + const factory _AnalysisSessionEntity({ + required final String id, + required final String name, + required final String date, + final String? videoPath, + }) = _$AnalysisSessionEntityImpl; + + @override + String get id; + @override + String get name; + @override + String get date; + @override + String? get videoPath; + + /// Create a copy of AnalysisSessionEntity + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$AnalysisSessionEntityImplCopyWith<_$AnalysisSessionEntityImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/domain/entities/annotation.dart b/lib/domain/entities/annotation.dart new file mode 100644 index 0000000..7c1252b --- /dev/null +++ b/lib/domain/entities/annotation.dart @@ -0,0 +1,16 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'annotation.freezed.dart'; + +@freezed +class AnnotationEntity with _$AnnotationEntity { + const factory AnnotationEntity({ + required String id, + required String sessionId, + required double startTime, + required double endTime, + String? name, + String? description, + String? color, + }) = _AnnotationEntity; +} diff --git a/lib/domain/entities/annotation.freezed.dart b/lib/domain/entities/annotation.freezed.dart new file mode 100644 index 0000000..d0fc6c1 --- /dev/null +++ b/lib/domain/entities/annotation.freezed.dart @@ -0,0 +1,295 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'annotation.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$AnnotationEntity { + String get id => throw _privateConstructorUsedError; + String get sessionId => throw _privateConstructorUsedError; + double get startTime => throw _privateConstructorUsedError; + double get endTime => throw _privateConstructorUsedError; + String? get name => throw _privateConstructorUsedError; + String? get description => throw _privateConstructorUsedError; + String? get color => throw _privateConstructorUsedError; + + /// Create a copy of AnnotationEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $AnnotationEntityCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AnnotationEntityCopyWith<$Res> { + factory $AnnotationEntityCopyWith( + AnnotationEntity value, + $Res Function(AnnotationEntity) then, + ) = _$AnnotationEntityCopyWithImpl<$Res, AnnotationEntity>; + @useResult + $Res call({ + String id, + String sessionId, + double startTime, + double endTime, + String? name, + String? description, + String? color, + }); +} + +/// @nodoc +class _$AnnotationEntityCopyWithImpl<$Res, $Val extends AnnotationEntity> + implements $AnnotationEntityCopyWith<$Res> { + _$AnnotationEntityCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of AnnotationEntity + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? sessionId = null, + Object? startTime = null, + Object? endTime = null, + Object? name = freezed, + Object? description = freezed, + Object? color = freezed, + }) { + return _then( + _value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + sessionId: null == sessionId + ? _value.sessionId + : sessionId // ignore: cast_nullable_to_non_nullable + as String, + startTime: null == startTime + ? _value.startTime + : startTime // ignore: cast_nullable_to_non_nullable + as double, + endTime: null == endTime + ? _value.endTime + : endTime // ignore: cast_nullable_to_non_nullable + as double, + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + description: freezed == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String?, + color: freezed == color + ? _value.color + : color // ignore: cast_nullable_to_non_nullable + as String?, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$AnnotationEntityImplCopyWith<$Res> + implements $AnnotationEntityCopyWith<$Res> { + factory _$$AnnotationEntityImplCopyWith( + _$AnnotationEntityImpl value, + $Res Function(_$AnnotationEntityImpl) then, + ) = __$$AnnotationEntityImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + String id, + String sessionId, + double startTime, + double endTime, + String? name, + String? description, + String? color, + }); +} + +/// @nodoc +class __$$AnnotationEntityImplCopyWithImpl<$Res> + extends _$AnnotationEntityCopyWithImpl<$Res, _$AnnotationEntityImpl> + implements _$$AnnotationEntityImplCopyWith<$Res> { + __$$AnnotationEntityImplCopyWithImpl( + _$AnnotationEntityImpl _value, + $Res Function(_$AnnotationEntityImpl) _then, + ) : super(_value, _then); + + /// Create a copy of AnnotationEntity + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? sessionId = null, + Object? startTime = null, + Object? endTime = null, + Object? name = freezed, + Object? description = freezed, + Object? color = freezed, + }) { + return _then( + _$AnnotationEntityImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + sessionId: null == sessionId + ? _value.sessionId + : sessionId // ignore: cast_nullable_to_non_nullable + as String, + startTime: null == startTime + ? _value.startTime + : startTime // ignore: cast_nullable_to_non_nullable + as double, + endTime: null == endTime + ? _value.endTime + : endTime // ignore: cast_nullable_to_non_nullable + as double, + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + description: freezed == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String?, + color: freezed == color + ? _value.color + : color // ignore: cast_nullable_to_non_nullable + as String?, + ), + ); + } +} + +/// @nodoc + +class _$AnnotationEntityImpl implements _AnnotationEntity { + const _$AnnotationEntityImpl({ + required this.id, + required this.sessionId, + required this.startTime, + required this.endTime, + this.name, + this.description, + this.color, + }); + + @override + final String id; + @override + final String sessionId; + @override + final double startTime; + @override + final double endTime; + @override + final String? name; + @override + final String? description; + @override + final String? color; + + @override + String toString() { + return 'AnnotationEntity(id: $id, sessionId: $sessionId, startTime: $startTime, endTime: $endTime, name: $name, description: $description, color: $color)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AnnotationEntityImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.sessionId, sessionId) || + other.sessionId == sessionId) && + (identical(other.startTime, startTime) || + other.startTime == startTime) && + (identical(other.endTime, endTime) || other.endTime == endTime) && + (identical(other.name, name) || other.name == name) && + (identical(other.description, description) || + other.description == description) && + (identical(other.color, color) || other.color == color)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + id, + sessionId, + startTime, + endTime, + name, + description, + color, + ); + + /// Create a copy of AnnotationEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$AnnotationEntityImplCopyWith<_$AnnotationEntityImpl> get copyWith => + __$$AnnotationEntityImplCopyWithImpl<_$AnnotationEntityImpl>( + this, + _$identity, + ); +} + +abstract class _AnnotationEntity implements AnnotationEntity { + const factory _AnnotationEntity({ + required final String id, + required final String sessionId, + required final double startTime, + required final double endTime, + final String? name, + final String? description, + final String? color, + }) = _$AnnotationEntityImpl; + + @override + String get id; + @override + String get sessionId; + @override + double get startTime; + @override + double get endTime; + @override + String? get name; + @override + String? get description; + @override + String? get color; + + /// Create a copy of AnnotationEntity + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$AnnotationEntityImplCopyWith<_$AnnotationEntityImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/domain/entities/chat_message.dart b/lib/domain/entities/chat_message.dart new file mode 100644 index 0000000..821ae37 --- /dev/null +++ b/lib/domain/entities/chat_message.dart @@ -0,0 +1,19 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'chat_message.freezed.dart'; + +@freezed +class ChatMessageEntity with _$ChatMessageEntity { + const factory ChatMessageEntity({ + required String id, + required String sessionId, + required String role, + required String content, + required String createdAt, + }) = _ChatMessageEntity; + + const ChatMessageEntity._(); + + bool get isUser => role == 'user'; + bool get isAssistant => role == 'assistant'; +} diff --git a/lib/domain/entities/chat_message.freezed.dart b/lib/domain/entities/chat_message.freezed.dart new file mode 100644 index 0000000..48038f1 --- /dev/null +++ b/lib/domain/entities/chat_message.freezed.dart @@ -0,0 +1,247 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'chat_message.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$ChatMessageEntity { + String get id => throw _privateConstructorUsedError; + String get sessionId => throw _privateConstructorUsedError; + String get role => throw _privateConstructorUsedError; + String get content => throw _privateConstructorUsedError; + String get createdAt => throw _privateConstructorUsedError; + + /// Create a copy of ChatMessageEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ChatMessageEntityCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ChatMessageEntityCopyWith<$Res> { + factory $ChatMessageEntityCopyWith( + ChatMessageEntity value, + $Res Function(ChatMessageEntity) then, + ) = _$ChatMessageEntityCopyWithImpl<$Res, ChatMessageEntity>; + @useResult + $Res call({ + String id, + String sessionId, + String role, + String content, + String createdAt, + }); +} + +/// @nodoc +class _$ChatMessageEntityCopyWithImpl<$Res, $Val extends ChatMessageEntity> + implements $ChatMessageEntityCopyWith<$Res> { + _$ChatMessageEntityCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ChatMessageEntity + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? sessionId = null, + Object? role = null, + Object? content = null, + Object? createdAt = null, + }) { + return _then( + _value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + sessionId: null == sessionId + ? _value.sessionId + : sessionId // ignore: cast_nullable_to_non_nullable + as String, + role: null == role + ? _value.role + : role // ignore: cast_nullable_to_non_nullable + as String, + content: null == content + ? _value.content + : content // ignore: cast_nullable_to_non_nullable + as String, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as String, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ChatMessageEntityImplCopyWith<$Res> + implements $ChatMessageEntityCopyWith<$Res> { + factory _$$ChatMessageEntityImplCopyWith( + _$ChatMessageEntityImpl value, + $Res Function(_$ChatMessageEntityImpl) then, + ) = __$$ChatMessageEntityImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + String id, + String sessionId, + String role, + String content, + String createdAt, + }); +} + +/// @nodoc +class __$$ChatMessageEntityImplCopyWithImpl<$Res> + extends _$ChatMessageEntityCopyWithImpl<$Res, _$ChatMessageEntityImpl> + implements _$$ChatMessageEntityImplCopyWith<$Res> { + __$$ChatMessageEntityImplCopyWithImpl( + _$ChatMessageEntityImpl _value, + $Res Function(_$ChatMessageEntityImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ChatMessageEntity + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? sessionId = null, + Object? role = null, + Object? content = null, + Object? createdAt = null, + }) { + return _then( + _$ChatMessageEntityImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + sessionId: null == sessionId + ? _value.sessionId + : sessionId // ignore: cast_nullable_to_non_nullable + as String, + role: null == role + ? _value.role + : role // ignore: cast_nullable_to_non_nullable + as String, + content: null == content + ? _value.content + : content // ignore: cast_nullable_to_non_nullable + as String, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$ChatMessageEntityImpl extends _ChatMessageEntity { + const _$ChatMessageEntityImpl({ + required this.id, + required this.sessionId, + required this.role, + required this.content, + required this.createdAt, + }) : super._(); + + @override + final String id; + @override + final String sessionId; + @override + final String role; + @override + final String content; + @override + final String createdAt; + + @override + String toString() { + return 'ChatMessageEntity(id: $id, sessionId: $sessionId, role: $role, content: $content, createdAt: $createdAt)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ChatMessageEntityImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.sessionId, sessionId) || + other.sessionId == sessionId) && + (identical(other.role, role) || other.role == role) && + (identical(other.content, content) || other.content == content) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt)); + } + + @override + int get hashCode => + Object.hash(runtimeType, id, sessionId, role, content, createdAt); + + /// Create a copy of ChatMessageEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ChatMessageEntityImplCopyWith<_$ChatMessageEntityImpl> get copyWith => + __$$ChatMessageEntityImplCopyWithImpl<_$ChatMessageEntityImpl>( + this, + _$identity, + ); +} + +abstract class _ChatMessageEntity extends ChatMessageEntity { + const factory _ChatMessageEntity({ + required final String id, + required final String sessionId, + required final String role, + required final String content, + required final String createdAt, + }) = _$ChatMessageEntityImpl; + const _ChatMessageEntity._() : super._(); + + @override + String get id; + @override + String get sessionId; + @override + String get role; + @override + String get content; + @override + String get createdAt; + + /// Create a copy of ChatMessageEntity + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ChatMessageEntityImplCopyWith<_$ChatMessageEntityImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/domain/entities/chat_session.dart b/lib/domain/entities/chat_session.dart new file mode 100644 index 0000000..107c7ed --- /dev/null +++ b/lib/domain/entities/chat_session.dart @@ -0,0 +1,13 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'chat_session.freezed.dart'; + +@freezed +class ChatSessionEntity with _$ChatSessionEntity { + const factory ChatSessionEntity({ + required String id, + String? title, + required String createdAt, + required String updatedAt, + }) = _ChatSessionEntity; +} diff --git a/lib/domain/entities/chat_session.freezed.dart b/lib/domain/entities/chat_session.freezed.dart new file mode 100644 index 0000000..546aba4 --- /dev/null +++ b/lib/domain/entities/chat_session.freezed.dart @@ -0,0 +1,215 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'chat_session.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$ChatSessionEntity { + String get id => throw _privateConstructorUsedError; + String? get title => throw _privateConstructorUsedError; + String get createdAt => throw _privateConstructorUsedError; + String get updatedAt => throw _privateConstructorUsedError; + + /// Create a copy of ChatSessionEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ChatSessionEntityCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ChatSessionEntityCopyWith<$Res> { + factory $ChatSessionEntityCopyWith( + ChatSessionEntity value, + $Res Function(ChatSessionEntity) then, + ) = _$ChatSessionEntityCopyWithImpl<$Res, ChatSessionEntity>; + @useResult + $Res call({String id, String? title, String createdAt, String updatedAt}); +} + +/// @nodoc +class _$ChatSessionEntityCopyWithImpl<$Res, $Val extends ChatSessionEntity> + implements $ChatSessionEntityCopyWith<$Res> { + _$ChatSessionEntityCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ChatSessionEntity + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? title = freezed, + Object? createdAt = null, + Object? updatedAt = null, + }) { + return _then( + _value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + title: freezed == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String?, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as String, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as String, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ChatSessionEntityImplCopyWith<$Res> + implements $ChatSessionEntityCopyWith<$Res> { + factory _$$ChatSessionEntityImplCopyWith( + _$ChatSessionEntityImpl value, + $Res Function(_$ChatSessionEntityImpl) then, + ) = __$$ChatSessionEntityImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String id, String? title, String createdAt, String updatedAt}); +} + +/// @nodoc +class __$$ChatSessionEntityImplCopyWithImpl<$Res> + extends _$ChatSessionEntityCopyWithImpl<$Res, _$ChatSessionEntityImpl> + implements _$$ChatSessionEntityImplCopyWith<$Res> { + __$$ChatSessionEntityImplCopyWithImpl( + _$ChatSessionEntityImpl _value, + $Res Function(_$ChatSessionEntityImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ChatSessionEntity + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? title = freezed, + Object? createdAt = null, + Object? updatedAt = null, + }) { + return _then( + _$ChatSessionEntityImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + title: freezed == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String?, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as String, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$ChatSessionEntityImpl implements _ChatSessionEntity { + const _$ChatSessionEntityImpl({ + required this.id, + this.title, + required this.createdAt, + required this.updatedAt, + }); + + @override + final String id; + @override + final String? title; + @override + final String createdAt; + @override + final String updatedAt; + + @override + String toString() { + return 'ChatSessionEntity(id: $id, title: $title, createdAt: $createdAt, updatedAt: $updatedAt)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ChatSessionEntityImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.title, title) || other.title == title) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt) && + (identical(other.updatedAt, updatedAt) || + other.updatedAt == updatedAt)); + } + + @override + int get hashCode => Object.hash(runtimeType, id, title, createdAt, updatedAt); + + /// Create a copy of ChatSessionEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ChatSessionEntityImplCopyWith<_$ChatSessionEntityImpl> get copyWith => + __$$ChatSessionEntityImplCopyWithImpl<_$ChatSessionEntityImpl>( + this, + _$identity, + ); +} + +abstract class _ChatSessionEntity implements ChatSessionEntity { + const factory _ChatSessionEntity({ + required final String id, + final String? title, + required final String createdAt, + required final String updatedAt, + }) = _$ChatSessionEntityImpl; + + @override + String get id; + @override + String? get title; + @override + String get createdAt; + @override + String get updatedAt; + + /// Create a copy of ChatSessionEntity + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ChatSessionEntityImplCopyWith<_$ChatSessionEntityImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/domain/entities/exercise.dart b/lib/domain/entities/exercise.dart new file mode 100644 index 0000000..60ce64c --- /dev/null +++ b/lib/domain/entities/exercise.dart @@ -0,0 +1,16 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'exercise.freezed.dart'; + +@freezed +class ExerciseEntity with _$ExerciseEntity { + const factory ExerciseEntity({ + required String id, + required String name, + String? instructions, + String? enrichment, + String? tags, + String? videoUrl, + String? muscleGroup, + }) = _ExerciseEntity; +} diff --git a/lib/domain/entities/exercise.freezed.dart b/lib/domain/entities/exercise.freezed.dart new file mode 100644 index 0000000..5545a3d --- /dev/null +++ b/lib/domain/entities/exercise.freezed.dart @@ -0,0 +1,296 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'exercise.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$ExerciseEntity { + String get id => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + String? get instructions => throw _privateConstructorUsedError; + String? get enrichment => throw _privateConstructorUsedError; + String? get tags => throw _privateConstructorUsedError; + String? get videoUrl => throw _privateConstructorUsedError; + String? get muscleGroup => throw _privateConstructorUsedError; + + /// Create a copy of ExerciseEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ExerciseEntityCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ExerciseEntityCopyWith<$Res> { + factory $ExerciseEntityCopyWith( + ExerciseEntity value, + $Res Function(ExerciseEntity) then, + ) = _$ExerciseEntityCopyWithImpl<$Res, ExerciseEntity>; + @useResult + $Res call({ + String id, + String name, + String? instructions, + String? enrichment, + String? tags, + String? videoUrl, + String? muscleGroup, + }); +} + +/// @nodoc +class _$ExerciseEntityCopyWithImpl<$Res, $Val extends ExerciseEntity> + implements $ExerciseEntityCopyWith<$Res> { + _$ExerciseEntityCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ExerciseEntity + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? name = null, + Object? instructions = freezed, + Object? enrichment = freezed, + Object? tags = freezed, + Object? videoUrl = freezed, + Object? muscleGroup = freezed, + }) { + return _then( + _value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + instructions: freezed == instructions + ? _value.instructions + : instructions // ignore: cast_nullable_to_non_nullable + as String?, + enrichment: freezed == enrichment + ? _value.enrichment + : enrichment // ignore: cast_nullable_to_non_nullable + as String?, + tags: freezed == tags + ? _value.tags + : tags // ignore: cast_nullable_to_non_nullable + as String?, + videoUrl: freezed == videoUrl + ? _value.videoUrl + : videoUrl // ignore: cast_nullable_to_non_nullable + as String?, + muscleGroup: freezed == muscleGroup + ? _value.muscleGroup + : muscleGroup // ignore: cast_nullable_to_non_nullable + as String?, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ExerciseEntityImplCopyWith<$Res> + implements $ExerciseEntityCopyWith<$Res> { + factory _$$ExerciseEntityImplCopyWith( + _$ExerciseEntityImpl value, + $Res Function(_$ExerciseEntityImpl) then, + ) = __$$ExerciseEntityImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + String id, + String name, + String? instructions, + String? enrichment, + String? tags, + String? videoUrl, + String? muscleGroup, + }); +} + +/// @nodoc +class __$$ExerciseEntityImplCopyWithImpl<$Res> + extends _$ExerciseEntityCopyWithImpl<$Res, _$ExerciseEntityImpl> + implements _$$ExerciseEntityImplCopyWith<$Res> { + __$$ExerciseEntityImplCopyWithImpl( + _$ExerciseEntityImpl _value, + $Res Function(_$ExerciseEntityImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ExerciseEntity + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? name = null, + Object? instructions = freezed, + Object? enrichment = freezed, + Object? tags = freezed, + Object? videoUrl = freezed, + Object? muscleGroup = freezed, + }) { + return _then( + _$ExerciseEntityImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + instructions: freezed == instructions + ? _value.instructions + : instructions // ignore: cast_nullable_to_non_nullable + as String?, + enrichment: freezed == enrichment + ? _value.enrichment + : enrichment // ignore: cast_nullable_to_non_nullable + as String?, + tags: freezed == tags + ? _value.tags + : tags // ignore: cast_nullable_to_non_nullable + as String?, + videoUrl: freezed == videoUrl + ? _value.videoUrl + : videoUrl // ignore: cast_nullable_to_non_nullable + as String?, + muscleGroup: freezed == muscleGroup + ? _value.muscleGroup + : muscleGroup // ignore: cast_nullable_to_non_nullable + as String?, + ), + ); + } +} + +/// @nodoc + +class _$ExerciseEntityImpl implements _ExerciseEntity { + const _$ExerciseEntityImpl({ + required this.id, + required this.name, + this.instructions, + this.enrichment, + this.tags, + this.videoUrl, + this.muscleGroup, + }); + + @override + final String id; + @override + final String name; + @override + final String? instructions; + @override + final String? enrichment; + @override + final String? tags; + @override + final String? videoUrl; + @override + final String? muscleGroup; + + @override + String toString() { + return 'ExerciseEntity(id: $id, name: $name, instructions: $instructions, enrichment: $enrichment, tags: $tags, videoUrl: $videoUrl, muscleGroup: $muscleGroup)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ExerciseEntityImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.name, name) || other.name == name) && + (identical(other.instructions, instructions) || + other.instructions == instructions) && + (identical(other.enrichment, enrichment) || + other.enrichment == enrichment) && + (identical(other.tags, tags) || other.tags == tags) && + (identical(other.videoUrl, videoUrl) || + other.videoUrl == videoUrl) && + (identical(other.muscleGroup, muscleGroup) || + other.muscleGroup == muscleGroup)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + id, + name, + instructions, + enrichment, + tags, + videoUrl, + muscleGroup, + ); + + /// Create a copy of ExerciseEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ExerciseEntityImplCopyWith<_$ExerciseEntityImpl> get copyWith => + __$$ExerciseEntityImplCopyWithImpl<_$ExerciseEntityImpl>( + this, + _$identity, + ); +} + +abstract class _ExerciseEntity implements ExerciseEntity { + const factory _ExerciseEntity({ + required final String id, + required final String name, + final String? instructions, + final String? enrichment, + final String? tags, + final String? videoUrl, + final String? muscleGroup, + }) = _$ExerciseEntityImpl; + + @override + String get id; + @override + String get name; + @override + String? get instructions; + @override + String? get enrichment; + @override + String? get tags; + @override + String? get videoUrl; + @override + String? get muscleGroup; + + /// Create a copy of ExerciseEntity + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ExerciseEntityImplCopyWith<_$ExerciseEntityImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/domain/entities/program.dart b/lib/domain/entities/program.dart new file mode 100644 index 0000000..d249ef6 --- /dev/null +++ b/lib/domain/entities/program.dart @@ -0,0 +1,12 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'program.freezed.dart'; + +@freezed +class ProgramEntity with _$ProgramEntity { + const factory ProgramEntity({ + required String id, + required String name, + required String createdAt, + }) = _ProgramEntity; +} diff --git a/lib/domain/entities/program.freezed.dart b/lib/domain/entities/program.freezed.dart new file mode 100644 index 0000000..4a46b0d --- /dev/null +++ b/lib/domain/entities/program.freezed.dart @@ -0,0 +1,193 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'program.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$ProgramEntity { + String get id => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + String get createdAt => throw _privateConstructorUsedError; + + /// Create a copy of ProgramEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProgramEntityCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProgramEntityCopyWith<$Res> { + factory $ProgramEntityCopyWith( + ProgramEntity value, + $Res Function(ProgramEntity) then, + ) = _$ProgramEntityCopyWithImpl<$Res, ProgramEntity>; + @useResult + $Res call({String id, String name, String createdAt}); +} + +/// @nodoc +class _$ProgramEntityCopyWithImpl<$Res, $Val extends ProgramEntity> + implements $ProgramEntityCopyWith<$Res> { + _$ProgramEntityCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProgramEntity + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? name = null, + Object? createdAt = null, + }) { + return _then( + _value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as String, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ProgramEntityImplCopyWith<$Res> + implements $ProgramEntityCopyWith<$Res> { + factory _$$ProgramEntityImplCopyWith( + _$ProgramEntityImpl value, + $Res Function(_$ProgramEntityImpl) then, + ) = __$$ProgramEntityImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String id, String name, String createdAt}); +} + +/// @nodoc +class __$$ProgramEntityImplCopyWithImpl<$Res> + extends _$ProgramEntityCopyWithImpl<$Res, _$ProgramEntityImpl> + implements _$$ProgramEntityImplCopyWith<$Res> { + __$$ProgramEntityImplCopyWithImpl( + _$ProgramEntityImpl _value, + $Res Function(_$ProgramEntityImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProgramEntity + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? name = null, + Object? createdAt = null, + }) { + return _then( + _$ProgramEntityImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$ProgramEntityImpl implements _ProgramEntity { + const _$ProgramEntityImpl({ + required this.id, + required this.name, + required this.createdAt, + }); + + @override + final String id; + @override + final String name; + @override + final String createdAt; + + @override + String toString() { + return 'ProgramEntity(id: $id, name: $name, createdAt: $createdAt)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProgramEntityImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.name, name) || other.name == name) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt)); + } + + @override + int get hashCode => Object.hash(runtimeType, id, name, createdAt); + + /// Create a copy of ProgramEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ProgramEntityImplCopyWith<_$ProgramEntityImpl> get copyWith => + __$$ProgramEntityImplCopyWithImpl<_$ProgramEntityImpl>(this, _$identity); +} + +abstract class _ProgramEntity implements ProgramEntity { + const factory _ProgramEntity({ + required final String id, + required final String name, + required final String createdAt, + }) = _$ProgramEntityImpl; + + @override + String get id; + @override + String get name; + @override + String get createdAt; + + /// Create a copy of ProgramEntity + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ProgramEntityImplCopyWith<_$ProgramEntityImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/domain/entities/program_week.dart b/lib/domain/entities/program_week.dart new file mode 100644 index 0000000..bee7d93 --- /dev/null +++ b/lib/domain/entities/program_week.dart @@ -0,0 +1,13 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'program_week.freezed.dart'; + +@freezed +class ProgramWeekEntity with _$ProgramWeekEntity { + const factory ProgramWeekEntity({ + required String id, + required String programId, + required int position, + String? notes, + }) = _ProgramWeekEntity; +} diff --git a/lib/domain/entities/program_week.freezed.dart b/lib/domain/entities/program_week.freezed.dart new file mode 100644 index 0000000..82cf4a0 --- /dev/null +++ b/lib/domain/entities/program_week.freezed.dart @@ -0,0 +1,215 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'program_week.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$ProgramWeekEntity { + String get id => throw _privateConstructorUsedError; + String get programId => throw _privateConstructorUsedError; + int get position => throw _privateConstructorUsedError; + String? get notes => throw _privateConstructorUsedError; + + /// Create a copy of ProgramWeekEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProgramWeekEntityCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProgramWeekEntityCopyWith<$Res> { + factory $ProgramWeekEntityCopyWith( + ProgramWeekEntity value, + $Res Function(ProgramWeekEntity) then, + ) = _$ProgramWeekEntityCopyWithImpl<$Res, ProgramWeekEntity>; + @useResult + $Res call({String id, String programId, int position, String? notes}); +} + +/// @nodoc +class _$ProgramWeekEntityCopyWithImpl<$Res, $Val extends ProgramWeekEntity> + implements $ProgramWeekEntityCopyWith<$Res> { + _$ProgramWeekEntityCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProgramWeekEntity + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? programId = null, + Object? position = null, + Object? notes = freezed, + }) { + return _then( + _value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + programId: null == programId + ? _value.programId + : programId // ignore: cast_nullable_to_non_nullable + as String, + position: null == position + ? _value.position + : position // ignore: cast_nullable_to_non_nullable + as int, + notes: freezed == notes + ? _value.notes + : notes // ignore: cast_nullable_to_non_nullable + as String?, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ProgramWeekEntityImplCopyWith<$Res> + implements $ProgramWeekEntityCopyWith<$Res> { + factory _$$ProgramWeekEntityImplCopyWith( + _$ProgramWeekEntityImpl value, + $Res Function(_$ProgramWeekEntityImpl) then, + ) = __$$ProgramWeekEntityImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String id, String programId, int position, String? notes}); +} + +/// @nodoc +class __$$ProgramWeekEntityImplCopyWithImpl<$Res> + extends _$ProgramWeekEntityCopyWithImpl<$Res, _$ProgramWeekEntityImpl> + implements _$$ProgramWeekEntityImplCopyWith<$Res> { + __$$ProgramWeekEntityImplCopyWithImpl( + _$ProgramWeekEntityImpl _value, + $Res Function(_$ProgramWeekEntityImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProgramWeekEntity + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? programId = null, + Object? position = null, + Object? notes = freezed, + }) { + return _then( + _$ProgramWeekEntityImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + programId: null == programId + ? _value.programId + : programId // ignore: cast_nullable_to_non_nullable + as String, + position: null == position + ? _value.position + : position // ignore: cast_nullable_to_non_nullable + as int, + notes: freezed == notes + ? _value.notes + : notes // ignore: cast_nullable_to_non_nullable + as String?, + ), + ); + } +} + +/// @nodoc + +class _$ProgramWeekEntityImpl implements _ProgramWeekEntity { + const _$ProgramWeekEntityImpl({ + required this.id, + required this.programId, + required this.position, + this.notes, + }); + + @override + final String id; + @override + final String programId; + @override + final int position; + @override + final String? notes; + + @override + String toString() { + return 'ProgramWeekEntity(id: $id, programId: $programId, position: $position, notes: $notes)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProgramWeekEntityImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.programId, programId) || + other.programId == programId) && + (identical(other.position, position) || + other.position == position) && + (identical(other.notes, notes) || other.notes == notes)); + } + + @override + int get hashCode => Object.hash(runtimeType, id, programId, position, notes); + + /// Create a copy of ProgramWeekEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ProgramWeekEntityImplCopyWith<_$ProgramWeekEntityImpl> get copyWith => + __$$ProgramWeekEntityImplCopyWithImpl<_$ProgramWeekEntityImpl>( + this, + _$identity, + ); +} + +abstract class _ProgramWeekEntity implements ProgramWeekEntity { + const factory _ProgramWeekEntity({ + required final String id, + required final String programId, + required final int position, + final String? notes, + }) = _$ProgramWeekEntityImpl; + + @override + String get id; + @override + String get programId; + @override + int get position; + @override + String? get notes; + + /// Create a copy of ProgramWeekEntity + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ProgramWeekEntityImplCopyWith<_$ProgramWeekEntityImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/domain/entities/program_workout.dart b/lib/domain/entities/program_workout.dart new file mode 100644 index 0000000..ec44412 --- /dev/null +++ b/lib/domain/entities/program_workout.dart @@ -0,0 +1,18 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'program_workout.freezed.dart'; + +@freezed +class ProgramWorkoutEntity with _$ProgramWorkoutEntity { + const factory ProgramWorkoutEntity({ + required String id, + required String weekId, + required String programId, + required String day, + required String type, + String? refId, + String? name, + String? description, + @Default(false) bool completed, + }) = _ProgramWorkoutEntity; +} diff --git a/lib/domain/entities/program_workout.freezed.dart b/lib/domain/entities/program_workout.freezed.dart new file mode 100644 index 0000000..c083353 --- /dev/null +++ b/lib/domain/entities/program_workout.freezed.dart @@ -0,0 +1,342 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'program_workout.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$ProgramWorkoutEntity { + String get id => throw _privateConstructorUsedError; + String get weekId => throw _privateConstructorUsedError; + String get programId => throw _privateConstructorUsedError; + String get day => throw _privateConstructorUsedError; + String get type => throw _privateConstructorUsedError; + String? get refId => throw _privateConstructorUsedError; + String? get name => throw _privateConstructorUsedError; + String? get description => throw _privateConstructorUsedError; + bool get completed => throw _privateConstructorUsedError; + + /// Create a copy of ProgramWorkoutEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProgramWorkoutEntityCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProgramWorkoutEntityCopyWith<$Res> { + factory $ProgramWorkoutEntityCopyWith( + ProgramWorkoutEntity value, + $Res Function(ProgramWorkoutEntity) then, + ) = _$ProgramWorkoutEntityCopyWithImpl<$Res, ProgramWorkoutEntity>; + @useResult + $Res call({ + String id, + String weekId, + String programId, + String day, + String type, + String? refId, + String? name, + String? description, + bool completed, + }); +} + +/// @nodoc +class _$ProgramWorkoutEntityCopyWithImpl< + $Res, + $Val extends ProgramWorkoutEntity +> + implements $ProgramWorkoutEntityCopyWith<$Res> { + _$ProgramWorkoutEntityCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProgramWorkoutEntity + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? weekId = null, + Object? programId = null, + Object? day = null, + Object? type = null, + Object? refId = freezed, + Object? name = freezed, + Object? description = freezed, + Object? completed = null, + }) { + return _then( + _value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + weekId: null == weekId + ? _value.weekId + : weekId // ignore: cast_nullable_to_non_nullable + as String, + programId: null == programId + ? _value.programId + : programId // ignore: cast_nullable_to_non_nullable + as String, + day: null == day + ? _value.day + : day // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + refId: freezed == refId + ? _value.refId + : refId // ignore: cast_nullable_to_non_nullable + as String?, + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + description: freezed == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String?, + completed: null == completed + ? _value.completed + : completed // ignore: cast_nullable_to_non_nullable + as bool, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ProgramWorkoutEntityImplCopyWith<$Res> + implements $ProgramWorkoutEntityCopyWith<$Res> { + factory _$$ProgramWorkoutEntityImplCopyWith( + _$ProgramWorkoutEntityImpl value, + $Res Function(_$ProgramWorkoutEntityImpl) then, + ) = __$$ProgramWorkoutEntityImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + String id, + String weekId, + String programId, + String day, + String type, + String? refId, + String? name, + String? description, + bool completed, + }); +} + +/// @nodoc +class __$$ProgramWorkoutEntityImplCopyWithImpl<$Res> + extends _$ProgramWorkoutEntityCopyWithImpl<$Res, _$ProgramWorkoutEntityImpl> + implements _$$ProgramWorkoutEntityImplCopyWith<$Res> { + __$$ProgramWorkoutEntityImplCopyWithImpl( + _$ProgramWorkoutEntityImpl _value, + $Res Function(_$ProgramWorkoutEntityImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProgramWorkoutEntity + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? weekId = null, + Object? programId = null, + Object? day = null, + Object? type = null, + Object? refId = freezed, + Object? name = freezed, + Object? description = freezed, + Object? completed = null, + }) { + return _then( + _$ProgramWorkoutEntityImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + weekId: null == weekId + ? _value.weekId + : weekId // ignore: cast_nullable_to_non_nullable + as String, + programId: null == programId + ? _value.programId + : programId // ignore: cast_nullable_to_non_nullable + as String, + day: null == day + ? _value.day + : day // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + refId: freezed == refId + ? _value.refId + : refId // ignore: cast_nullable_to_non_nullable + as String?, + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + description: freezed == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String?, + completed: null == completed + ? _value.completed + : completed // ignore: cast_nullable_to_non_nullable + as bool, + ), + ); + } +} + +/// @nodoc + +class _$ProgramWorkoutEntityImpl implements _ProgramWorkoutEntity { + const _$ProgramWorkoutEntityImpl({ + required this.id, + required this.weekId, + required this.programId, + required this.day, + required this.type, + this.refId, + this.name, + this.description, + this.completed = false, + }); + + @override + final String id; + @override + final String weekId; + @override + final String programId; + @override + final String day; + @override + final String type; + @override + final String? refId; + @override + final String? name; + @override + final String? description; + @override + @JsonKey() + final bool completed; + + @override + String toString() { + return 'ProgramWorkoutEntity(id: $id, weekId: $weekId, programId: $programId, day: $day, type: $type, refId: $refId, name: $name, description: $description, completed: $completed)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProgramWorkoutEntityImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.weekId, weekId) || other.weekId == weekId) && + (identical(other.programId, programId) || + other.programId == programId) && + (identical(other.day, day) || other.day == day) && + (identical(other.type, type) || other.type == type) && + (identical(other.refId, refId) || other.refId == refId) && + (identical(other.name, name) || other.name == name) && + (identical(other.description, description) || + other.description == description) && + (identical(other.completed, completed) || + other.completed == completed)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + id, + weekId, + programId, + day, + type, + refId, + name, + description, + completed, + ); + + /// Create a copy of ProgramWorkoutEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ProgramWorkoutEntityImplCopyWith<_$ProgramWorkoutEntityImpl> + get copyWith => + __$$ProgramWorkoutEntityImplCopyWithImpl<_$ProgramWorkoutEntityImpl>( + this, + _$identity, + ); +} + +abstract class _ProgramWorkoutEntity implements ProgramWorkoutEntity { + const factory _ProgramWorkoutEntity({ + required final String id, + required final String weekId, + required final String programId, + required final String day, + required final String type, + final String? refId, + final String? name, + final String? description, + final bool completed, + }) = _$ProgramWorkoutEntityImpl; + + @override + String get id; + @override + String get weekId; + @override + String get programId; + @override + String get day; + @override + String get type; + @override + String? get refId; + @override + String? get name; + @override + String? get description; + @override + bool get completed; + + /// Create a copy of ProgramWorkoutEntity + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ProgramWorkoutEntityImplCopyWith<_$ProgramWorkoutEntityImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/domain/entities/training_exercise.dart b/lib/domain/entities/training_exercise.dart new file mode 100644 index 0000000..0db6799 --- /dev/null +++ b/lib/domain/entities/training_exercise.dart @@ -0,0 +1,16 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'training_exercise.freezed.dart'; + +@freezed +class TrainingExerciseEntity with _$TrainingExerciseEntity { + const factory TrainingExerciseEntity({ + required String instanceId, + required String exerciseId, + required String name, + @Default(3) int sets, + @Default(10) int value, + @Default(false) bool isTime, + @Default(60) int rest, + }) = _TrainingExerciseEntity; +} diff --git a/lib/domain/entities/training_exercise.freezed.dart b/lib/domain/entities/training_exercise.freezed.dart new file mode 100644 index 0000000..616a21c --- /dev/null +++ b/lib/domain/entities/training_exercise.freezed.dart @@ -0,0 +1,303 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'training_exercise.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$TrainingExerciseEntity { + String get instanceId => throw _privateConstructorUsedError; + String get exerciseId => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + int get sets => throw _privateConstructorUsedError; + int get value => throw _privateConstructorUsedError; + bool get isTime => throw _privateConstructorUsedError; + int get rest => throw _privateConstructorUsedError; + + /// Create a copy of TrainingExerciseEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $TrainingExerciseEntityCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $TrainingExerciseEntityCopyWith<$Res> { + factory $TrainingExerciseEntityCopyWith( + TrainingExerciseEntity value, + $Res Function(TrainingExerciseEntity) then, + ) = _$TrainingExerciseEntityCopyWithImpl<$Res, TrainingExerciseEntity>; + @useResult + $Res call({ + String instanceId, + String exerciseId, + String name, + int sets, + int value, + bool isTime, + int rest, + }); +} + +/// @nodoc +class _$TrainingExerciseEntityCopyWithImpl< + $Res, + $Val extends TrainingExerciseEntity +> + implements $TrainingExerciseEntityCopyWith<$Res> { + _$TrainingExerciseEntityCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of TrainingExerciseEntity + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? instanceId = null, + Object? exerciseId = null, + Object? name = null, + Object? sets = null, + Object? value = null, + Object? isTime = null, + Object? rest = null, + }) { + return _then( + _value.copyWith( + instanceId: null == instanceId + ? _value.instanceId + : instanceId // ignore: cast_nullable_to_non_nullable + as String, + exerciseId: null == exerciseId + ? _value.exerciseId + : exerciseId // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + sets: null == sets + ? _value.sets + : sets // ignore: cast_nullable_to_non_nullable + as int, + value: null == value + ? _value.value + : value // ignore: cast_nullable_to_non_nullable + as int, + isTime: null == isTime + ? _value.isTime + : isTime // ignore: cast_nullable_to_non_nullable + as bool, + rest: null == rest + ? _value.rest + : rest // ignore: cast_nullable_to_non_nullable + as int, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$TrainingExerciseEntityImplCopyWith<$Res> + implements $TrainingExerciseEntityCopyWith<$Res> { + factory _$$TrainingExerciseEntityImplCopyWith( + _$TrainingExerciseEntityImpl value, + $Res Function(_$TrainingExerciseEntityImpl) then, + ) = __$$TrainingExerciseEntityImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + String instanceId, + String exerciseId, + String name, + int sets, + int value, + bool isTime, + int rest, + }); +} + +/// @nodoc +class __$$TrainingExerciseEntityImplCopyWithImpl<$Res> + extends + _$TrainingExerciseEntityCopyWithImpl<$Res, _$TrainingExerciseEntityImpl> + implements _$$TrainingExerciseEntityImplCopyWith<$Res> { + __$$TrainingExerciseEntityImplCopyWithImpl( + _$TrainingExerciseEntityImpl _value, + $Res Function(_$TrainingExerciseEntityImpl) _then, + ) : super(_value, _then); + + /// Create a copy of TrainingExerciseEntity + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? instanceId = null, + Object? exerciseId = null, + Object? name = null, + Object? sets = null, + Object? value = null, + Object? isTime = null, + Object? rest = null, + }) { + return _then( + _$TrainingExerciseEntityImpl( + instanceId: null == instanceId + ? _value.instanceId + : instanceId // ignore: cast_nullable_to_non_nullable + as String, + exerciseId: null == exerciseId + ? _value.exerciseId + : exerciseId // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + sets: null == sets + ? _value.sets + : sets // ignore: cast_nullable_to_non_nullable + as int, + value: null == value + ? _value.value + : value // ignore: cast_nullable_to_non_nullable + as int, + isTime: null == isTime + ? _value.isTime + : isTime // ignore: cast_nullable_to_non_nullable + as bool, + rest: null == rest + ? _value.rest + : rest // ignore: cast_nullable_to_non_nullable + as int, + ), + ); + } +} + +/// @nodoc + +class _$TrainingExerciseEntityImpl implements _TrainingExerciseEntity { + const _$TrainingExerciseEntityImpl({ + required this.instanceId, + required this.exerciseId, + required this.name, + this.sets = 3, + this.value = 10, + this.isTime = false, + this.rest = 60, + }); + + @override + final String instanceId; + @override + final String exerciseId; + @override + final String name; + @override + @JsonKey() + final int sets; + @override + @JsonKey() + final int value; + @override + @JsonKey() + final bool isTime; + @override + @JsonKey() + final int rest; + + @override + String toString() { + return 'TrainingExerciseEntity(instanceId: $instanceId, exerciseId: $exerciseId, name: $name, sets: $sets, value: $value, isTime: $isTime, rest: $rest)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TrainingExerciseEntityImpl && + (identical(other.instanceId, instanceId) || + other.instanceId == instanceId) && + (identical(other.exerciseId, exerciseId) || + other.exerciseId == exerciseId) && + (identical(other.name, name) || other.name == name) && + (identical(other.sets, sets) || other.sets == sets) && + (identical(other.value, value) || other.value == value) && + (identical(other.isTime, isTime) || other.isTime == isTime) && + (identical(other.rest, rest) || other.rest == rest)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + instanceId, + exerciseId, + name, + sets, + value, + isTime, + rest, + ); + + /// Create a copy of TrainingExerciseEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$TrainingExerciseEntityImplCopyWith<_$TrainingExerciseEntityImpl> + get copyWith => + __$$TrainingExerciseEntityImplCopyWithImpl<_$TrainingExerciseEntityImpl>( + this, + _$identity, + ); +} + +abstract class _TrainingExerciseEntity implements TrainingExerciseEntity { + const factory _TrainingExerciseEntity({ + required final String instanceId, + required final String exerciseId, + required final String name, + final int sets, + final int value, + final bool isTime, + final int rest, + }) = _$TrainingExerciseEntityImpl; + + @override + String get instanceId; + @override + String get exerciseId; + @override + String get name; + @override + int get sets; + @override + int get value; + @override + bool get isTime; + @override + int get rest; + + /// Create a copy of TrainingExerciseEntity + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$TrainingExerciseEntityImplCopyWith<_$TrainingExerciseEntityImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/domain/entities/training_plan.dart b/lib/domain/entities/training_plan.dart new file mode 100644 index 0000000..d4c8d10 --- /dev/null +++ b/lib/domain/entities/training_plan.dart @@ -0,0 +1,18 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:trainhub_flutter/domain/entities/training_section.dart'; + +part 'training_plan.freezed.dart'; + +@freezed +class TrainingPlanEntity with _$TrainingPlanEntity { + const factory TrainingPlanEntity({ + required String id, + required String name, + @Default([]) List sections, + }) = _TrainingPlanEntity; + + const TrainingPlanEntity._(); + + int get totalExercises => + sections.fold(0, (sum, s) => sum + s.exercises.length); +} diff --git a/lib/domain/entities/training_plan.freezed.dart b/lib/domain/entities/training_plan.freezed.dart new file mode 100644 index 0000000..ffd033c --- /dev/null +++ b/lib/domain/entities/training_plan.freezed.dart @@ -0,0 +1,201 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'training_plan.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$TrainingPlanEntity { + String get id => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + List get sections => + throw _privateConstructorUsedError; + + /// Create a copy of TrainingPlanEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $TrainingPlanEntityCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $TrainingPlanEntityCopyWith<$Res> { + factory $TrainingPlanEntityCopyWith( + TrainingPlanEntity value, + $Res Function(TrainingPlanEntity) then, + ) = _$TrainingPlanEntityCopyWithImpl<$Res, TrainingPlanEntity>; + @useResult + $Res call({String id, String name, List sections}); +} + +/// @nodoc +class _$TrainingPlanEntityCopyWithImpl<$Res, $Val extends TrainingPlanEntity> + implements $TrainingPlanEntityCopyWith<$Res> { + _$TrainingPlanEntityCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of TrainingPlanEntity + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? id = null, Object? name = null, Object? sections = null}) { + return _then( + _value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + sections: null == sections + ? _value.sections + : sections // ignore: cast_nullable_to_non_nullable + as List, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$TrainingPlanEntityImplCopyWith<$Res> + implements $TrainingPlanEntityCopyWith<$Res> { + factory _$$TrainingPlanEntityImplCopyWith( + _$TrainingPlanEntityImpl value, + $Res Function(_$TrainingPlanEntityImpl) then, + ) = __$$TrainingPlanEntityImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String id, String name, List sections}); +} + +/// @nodoc +class __$$TrainingPlanEntityImplCopyWithImpl<$Res> + extends _$TrainingPlanEntityCopyWithImpl<$Res, _$TrainingPlanEntityImpl> + implements _$$TrainingPlanEntityImplCopyWith<$Res> { + __$$TrainingPlanEntityImplCopyWithImpl( + _$TrainingPlanEntityImpl _value, + $Res Function(_$TrainingPlanEntityImpl) _then, + ) : super(_value, _then); + + /// Create a copy of TrainingPlanEntity + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? id = null, Object? name = null, Object? sections = null}) { + return _then( + _$TrainingPlanEntityImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + sections: null == sections + ? _value._sections + : sections // ignore: cast_nullable_to_non_nullable + as List, + ), + ); + } +} + +/// @nodoc + +class _$TrainingPlanEntityImpl extends _TrainingPlanEntity { + const _$TrainingPlanEntityImpl({ + required this.id, + required this.name, + final List sections = const [], + }) : _sections = sections, + super._(); + + @override + final String id; + @override + final String name; + final List _sections; + @override + @JsonKey() + List get sections { + if (_sections is EqualUnmodifiableListView) return _sections; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_sections); + } + + @override + String toString() { + return 'TrainingPlanEntity(id: $id, name: $name, sections: $sections)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TrainingPlanEntityImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.name, name) || other.name == name) && + const DeepCollectionEquality().equals(other._sections, _sections)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + id, + name, + const DeepCollectionEquality().hash(_sections), + ); + + /// Create a copy of TrainingPlanEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$TrainingPlanEntityImplCopyWith<_$TrainingPlanEntityImpl> get copyWith => + __$$TrainingPlanEntityImplCopyWithImpl<_$TrainingPlanEntityImpl>( + this, + _$identity, + ); +} + +abstract class _TrainingPlanEntity extends TrainingPlanEntity { + const factory _TrainingPlanEntity({ + required final String id, + required final String name, + final List sections, + }) = _$TrainingPlanEntityImpl; + const _TrainingPlanEntity._() : super._(); + + @override + String get id; + @override + String get name; + @override + List get sections; + + /// Create a copy of TrainingPlanEntity + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$TrainingPlanEntityImplCopyWith<_$TrainingPlanEntityImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/domain/entities/training_section.dart b/lib/domain/entities/training_section.dart new file mode 100644 index 0000000..ad8f565 --- /dev/null +++ b/lib/domain/entities/training_section.dart @@ -0,0 +1,13 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:trainhub_flutter/domain/entities/training_exercise.dart'; + +part 'training_section.freezed.dart'; + +@freezed +class TrainingSectionEntity with _$TrainingSectionEntity { + const factory TrainingSectionEntity({ + required String id, + required String name, + @Default([]) List exercises, + }) = _TrainingSectionEntity; +} diff --git a/lib/domain/entities/training_section.freezed.dart b/lib/domain/entities/training_section.freezed.dart new file mode 100644 index 0000000..1972781 --- /dev/null +++ b/lib/domain/entities/training_section.freezed.dart @@ -0,0 +1,215 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'training_section.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$TrainingSectionEntity { + String get id => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + List get exercises => + throw _privateConstructorUsedError; + + /// Create a copy of TrainingSectionEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $TrainingSectionEntityCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $TrainingSectionEntityCopyWith<$Res> { + factory $TrainingSectionEntityCopyWith( + TrainingSectionEntity value, + $Res Function(TrainingSectionEntity) then, + ) = _$TrainingSectionEntityCopyWithImpl<$Res, TrainingSectionEntity>; + @useResult + $Res call({String id, String name, List exercises}); +} + +/// @nodoc +class _$TrainingSectionEntityCopyWithImpl< + $Res, + $Val extends TrainingSectionEntity +> + implements $TrainingSectionEntityCopyWith<$Res> { + _$TrainingSectionEntityCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of TrainingSectionEntity + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? name = null, + Object? exercises = null, + }) { + return _then( + _value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + exercises: null == exercises + ? _value.exercises + : exercises // ignore: cast_nullable_to_non_nullable + as List, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$TrainingSectionEntityImplCopyWith<$Res> + implements $TrainingSectionEntityCopyWith<$Res> { + factory _$$TrainingSectionEntityImplCopyWith( + _$TrainingSectionEntityImpl value, + $Res Function(_$TrainingSectionEntityImpl) then, + ) = __$$TrainingSectionEntityImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String id, String name, List exercises}); +} + +/// @nodoc +class __$$TrainingSectionEntityImplCopyWithImpl<$Res> + extends + _$TrainingSectionEntityCopyWithImpl<$Res, _$TrainingSectionEntityImpl> + implements _$$TrainingSectionEntityImplCopyWith<$Res> { + __$$TrainingSectionEntityImplCopyWithImpl( + _$TrainingSectionEntityImpl _value, + $Res Function(_$TrainingSectionEntityImpl) _then, + ) : super(_value, _then); + + /// Create a copy of TrainingSectionEntity + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? name = null, + Object? exercises = null, + }) { + return _then( + _$TrainingSectionEntityImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + exercises: null == exercises + ? _value._exercises + : exercises // ignore: cast_nullable_to_non_nullable + as List, + ), + ); + } +} + +/// @nodoc + +class _$TrainingSectionEntityImpl implements _TrainingSectionEntity { + const _$TrainingSectionEntityImpl({ + required this.id, + required this.name, + final List exercises = const [], + }) : _exercises = exercises; + + @override + final String id; + @override + final String name; + final List _exercises; + @override + @JsonKey() + List get exercises { + if (_exercises is EqualUnmodifiableListView) return _exercises; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_exercises); + } + + @override + String toString() { + return 'TrainingSectionEntity(id: $id, name: $name, exercises: $exercises)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TrainingSectionEntityImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.name, name) || other.name == name) && + const DeepCollectionEquality().equals( + other._exercises, + _exercises, + )); + } + + @override + int get hashCode => Object.hash( + runtimeType, + id, + name, + const DeepCollectionEquality().hash(_exercises), + ); + + /// Create a copy of TrainingSectionEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$TrainingSectionEntityImplCopyWith<_$TrainingSectionEntityImpl> + get copyWith => + __$$TrainingSectionEntityImplCopyWithImpl<_$TrainingSectionEntityImpl>( + this, + _$identity, + ); +} + +abstract class _TrainingSectionEntity implements TrainingSectionEntity { + const factory _TrainingSectionEntity({ + required final String id, + required final String name, + final List exercises, + }) = _$TrainingSectionEntityImpl; + + @override + String get id; + @override + String get name; + @override + List get exercises; + + /// Create a copy of TrainingSectionEntity + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$TrainingSectionEntityImplCopyWith<_$TrainingSectionEntityImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/domain/entities/workout_activity.dart b/lib/domain/entities/workout_activity.dart new file mode 100644 index 0000000..858fad4 --- /dev/null +++ b/lib/domain/entities/workout_activity.dart @@ -0,0 +1,24 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:trainhub_flutter/domain/entities/training_exercise.dart'; + +part 'workout_activity.freezed.dart'; + +@freezed +class WorkoutActivityEntity with _$WorkoutActivityEntity { + const factory WorkoutActivityEntity({ + required String id, + required String name, + required String type, + required int duration, + TrainingExerciseEntity? originalExercise, + String? sectionName, + int? setIndex, + int? totalSets, + }) = _WorkoutActivityEntity; + + const WorkoutActivityEntity._(); + + bool get isRest => type == 'rest'; + bool get isWork => type == 'work'; + bool get isTimeBased => duration > 0; +} diff --git a/lib/domain/entities/workout_activity.freezed.dart b/lib/domain/entities/workout_activity.freezed.dart new file mode 100644 index 0000000..394c2e5 --- /dev/null +++ b/lib/domain/entities/workout_activity.freezed.dart @@ -0,0 +1,346 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'workout_activity.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$WorkoutActivityEntity { + String get id => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + String get type => throw _privateConstructorUsedError; + int get duration => throw _privateConstructorUsedError; + TrainingExerciseEntity? get originalExercise => + throw _privateConstructorUsedError; + String? get sectionName => throw _privateConstructorUsedError; + int? get setIndex => throw _privateConstructorUsedError; + int? get totalSets => throw _privateConstructorUsedError; + + /// Create a copy of WorkoutActivityEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $WorkoutActivityEntityCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $WorkoutActivityEntityCopyWith<$Res> { + factory $WorkoutActivityEntityCopyWith( + WorkoutActivityEntity value, + $Res Function(WorkoutActivityEntity) then, + ) = _$WorkoutActivityEntityCopyWithImpl<$Res, WorkoutActivityEntity>; + @useResult + $Res call({ + String id, + String name, + String type, + int duration, + TrainingExerciseEntity? originalExercise, + String? sectionName, + int? setIndex, + int? totalSets, + }); + + $TrainingExerciseEntityCopyWith<$Res>? get originalExercise; +} + +/// @nodoc +class _$WorkoutActivityEntityCopyWithImpl< + $Res, + $Val extends WorkoutActivityEntity +> + implements $WorkoutActivityEntityCopyWith<$Res> { + _$WorkoutActivityEntityCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of WorkoutActivityEntity + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? name = null, + Object? type = null, + Object? duration = null, + Object? originalExercise = freezed, + Object? sectionName = freezed, + Object? setIndex = freezed, + Object? totalSets = freezed, + }) { + return _then( + _value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + duration: null == duration + ? _value.duration + : duration // ignore: cast_nullable_to_non_nullable + as int, + originalExercise: freezed == originalExercise + ? _value.originalExercise + : originalExercise // ignore: cast_nullable_to_non_nullable + as TrainingExerciseEntity?, + sectionName: freezed == sectionName + ? _value.sectionName + : sectionName // ignore: cast_nullable_to_non_nullable + as String?, + setIndex: freezed == setIndex + ? _value.setIndex + : setIndex // ignore: cast_nullable_to_non_nullable + as int?, + totalSets: freezed == totalSets + ? _value.totalSets + : totalSets // ignore: cast_nullable_to_non_nullable + as int?, + ) + as $Val, + ); + } + + /// Create a copy of WorkoutActivityEntity + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $TrainingExerciseEntityCopyWith<$Res>? get originalExercise { + if (_value.originalExercise == null) { + return null; + } + + return $TrainingExerciseEntityCopyWith<$Res>(_value.originalExercise!, ( + value, + ) { + return _then(_value.copyWith(originalExercise: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$WorkoutActivityEntityImplCopyWith<$Res> + implements $WorkoutActivityEntityCopyWith<$Res> { + factory _$$WorkoutActivityEntityImplCopyWith( + _$WorkoutActivityEntityImpl value, + $Res Function(_$WorkoutActivityEntityImpl) then, + ) = __$$WorkoutActivityEntityImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + String id, + String name, + String type, + int duration, + TrainingExerciseEntity? originalExercise, + String? sectionName, + int? setIndex, + int? totalSets, + }); + + @override + $TrainingExerciseEntityCopyWith<$Res>? get originalExercise; +} + +/// @nodoc +class __$$WorkoutActivityEntityImplCopyWithImpl<$Res> + extends + _$WorkoutActivityEntityCopyWithImpl<$Res, _$WorkoutActivityEntityImpl> + implements _$$WorkoutActivityEntityImplCopyWith<$Res> { + __$$WorkoutActivityEntityImplCopyWithImpl( + _$WorkoutActivityEntityImpl _value, + $Res Function(_$WorkoutActivityEntityImpl) _then, + ) : super(_value, _then); + + /// Create a copy of WorkoutActivityEntity + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? name = null, + Object? type = null, + Object? duration = null, + Object? originalExercise = freezed, + Object? sectionName = freezed, + Object? setIndex = freezed, + Object? totalSets = freezed, + }) { + return _then( + _$WorkoutActivityEntityImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + duration: null == duration + ? _value.duration + : duration // ignore: cast_nullable_to_non_nullable + as int, + originalExercise: freezed == originalExercise + ? _value.originalExercise + : originalExercise // ignore: cast_nullable_to_non_nullable + as TrainingExerciseEntity?, + sectionName: freezed == sectionName + ? _value.sectionName + : sectionName // ignore: cast_nullable_to_non_nullable + as String?, + setIndex: freezed == setIndex + ? _value.setIndex + : setIndex // ignore: cast_nullable_to_non_nullable + as int?, + totalSets: freezed == totalSets + ? _value.totalSets + : totalSets // ignore: cast_nullable_to_non_nullable + as int?, + ), + ); + } +} + +/// @nodoc + +class _$WorkoutActivityEntityImpl extends _WorkoutActivityEntity { + const _$WorkoutActivityEntityImpl({ + required this.id, + required this.name, + required this.type, + required this.duration, + this.originalExercise, + this.sectionName, + this.setIndex, + this.totalSets, + }) : super._(); + + @override + final String id; + @override + final String name; + @override + final String type; + @override + final int duration; + @override + final TrainingExerciseEntity? originalExercise; + @override + final String? sectionName; + @override + final int? setIndex; + @override + final int? totalSets; + + @override + String toString() { + return 'WorkoutActivityEntity(id: $id, name: $name, type: $type, duration: $duration, originalExercise: $originalExercise, sectionName: $sectionName, setIndex: $setIndex, totalSets: $totalSets)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$WorkoutActivityEntityImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.name, name) || other.name == name) && + (identical(other.type, type) || other.type == type) && + (identical(other.duration, duration) || + other.duration == duration) && + (identical(other.originalExercise, originalExercise) || + other.originalExercise == originalExercise) && + (identical(other.sectionName, sectionName) || + other.sectionName == sectionName) && + (identical(other.setIndex, setIndex) || + other.setIndex == setIndex) && + (identical(other.totalSets, totalSets) || + other.totalSets == totalSets)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + id, + name, + type, + duration, + originalExercise, + sectionName, + setIndex, + totalSets, + ); + + /// Create a copy of WorkoutActivityEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$WorkoutActivityEntityImplCopyWith<_$WorkoutActivityEntityImpl> + get copyWith => + __$$WorkoutActivityEntityImplCopyWithImpl<_$WorkoutActivityEntityImpl>( + this, + _$identity, + ); +} + +abstract class _WorkoutActivityEntity extends WorkoutActivityEntity { + const factory _WorkoutActivityEntity({ + required final String id, + required final String name, + required final String type, + required final int duration, + final TrainingExerciseEntity? originalExercise, + final String? sectionName, + final int? setIndex, + final int? totalSets, + }) = _$WorkoutActivityEntityImpl; + const _WorkoutActivityEntity._() : super._(); + + @override + String get id; + @override + String get name; + @override + String get type; + @override + int get duration; + @override + TrainingExerciseEntity? get originalExercise; + @override + String? get sectionName; + @override + int? get setIndex; + @override + int? get totalSets; + + /// Create a copy of WorkoutActivityEntity + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$WorkoutActivityEntityImplCopyWith<_$WorkoutActivityEntityImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/domain/repositories/analysis_repository.dart b/lib/domain/repositories/analysis_repository.dart new file mode 100644 index 0000000..6a031ae --- /dev/null +++ b/lib/domain/repositories/analysis_repository.dart @@ -0,0 +1,19 @@ +import 'package:trainhub_flutter/domain/entities/analysis_session.dart'; +import 'package:trainhub_flutter/domain/entities/annotation.dart'; + +abstract class AnalysisRepository { + Future> getAllSessions(); + Future getSession(String id); + Future createSession(String name, String videoPath); + Future deleteSession(String id); + Future> getAnnotations(String sessionId); + Future addAnnotation({ + required String sessionId, + required String name, + required String description, + required double startTime, + required double endTime, + required String color, + }); + Future deleteAnnotation(String id); +} diff --git a/lib/domain/repositories/chat_repository.dart b/lib/domain/repositories/chat_repository.dart new file mode 100644 index 0000000..24cc27b --- /dev/null +++ b/lib/domain/repositories/chat_repository.dart @@ -0,0 +1,16 @@ +import 'package:trainhub_flutter/domain/entities/chat_session.dart'; +import 'package:trainhub_flutter/domain/entities/chat_message.dart'; + +abstract class ChatRepository { + Future> getAllSessions(); + Future getSession(String id); + Future createSession(); + Future deleteSession(String id); + Future> getMessages(String sessionId); + Future addMessage({ + required String sessionId, + required String role, + required String content, + }); + Future updateSessionTitle(String sessionId, String title); +} diff --git a/lib/domain/repositories/exercise_repository.dart b/lib/domain/repositories/exercise_repository.dart new file mode 100644 index 0000000..ff15830 --- /dev/null +++ b/lib/domain/repositories/exercise_repository.dart @@ -0,0 +1,13 @@ +import 'package:trainhub_flutter/domain/entities/exercise.dart'; + +abstract class ExerciseRepository { + Future> getAll(); + Future create({ + required String name, + String? instructions, + String? tags, + String? videoUrl, + }); + Future update(ExerciseEntity exercise); + Future delete(String id); +} diff --git a/lib/domain/repositories/program_repository.dart b/lib/domain/repositories/program_repository.dart new file mode 100644 index 0000000..b7442c0 --- /dev/null +++ b/lib/domain/repositories/program_repository.dart @@ -0,0 +1,20 @@ +import 'package:trainhub_flutter/domain/entities/program.dart'; +import 'package:trainhub_flutter/domain/entities/program_week.dart'; +import 'package:trainhub_flutter/domain/entities/program_workout.dart'; + +abstract class ProgramRepository { + Future> getAllPrograms(); + Future getProgram(String id); + Future> getWeeks(String programId); + Future> getWorkouts(String programId); + Future createProgram(String name); + Future deleteProgram(String id); + Future duplicateProgram(String sourceId); + Future addWeek(String programId, int position); + Future deleteWeek(String id); + Future updateWeekNote(String weekId, String note); + Future addWorkout(ProgramWorkoutEntity workout); + Future updateWorkout(ProgramWorkoutEntity workout); + Future deleteWorkout(String id); + Future toggleWorkoutComplete(String id, bool currentStatus); +} diff --git a/lib/domain/repositories/training_plan_repository.dart b/lib/domain/repositories/training_plan_repository.dart new file mode 100644 index 0000000..3d6e63b --- /dev/null +++ b/lib/domain/repositories/training_plan_repository.dart @@ -0,0 +1,9 @@ +import 'package:trainhub_flutter/domain/entities/training_plan.dart'; + +abstract class TrainingPlanRepository { + Future> getAll(); + Future getById(String id); + Future create(String name); + Future update(TrainingPlanEntity plan); + Future delete(String id); +} diff --git a/lib/injection.dart b/lib/injection.dart new file mode 100644 index 0000000..e411a48 --- /dev/null +++ b/lib/injection.dart @@ -0,0 +1,50 @@ +import 'package:get_it/get_it.dart'; +import 'package:trainhub_flutter/data/database/app_database.dart'; +import 'package:trainhub_flutter/data/database/daos/exercise_dao.dart'; +import 'package:trainhub_flutter/data/database/daos/training_plan_dao.dart'; +import 'package:trainhub_flutter/data/database/daos/program_dao.dart'; +import 'package:trainhub_flutter/data/database/daos/analysis_dao.dart'; +import 'package:trainhub_flutter/data/database/daos/chat_dao.dart'; +import 'package:trainhub_flutter/domain/repositories/exercise_repository.dart'; +import 'package:trainhub_flutter/domain/repositories/training_plan_repository.dart'; +import 'package:trainhub_flutter/domain/repositories/program_repository.dart'; +import 'package:trainhub_flutter/domain/repositories/analysis_repository.dart'; +import 'package:trainhub_flutter/domain/repositories/chat_repository.dart'; +import 'package:trainhub_flutter/data/repositories/exercise_repository_impl.dart'; +import 'package:trainhub_flutter/data/repositories/training_plan_repository_impl.dart'; +import 'package:trainhub_flutter/data/repositories/program_repository_impl.dart'; +import 'package:trainhub_flutter/data/repositories/analysis_repository_impl.dart'; +import 'package:trainhub_flutter/data/repositories/chat_repository_impl.dart'; + +final GetIt getIt = GetIt.instance; + +void init() { + // Database + getIt.registerSingleton(AppDatabase()); + + // DAOs + getIt.registerSingleton(ExerciseDao(getIt())); + getIt.registerSingleton( + TrainingPlanDao(getIt()), + ); + getIt.registerSingleton(ProgramDao(getIt())); + getIt.registerSingleton(AnalysisDao(getIt())); + getIt.registerSingleton(ChatDao(getIt())); + + // Repositories + getIt.registerLazySingleton( + () => ExerciseRepositoryImpl(getIt()), + ); + getIt.registerLazySingleton( + () => TrainingPlanRepositoryImpl(getIt()), + ); + getIt.registerLazySingleton( + () => ProgramRepositoryImpl(getIt()), + ); + getIt.registerLazySingleton( + () => AnalysisRepositoryImpl(getIt()), + ); + getIt.registerLazySingleton( + () => ChatRepositoryImpl(getIt()), + ); +} diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..dc7aa95 --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:window_manager/window_manager.dart'; +import 'package:trainhub_flutter/core/router/app_router.dart'; +import 'package:trainhub_flutter/core/theme/app_theme.dart'; +import 'package:trainhub_flutter/injection.dart' as di; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await windowManager.ensureInitialized(); + + // Initialize dependency injection + di.init(); + + WindowOptions windowOptions = const WindowOptions( + size: Size(1280, 800), + minimumSize: Size(800, 600), + center: true, + backgroundColor: Colors.transparent, + skipTaskbar: false, + titleBarStyle: TitleBarStyle.normal, + title: 'TrainHub', + ); + + windowManager.waitUntilReadyToShow(windowOptions, () async { + await windowManager.show(); + await windowManager.focus(); + }); + + runApp(const ProviderScope(child: TrainHubApp())); +} + +class TrainHubApp extends StatelessWidget { + const TrainHubApp({super.key}); + + @override + Widget build(BuildContext context) { + final appRouter = AppRouter(); + + return MaterialApp.router( + title: 'TrainHub', + theme: AppTheme.dark, + routerConfig: appRouter.config(), + debugShowCheckedModeBanner: false, + ); + } +} diff --git a/lib/models/training_models.dart b/lib/models/training_models.dart new file mode 100644 index 0000000..c094792 --- /dev/null +++ b/lib/models/training_models.dart @@ -0,0 +1,107 @@ +class TrainingPlanModel { + String id; + String name; + List sections; + + TrainingPlanModel({ + required this.id, + required this.name, + required this.sections, + }); + + factory TrainingPlanModel.fromJson(Map json) { + return TrainingPlanModel( + id: json['id'] ?? '', + name: json['name'] ?? '', + sections: + (json['sections'] as List?) + ?.map((e) => TrainingSectionModel.fromJson(e)) + .toList() ?? + [], + ); + } + + Map toJson() { + return { + 'id': id, + 'name': name, + 'sections': sections.map((e) => e.toJson()).toList(), + }; + } +} + +class TrainingSectionModel { + String id; + String name; + List exercises; + + TrainingSectionModel({ + required this.id, + required this.name, + required this.exercises, + }); + + factory TrainingSectionModel.fromJson(Map json) { + return TrainingSectionModel( + id: json['id'] ?? '', + name: json['name'] ?? '', + exercises: + (json['exercises'] as List?) + ?.map((e) => TrainingExerciseModel.fromJson(e)) + .toList() ?? + [], + ); + } + + Map toJson() { + return { + 'id': id, + 'name': name, + 'exercises': exercises.map((e) => e.toJson()).toList(), + }; + } +} + +class TrainingExerciseModel { + String instanceId; + String exerciseId; + String name; + int sets; + int value; // Reps or Seconds + bool isTime; + int rest; + + TrainingExerciseModel({ + required this.instanceId, + required this.exerciseId, + required this.name, + required this.sets, + required this.value, + required this.isTime, + required this.rest, + }); + + factory TrainingExerciseModel.fromJson(Map json) { + return TrainingExerciseModel( + instanceId: json['instanceId'] ?? '', + exerciseId: json['exerciseId'] ?? '', + name: json['name'] ?? '', + sets: json['sets'] ?? 3, + value: json['value'] ?? 10, + isTime: json['isTime'] ?? false, + rest: json['rest'] ?? 60, + ); + } + + Map toJson() { + return { + 'instanceId': instanceId, + 'exerciseId': exerciseId, + 'name': name, + 'sets': sets, + 'value': value, + 'isTime': isTime, + 'rest': rest, + }; + } +} diff --git a/lib/presentation/analysis/analysis_controller.dart b/lib/presentation/analysis/analysis_controller.dart new file mode 100644 index 0000000..f3f2497 --- /dev/null +++ b/lib/presentation/analysis/analysis_controller.dart @@ -0,0 +1,87 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:trainhub_flutter/injection.dart'; +import 'package:trainhub_flutter/domain/repositories/analysis_repository.dart'; +import 'package:trainhub_flutter/presentation/analysis/analysis_state.dart'; + +part 'analysis_controller.g.dart'; + +@riverpod +class AnalysisController extends _$AnalysisController { + late final AnalysisRepository _repo; + + @override + Future build() async { + _repo = getIt(); + final sessions = await _repo.getAllSessions(); + return AnalysisState(sessions: sessions); + } + + Future createSession(String name, String videoPath) async { + final session = await _repo.createSession(name, videoPath); + final sessions = await _repo.getAllSessions(); + final annotations = await _repo.getAnnotations(session.id); + state = AsyncValue.data( + AnalysisState( + sessions: sessions, + activeSession: session, + annotations: annotations, + ), + ); + } + + Future loadSession(String id) async { + final session = await _repo.getSession(id); + if (session == null) return; + final annotations = await _repo.getAnnotations(id); + final current = state.valueOrNull ?? const AnalysisState(); + state = AsyncValue.data( + current.copyWith( + activeSession: session, + annotations: annotations, + ), + ); + } + + Future deleteSession(String id) async { + await _repo.deleteSession(id); + final sessions = await _repo.getAllSessions(); + final current = state.valueOrNull ?? const AnalysisState(); + state = AsyncValue.data( + current.copyWith( + sessions: sessions, + activeSession: + current.activeSession?.id == id ? null : current.activeSession, + annotations: current.activeSession?.id == id ? [] : current.annotations, + ), + ); + } + + Future addAnnotation({ + required String name, + required String description, + required double startTime, + required double endTime, + required String color, + }) async { + final current = state.valueOrNull; + if (current?.activeSession == null) return; + await _repo.addAnnotation( + sessionId: current!.activeSession!.id, + name: name, + description: description, + startTime: startTime, + endTime: endTime, + color: color, + ); + final annotations = await _repo.getAnnotations(current.activeSession!.id); + state = AsyncValue.data(current.copyWith(annotations: annotations)); + } + + Future deleteAnnotation(String id) async { + await _repo.deleteAnnotation(id); + final current = state.valueOrNull; + if (current?.activeSession == null) return; + final annotations = await _repo.getAnnotations(current!.activeSession!.id); + state = AsyncValue.data(current.copyWith(annotations: annotations)); + } +} diff --git a/lib/presentation/analysis/analysis_controller.g.dart b/lib/presentation/analysis/analysis_controller.g.dart new file mode 100644 index 0000000..0d35726 --- /dev/null +++ b/lib/presentation/analysis/analysis_controller.g.dart @@ -0,0 +1,30 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'analysis_controller.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$analysisControllerHash() => + r'855d4ab55b8dc398e10c19d0ed245a60f104feed'; + +/// See also [AnalysisController]. +@ProviderFor(AnalysisController) +final analysisControllerProvider = + AutoDisposeAsyncNotifierProvider< + AnalysisController, + AnalysisState + >.internal( + AnalysisController.new, + name: r'analysisControllerProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$analysisControllerHash, + dependencies: null, + allTransitiveDependencies: null, + ); + +typedef _$AnalysisController = AutoDisposeAsyncNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/presentation/analysis/analysis_page.dart b/lib/presentation/analysis/analysis_page.dart new file mode 100644 index 0000000..7edadc6 --- /dev/null +++ b/lib/presentation/analysis/analysis_page.dart @@ -0,0 +1,232 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:trainhub_flutter/core/theme/app_colors.dart'; +import 'package:trainhub_flutter/core/constants/ui_constants.dart'; +import 'package:trainhub_flutter/presentation/analysis/analysis_controller.dart'; +import 'package:trainhub_flutter/presentation/analysis/widgets/analysis_session_list.dart'; +import 'package:trainhub_flutter/presentation/analysis/widgets/analysis_viewer.dart'; +import 'package:trainhub_flutter/presentation/common/widgets/app_empty_state.dart'; + +@RoutePage() +class AnalysisPage extends ConsumerWidget { + const AnalysisPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final state = ref.watch(analysisControllerProvider); + final controller = ref.read(analysisControllerProvider.notifier); + + return state.when( + data: (data) { + if (data.activeSession != null) { + return AnalysisViewer( + session: data.activeSession!, + annotations: data.annotations, + onClose: () { + ref.invalidate(analysisControllerProvider); + }, + ); + } + return Column( + children: [ + // Header toolbar + Container( + padding: const EdgeInsets.symmetric( + horizontal: UIConstants.spacing24, + vertical: UIConstants.spacing16, + ), + decoration: const BoxDecoration( + color: AppColors.surfaceContainer, + border: Border( + bottom: BorderSide(color: AppColors.border), + ), + ), + child: Row( + children: [ + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: AppColors.purpleMuted, + borderRadius: BorderRadius.circular(8), + ), + child: const Icon( + Icons.video_library, + color: AppColors.purple, + size: 20, + ), + ), + const SizedBox(width: UIConstants.spacing12), + const Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Video Analysis', + style: TextStyle( + color: AppColors.textPrimary, + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + Text( + 'Analyze and annotate your training videos', + style: TextStyle( + color: AppColors.textMuted, + fontSize: 12, + ), + ), + ], + ), + const Spacer(), + FilledButton.icon( + onPressed: () => + _showAddSessionDialog(context, controller), + icon: const Icon(Icons.add, size: 18), + label: const Text('New Session'), + ), + ], + ), + ), + // Session list + Expanded( + child: data.sessions.isEmpty + ? AppEmptyState( + icon: Icons.video_library_outlined, + title: 'No analysis sessions yet', + subtitle: + 'Create a session to analyze your training videos', + actionLabel: 'Create Session', + onAction: () => + _showAddSessionDialog(context, controller), + ) + : AnalysisSessionList( + sessions: data.sessions, + onSessionSelected: (session) => + controller.loadSession(session.id), + onDeleteSession: (session) => + controller.deleteSession(session.id), + ), + ), + ], + ); + }, + error: (e, s) => Center( + child: Text( + 'Error: $e', + style: const TextStyle(color: AppColors.destructive), + ), + ), + loading: () => const Center(child: CircularProgressIndicator()), + ); + } + + void _showAddSessionDialog( + BuildContext context, + AnalysisController controller, + ) { + final textController = TextEditingController(); + String? selectedVideoPath; + + showDialog( + context: context, + builder: (context) => StatefulBuilder( + builder: (context, setState) => AlertDialog( + title: const Text('New Analysis Session'), + content: SizedBox( + width: UIConstants.dialogWidth, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: textController, + decoration: const InputDecoration( + labelText: 'Session Name', + hintText: 'e.g. Squat Form Check', + ), + autofocus: true, + ), + const SizedBox(height: UIConstants.spacing16), + Container( + padding: const EdgeInsets.all(UIConstants.spacing12), + decoration: BoxDecoration( + color: AppColors.surface, + borderRadius: UIConstants.smallCardBorderRadius, + border: Border.all(color: AppColors.border), + ), + child: Row( + children: [ + Icon( + selectedVideoPath != null + ? Icons.videocam + : Icons.videocam_off_outlined, + color: selectedVideoPath != null + ? AppColors.success + : AppColors.textMuted, + size: 20, + ), + const SizedBox(width: UIConstants.spacing12), + Expanded( + child: Text( + selectedVideoPath != null + ? selectedVideoPath! + .split(RegExp(r'[\\/]')) + .last + : 'No video selected', + style: TextStyle( + color: selectedVideoPath != null + ? AppColors.textPrimary + : AppColors.textMuted, + fontSize: 13, + ), + overflow: TextOverflow.ellipsis, + ), + ), + const SizedBox(width: UIConstants.spacing8), + OutlinedButton.icon( + onPressed: () async { + final result = await FilePicker.platform.pickFiles( + type: FileType.video, + allowMultiple: false, + ); + if (result != null && + result.files.single.path != null) { + setState(() { + selectedVideoPath = result.files.single.path; + }); + } + }, + icon: const Icon(Icons.folder_open, size: 16), + label: const Text('Browse'), + ), + ], + ), + ), + ], + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Cancel'), + ), + FilledButton( + onPressed: () { + if (textController.text.isNotEmpty && + selectedVideoPath != null) { + controller.createSession( + textController.text, + selectedVideoPath!, + ); + Navigator.pop(context); + } + }, + child: const Text('Create'), + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/analysis/analysis_state.dart b/lib/presentation/analysis/analysis_state.dart new file mode 100644 index 0000000..82cd159 --- /dev/null +++ b/lib/presentation/analysis/analysis_state.dart @@ -0,0 +1,14 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:trainhub_flutter/domain/entities/analysis_session.dart'; +import 'package:trainhub_flutter/domain/entities/annotation.dart'; + +part 'analysis_state.freezed.dart'; + +@freezed +class AnalysisState with _$AnalysisState { + const factory AnalysisState({ + @Default([]) List sessions, + AnalysisSessionEntity? activeSession, + @Default([]) List annotations, + }) = _AnalysisState; +} diff --git a/lib/presentation/analysis/analysis_state.freezed.dart b/lib/presentation/analysis/analysis_state.freezed.dart new file mode 100644 index 0000000..9fdafd1 --- /dev/null +++ b/lib/presentation/analysis/analysis_state.freezed.dart @@ -0,0 +1,244 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'analysis_state.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$AnalysisState { + List get sessions => + throw _privateConstructorUsedError; + AnalysisSessionEntity? get activeSession => + throw _privateConstructorUsedError; + List get annotations => throw _privateConstructorUsedError; + + /// Create a copy of AnalysisState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $AnalysisStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AnalysisStateCopyWith<$Res> { + factory $AnalysisStateCopyWith( + AnalysisState value, + $Res Function(AnalysisState) then, + ) = _$AnalysisStateCopyWithImpl<$Res, AnalysisState>; + @useResult + $Res call({ + List sessions, + AnalysisSessionEntity? activeSession, + List annotations, + }); + + $AnalysisSessionEntityCopyWith<$Res>? get activeSession; +} + +/// @nodoc +class _$AnalysisStateCopyWithImpl<$Res, $Val extends AnalysisState> + implements $AnalysisStateCopyWith<$Res> { + _$AnalysisStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of AnalysisState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? sessions = null, + Object? activeSession = freezed, + Object? annotations = null, + }) { + return _then( + _value.copyWith( + sessions: null == sessions + ? _value.sessions + : sessions // ignore: cast_nullable_to_non_nullable + as List, + activeSession: freezed == activeSession + ? _value.activeSession + : activeSession // ignore: cast_nullable_to_non_nullable + as AnalysisSessionEntity?, + annotations: null == annotations + ? _value.annotations + : annotations // ignore: cast_nullable_to_non_nullable + as List, + ) + as $Val, + ); + } + + /// Create a copy of AnalysisState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $AnalysisSessionEntityCopyWith<$Res>? get activeSession { + if (_value.activeSession == null) { + return null; + } + + return $AnalysisSessionEntityCopyWith<$Res>(_value.activeSession!, (value) { + return _then(_value.copyWith(activeSession: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$AnalysisStateImplCopyWith<$Res> + implements $AnalysisStateCopyWith<$Res> { + factory _$$AnalysisStateImplCopyWith( + _$AnalysisStateImpl value, + $Res Function(_$AnalysisStateImpl) then, + ) = __$$AnalysisStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + List sessions, + AnalysisSessionEntity? activeSession, + List annotations, + }); + + @override + $AnalysisSessionEntityCopyWith<$Res>? get activeSession; +} + +/// @nodoc +class __$$AnalysisStateImplCopyWithImpl<$Res> + extends _$AnalysisStateCopyWithImpl<$Res, _$AnalysisStateImpl> + implements _$$AnalysisStateImplCopyWith<$Res> { + __$$AnalysisStateImplCopyWithImpl( + _$AnalysisStateImpl _value, + $Res Function(_$AnalysisStateImpl) _then, + ) : super(_value, _then); + + /// Create a copy of AnalysisState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? sessions = null, + Object? activeSession = freezed, + Object? annotations = null, + }) { + return _then( + _$AnalysisStateImpl( + sessions: null == sessions + ? _value._sessions + : sessions // ignore: cast_nullable_to_non_nullable + as List, + activeSession: freezed == activeSession + ? _value.activeSession + : activeSession // ignore: cast_nullable_to_non_nullable + as AnalysisSessionEntity?, + annotations: null == annotations + ? _value._annotations + : annotations // ignore: cast_nullable_to_non_nullable + as List, + ), + ); + } +} + +/// @nodoc + +class _$AnalysisStateImpl implements _AnalysisState { + const _$AnalysisStateImpl({ + final List sessions = const [], + this.activeSession, + final List annotations = const [], + }) : _sessions = sessions, + _annotations = annotations; + + final List _sessions; + @override + @JsonKey() + List get sessions { + if (_sessions is EqualUnmodifiableListView) return _sessions; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_sessions); + } + + @override + final AnalysisSessionEntity? activeSession; + final List _annotations; + @override + @JsonKey() + List get annotations { + if (_annotations is EqualUnmodifiableListView) return _annotations; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_annotations); + } + + @override + String toString() { + return 'AnalysisState(sessions: $sessions, activeSession: $activeSession, annotations: $annotations)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AnalysisStateImpl && + const DeepCollectionEquality().equals(other._sessions, _sessions) && + (identical(other.activeSession, activeSession) || + other.activeSession == activeSession) && + const DeepCollectionEquality().equals( + other._annotations, + _annotations, + )); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_sessions), + activeSession, + const DeepCollectionEquality().hash(_annotations), + ); + + /// Create a copy of AnalysisState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$AnalysisStateImplCopyWith<_$AnalysisStateImpl> get copyWith => + __$$AnalysisStateImplCopyWithImpl<_$AnalysisStateImpl>(this, _$identity); +} + +abstract class _AnalysisState implements AnalysisState { + const factory _AnalysisState({ + final List sessions, + final AnalysisSessionEntity? activeSession, + final List annotations, + }) = _$AnalysisStateImpl; + + @override + List get sessions; + @override + AnalysisSessionEntity? get activeSession; + @override + List get annotations; + + /// Create a copy of AnalysisState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$AnalysisStateImplCopyWith<_$AnalysisStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/presentation/analysis/widgets/analysis_session_list.dart b/lib/presentation/analysis/widgets/analysis_session_list.dart new file mode 100644 index 0000000..541b735 --- /dev/null +++ b/lib/presentation/analysis/widgets/analysis_session_list.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:trainhub_flutter/domain/entities/analysis_session.dart'; + +class AnalysisSessionList extends StatelessWidget { + final List sessions; + final Function(AnalysisSessionEntity) onSessionSelected; + final Function(AnalysisSessionEntity) onDeleteSession; + + const AnalysisSessionList({ + super.key, + required this.sessions, + required this.onSessionSelected, + required this.onDeleteSession, + }); + + @override + Widget build(BuildContext context) { + if (sessions.isEmpty) { + return const Center( + child: Text('No analysis sessions yet. Tap + to create one.'), + ); + } + return ListView.builder( + itemCount: sessions.length, + itemBuilder: (context, index) { + final session = sessions[index]; + return ListTile( + leading: const CircleAvatar(child: Icon(Icons.video_library)), + title: Text(session.name), + subtitle: Text(session.date), + trailing: IconButton( + icon: const Icon(Icons.delete_outline), + onPressed: () => onDeleteSession(session), + ), + onTap: () => onSessionSelected(session), + ); + }, + ); + } +} diff --git a/lib/presentation/analysis/widgets/analysis_viewer.dart b/lib/presentation/analysis/widgets/analysis_viewer.dart new file mode 100644 index 0000000..67260d7 --- /dev/null +++ b/lib/presentation/analysis/widgets/analysis_viewer.dart @@ -0,0 +1,470 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:trainhub_flutter/domain/entities/analysis_session.dart'; +import 'package:trainhub_flutter/domain/entities/annotation.dart'; +import 'package:trainhub_flutter/presentation/analysis/analysis_controller.dart'; +import 'package:video_player/video_player.dart'; + +class AnalysisViewer extends ConsumerStatefulWidget { + final AnalysisSessionEntity session; + final List annotations; + final VoidCallback onClose; + + const AnalysisViewer({ + super.key, + required this.session, + required this.annotations, + required this.onClose, + }); + + @override + ConsumerState createState() => _AnalysisViewerState(); +} + +class _AnalysisViewerState extends ConsumerState { + VideoPlayerController? _videoController; + bool _isPlaying = false; + double _currentPosition = 0.0; + double _totalDuration = 1.0; + + // IN/OUT points + double? _inPoint; + double? _outPoint; + bool _isLooping = false; + + @override + void initState() { + super.initState(); + _initializeVideo(); + } + + @override + void didUpdateWidget(covariant AnalysisViewer oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.session.videoPath != widget.session.videoPath) { + _initializeVideo(); + } + } + + Future _initializeVideo() async { + final path = widget.session.videoPath; + if (path == null) return; + + _videoController?.dispose(); + + final file = File(path); + if (await file.exists()) { + _videoController = VideoPlayerController.file(file); + await _videoController!.initialize(); + setState(() { + _totalDuration = _videoController!.value.duration.inSeconds.toDouble(); + }); + _videoController!.addListener(_videoListener); + } + } + + void _videoListener() { + if (_videoController == null) return; + + final bool isPlaying = _videoController!.value.isPlaying; + final double position = + _videoController!.value.position.inMilliseconds / 1000.0; + + // Loop logic + if (_isLooping && _outPoint != null && position >= _outPoint!) { + _seekTo(_inPoint ?? 0.0); + return; + } + + if (isPlaying != _isPlaying || (position - _currentPosition).abs() > 0.1) { + setState(() { + _isPlaying = isPlaying; + _currentPosition = position; + }); + } + } + + @override + void dispose() { + _videoController?.removeListener(_videoListener); + _videoController?.dispose(); + super.dispose(); + } + + void _togglePlay() { + if (_videoController == null) return; + if (_videoController!.value.isPlaying) { + _videoController!.pause(); + } else { + if (_isLooping && _outPoint != null && _currentPosition >= _outPoint!) { + _seekTo(_inPoint ?? 0.0); + } + _videoController!.play(); + } + } + + void _seekTo(double value) { + if (_videoController == null) return; + _videoController!.seekTo(Duration(milliseconds: (value * 1000).toInt())); + } + + void _setInPoint() { + setState(() { + _inPoint = _currentPosition; + if (_outPoint != null && _inPoint! > _outPoint!) { + _outPoint = null; + } + }); + } + + void _setOutPoint() { + setState(() { + _outPoint = _currentPosition; + if (_inPoint != null && _outPoint! < _inPoint!) { + _inPoint = null; + } + }); + } + + void _clearPoints() { + setState(() { + _inPoint = null; + _outPoint = null; + _isLooping = false; + }); + } + + void _toggleLoop() { + setState(() { + _isLooping = !_isLooping; + }); + } + + void _playRange(double start, double end) { + setState(() { + _inPoint = start; + _outPoint = end; + _isLooping = true; + }); + _seekTo(start); + _videoController?.play(); + } + + @override + Widget build(BuildContext context) { + final controller = ref.read(analysisControllerProvider.notifier); + + return Column( + children: [ + // Video Area + Expanded( + flex: 3, + child: Container( + color: Colors.black, + alignment: Alignment.center, + child: + _videoController != null && + _videoController!.value.isInitialized + ? Stack( + alignment: Alignment.bottomCenter, + children: [ + Center( + child: AspectRatio( + aspectRatio: _videoController!.value.aspectRatio, + child: VideoPlayer(_videoController!), + ), + ), + _buildTimelineControls(), + ], + ) + : const Center(child: CircularProgressIndicator()), + ), + ), + + // Annotations List + Expanded( + flex: 2, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 8, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Annotations", + style: Theme.of(context).textTheme.titleMedium, + ), + ElevatedButton.icon( + onPressed: () { + double start = _inPoint ?? _currentPosition; + double end = _outPoint ?? (_currentPosition + 5.0); + if (end > _totalDuration) end = _totalDuration; + + controller.addAnnotation( + name: "New Annotation", + description: + "${_formatDuration(start)} - ${_formatDuration(end)}", + startTime: start, + endTime: end, + color: "red", + ); + }, + icon: const Icon(Icons.add), + label: const Text("Add from Selection"), + ), + ], + ), + ), + const Divider(height: 1), + Expanded( + child: ListView.separated( + separatorBuilder: (context, index) => + const Divider(height: 1), + itemCount: widget.annotations.length, + itemBuilder: (context, index) { + final note = widget.annotations[index]; + final bool isActive = + _currentPosition >= note.startTime && + _currentPosition <= note.endTime; + + return Container( + color: isActive + ? Theme.of( + context, + ).colorScheme.primaryContainer.withOpacity(0.2) + : null, + child: ListTile( + leading: Icon( + Icons.label, + color: _parseColor(note.color ?? 'grey'), + ), + title: Text(note.name ?? 'Untitled'), + subtitle: Text( + "${_formatDuration(note.startTime)} - ${_formatDuration(note.endTime)}", + style: Theme.of(context).textTheme.bodySmall, + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Icons.play_circle_outline), + onPressed: () => + _playRange(note.startTime, note.endTime), + tooltip: "Play Range", + ), + IconButton( + icon: const Icon(Icons.delete_outline), + onPressed: () => + controller.deleteAnnotation(note.id), + ), + ], + ), + ), + ); + }, + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: SizedBox( + width: double.infinity, + child: TextButton( + onPressed: widget.onClose, + child: const Text('Back'), + ), + ), + ), + ], + ), + ), + ], + ); + } + + Widget _buildTimelineControls() { + return Container( + color: Colors.black.withOpacity(0.8), + padding: const EdgeInsets.symmetric(vertical: 8), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Timeline Visualization + SizedBox( + height: 20, + child: LayoutBuilder( + builder: (context, constraints) { + return Stack( + children: [ + // Annotations + ...widget.annotations.map((note) { + final left = + (note.startTime / _totalDuration) * + constraints.maxWidth; + final width = + ((note.endTime - note.startTime) / _totalDuration) * + constraints.maxWidth; + return Positioned( + left: left, + width: width, + top: 4, + bottom: 4, + child: Container( + decoration: BoxDecoration( + color: _parseColor( + note.color ?? 'grey', + ).withOpacity(0.6), + borderRadius: BorderRadius.circular(2), + ), + ), + ); + }), + // IN Point + if (_inPoint != null) + Positioned( + left: + (_inPoint! / _totalDuration) * constraints.maxWidth, + top: 0, + bottom: 0, + child: Container(width: 2, color: Colors.green), + ), + // OUT Point + if (_outPoint != null) + Positioned( + left: + (_outPoint! / _totalDuration) * + constraints.maxWidth, + top: 0, + bottom: 0, + child: Container(width: 2, color: Colors.red), + ), + ], + ); + }, + ), + ), + + // Slider + SliderTheme( + data: SliderTheme.of(context).copyWith( + thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 6), + overlayShape: const RoundSliderOverlayShape(overlayRadius: 10), + trackHeight: 2, + ), + child: Slider( + value: _currentPosition.clamp(0.0, _totalDuration), + min: 0.0, + max: _totalDuration, + onChanged: (value) => _seekTo(value), + activeColor: Theme.of(context).colorScheme.primary, + inactiveColor: Colors.grey, + ), + ), + + // Controls Row + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + _formatDuration(_currentPosition), + style: const TextStyle(color: Colors.white, fontSize: 12), + ), + const SizedBox(width: 16), + + IconButton( + icon: const Icon( + Icons.keyboard_arrow_left, + color: Colors.white, + ), + onPressed: () => _seekTo(_currentPosition - 1), + ), + IconButton( + icon: Icon( + _isPlaying ? Icons.pause : Icons.play_arrow, + color: Colors.white, + ), + onPressed: _togglePlay, + ), + IconButton( + icon: const Icon( + Icons.keyboard_arrow_right, + color: Colors.white, + ), + onPressed: () => _seekTo(_currentPosition + 1), + ), + + const SizedBox(width: 16), + + // IN/OUT Controls + IconButton( + icon: const Icon(Icons.login, color: Colors.green), + tooltip: "Set IN Point", + onPressed: _setInPoint, + ), + IconButton( + icon: const Icon(Icons.logout, color: Colors.red), + tooltip: "Set OUT Point", + onPressed: _setOutPoint, + ), + IconButton( + icon: Icon( + Icons.loop, + color: _isLooping + ? Theme.of(context).colorScheme.primary + : Colors.white, + ), + tooltip: "Toggle Loop", + onPressed: _toggleLoop, + ), + if (_inPoint != null || _outPoint != null) + IconButton( + icon: const Icon( + Icons.cancel_outlined, + color: Colors.white, + ), + tooltip: "Clear Points", + onPressed: _clearPoints, + ), + + const Spacer(), + Text( + _formatDuration(_totalDuration), + style: const TextStyle(color: Colors.white, fontSize: 12), + ), + ], + ), + ), + ], + ), + ); + } + + String _formatDuration(double seconds) { + final duration = Duration(milliseconds: (seconds * 1000).toInt()); + final mins = duration.inMinutes; + final secs = duration.inSeconds % 60; + return '$mins:${secs.toString().padLeft(2, '0')}'; + } + + Color _parseColor(String colorName) { + switch (colorName.toLowerCase()) { + case 'red': + return Colors.red; + case 'green': + return Colors.green; + case 'blue': + return Colors.blue; + case 'yellow': + return Colors.yellow; + default: + return Colors.grey; + } + } +} diff --git a/lib/presentation/calendar/calendar_controller.dart b/lib/presentation/calendar/calendar_controller.dart new file mode 100644 index 0000000..739a15b --- /dev/null +++ b/lib/presentation/calendar/calendar_controller.dart @@ -0,0 +1,125 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:trainhub_flutter/injection.dart'; +import 'package:trainhub_flutter/domain/entities/program_workout.dart'; +import 'package:trainhub_flutter/domain/repositories/program_repository.dart'; +import 'package:trainhub_flutter/domain/repositories/training_plan_repository.dart'; +import 'package:trainhub_flutter/domain/repositories/exercise_repository.dart'; +import 'package:trainhub_flutter/presentation/calendar/calendar_state.dart'; + +part 'calendar_controller.g.dart'; + +@riverpod +class CalendarController extends _$CalendarController { + late final ProgramRepository _programRepo; + late final TrainingPlanRepository _planRepo; + late final ExerciseRepository _exerciseRepo; + + @override + Future build() async { + _programRepo = getIt(); + _planRepo = getIt(); + _exerciseRepo = getIt(); + final programs = await _programRepo.getAllPrograms(); + final plans = await _planRepo.getAll(); + final exercises = await _exerciseRepo.getAll(); + if (programs.isEmpty) { + return CalendarState(plans: plans, exercises: exercises); + } + final activeProgram = programs.first; + final weeks = await _programRepo.getWeeks(activeProgram.id); + final workouts = await _programRepo.getWorkouts(activeProgram.id); + return CalendarState( + programs: programs, + activeProgram: activeProgram, + weeks: weeks, + workouts: workouts, + plans: plans, + exercises: exercises, + ); + } + + Future loadProgram(String id) async { + final program = await _programRepo.getProgram(id); + if (program == null) return; + final weeks = await _programRepo.getWeeks(id); + final workouts = await _programRepo.getWorkouts(id); + final current = state.valueOrNull ?? const CalendarState(); + state = AsyncValue.data( + current.copyWith( + activeProgram: program, + weeks: weeks, + workouts: workouts, + ), + ); + } + + Future createProgram(String name) async { + final program = await _programRepo.createProgram(name); + await _reloadFull(); + await loadProgram(program.id); + } + + Future deleteProgram(String id) async { + await _programRepo.deleteProgram(id); + state = await AsyncValue.guard(() => build()); + } + + Future duplicateProgram(String sourceId) async { + await _programRepo.duplicateProgram(sourceId); + await _reloadFull(); + } + + Future addWeek() async { + final current = state.valueOrNull; + if (current?.activeProgram == null) return; + final nextPosition = + current!.weeks.isEmpty ? 1 : current.weeks.last.position + 1; + await _programRepo.addWeek(current.activeProgram!.id, nextPosition); + await _reloadProgramDetails(); + } + + Future deleteWeek(String id) async { + await _programRepo.deleteWeek(id); + await _reloadProgramDetails(); + } + + Future updateWeekNote(String weekId, String note) async { + await _programRepo.updateWeekNote(weekId, note); + await _reloadProgramDetails(); + } + + Future addWorkout(ProgramWorkoutEntity workout) async { + await _programRepo.addWorkout(workout); + await _reloadProgramDetails(); + } + + Future updateWorkout(ProgramWorkoutEntity workout) async { + await _programRepo.updateWorkout(workout); + await _reloadProgramDetails(); + } + + Future deleteWorkout(String id) async { + await _programRepo.deleteWorkout(id); + await _reloadProgramDetails(); + } + + Future toggleWorkoutComplete(String id, bool currentStatus) async { + await _programRepo.toggleWorkoutComplete(id, currentStatus); + await _reloadProgramDetails(); + } + + Future _reloadProgramDetails() async { + final current = state.valueOrNull; + if (current?.activeProgram == null) return; + final weeks = await _programRepo.getWeeks(current!.activeProgram!.id); + final workouts = + await _programRepo.getWorkouts(current.activeProgram!.id); + state = AsyncValue.data( + current.copyWith(weeks: weeks, workouts: workouts), + ); + } + + Future _reloadFull() async { + state = await AsyncValue.guard(() => build()); + } +} diff --git a/lib/presentation/calendar/calendar_controller.g.dart b/lib/presentation/calendar/calendar_controller.g.dart new file mode 100644 index 0000000..48d7456 --- /dev/null +++ b/lib/presentation/calendar/calendar_controller.g.dart @@ -0,0 +1,30 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'calendar_controller.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$calendarControllerHash() => + r'747a59ba47bf4d1b6a66e3bcc82276e4ad81eb1a'; + +/// See also [CalendarController]. +@ProviderFor(CalendarController) +final calendarControllerProvider = + AutoDisposeAsyncNotifierProvider< + CalendarController, + CalendarState + >.internal( + CalendarController.new, + name: r'calendarControllerProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$calendarControllerHash, + dependencies: null, + allTransitiveDependencies: null, + ); + +typedef _$CalendarController = AutoDisposeAsyncNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/presentation/calendar/calendar_page.dart b/lib/presentation/calendar/calendar_page.dart new file mode 100644 index 0000000..5e68c79 --- /dev/null +++ b/lib/presentation/calendar/calendar_page.dart @@ -0,0 +1,116 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:trainhub_flutter/presentation/calendar/calendar_controller.dart'; +import 'package:trainhub_flutter/presentation/calendar/widgets/program_selector.dart'; +import 'package:trainhub_flutter/presentation/calendar/widgets/program_week_view.dart'; + +@RoutePage() +class CalendarPage extends ConsumerWidget { + const CalendarPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final state = ref.watch(calendarControllerProvider); + final controller = ref.read(calendarControllerProvider.notifier); + + return Scaffold( + appBar: AppBar(title: const Text('Program & Schedule')), + body: state.when( + data: (data) { + if (data.programs.isEmpty) { + return Center( + child: ElevatedButton( + onPressed: () => _showCreateProgramDialog(context, controller), + child: const Text('Create First Program'), + ), + ); + } + + return Column( + children: [ + ProgramSelector( + programs: data.programs, + activeProgram: data.activeProgram, + onProgramSelected: (p) => controller.loadProgram(p.id), + onCreateProgram: () => + _showCreateProgramDialog(context, controller), + ), + Expanded( + child: data.activeProgram == null + ? const Center(child: Text("Select a program")) + : ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: data.weeks.length + 1, + itemBuilder: (context, index) { + if (index == data.weeks.length) { + return Padding( + padding: const EdgeInsets.symmetric( + vertical: 24.0, + ), + child: Center( + child: OutlinedButton.icon( + onPressed: controller.addWeek, + icon: const Icon(Icons.add), + label: const Text("Add Week"), + ), + ), + ); + } + final week = data.weeks[index]; + final weekWorkouts = data.workouts + .where((w) => w.weekId == week.id) + .toList(); + return ProgramWeekView( + week: week, + workouts: weekWorkouts, + availablePlans: data.plans, + onAddWorkout: (workout) => controller.addWorkout( + workout, + ), // logic needs refined params + onDeleteWorkout: controller.deleteWorkout, + ); + }, + ), + ), + ], + ); + }, + error: (e, s) => Center(child: Text('Error: $e')), + loading: () => const Center(child: CircularProgressIndicator()), + ), + ); + } + + void _showCreateProgramDialog( + BuildContext context, + CalendarController controller, + ) { + final textController = TextEditingController(); + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('New Program'), + content: TextField( + controller: textController, + decoration: const InputDecoration(labelText: 'Program Name'), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Cancel'), + ), + FilledButton( + onPressed: () { + if (textController.text.isNotEmpty) { + controller.createProgram(textController.text); + Navigator.pop(context); + } + }, + child: const Text('Create'), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/calendar/calendar_state.dart b/lib/presentation/calendar/calendar_state.dart new file mode 100644 index 0000000..f038c81 --- /dev/null +++ b/lib/presentation/calendar/calendar_state.dart @@ -0,0 +1,20 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:trainhub_flutter/domain/entities/program.dart'; +import 'package:trainhub_flutter/domain/entities/program_week.dart'; +import 'package:trainhub_flutter/domain/entities/program_workout.dart'; +import 'package:trainhub_flutter/domain/entities/training_plan.dart'; +import 'package:trainhub_flutter/domain/entities/exercise.dart'; + +part 'calendar_state.freezed.dart'; + +@freezed +class CalendarState with _$CalendarState { + const factory CalendarState({ + @Default([]) List programs, + ProgramEntity? activeProgram, + @Default([]) List weeks, + @Default([]) List workouts, + @Default([]) List plans, + @Default([]) List exercises, + }) = _CalendarState; +} diff --git a/lib/presentation/calendar/calendar_state.freezed.dart b/lib/presentation/calendar/calendar_state.freezed.dart new file mode 100644 index 0000000..7d24d64 --- /dev/null +++ b/lib/presentation/calendar/calendar_state.freezed.dart @@ -0,0 +1,329 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'calendar_state.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$CalendarState { + List get programs => throw _privateConstructorUsedError; + ProgramEntity? get activeProgram => throw _privateConstructorUsedError; + List get weeks => throw _privateConstructorUsedError; + List get workouts => throw _privateConstructorUsedError; + List get plans => throw _privateConstructorUsedError; + List get exercises => throw _privateConstructorUsedError; + + /// Create a copy of CalendarState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $CalendarStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CalendarStateCopyWith<$Res> { + factory $CalendarStateCopyWith( + CalendarState value, + $Res Function(CalendarState) then, + ) = _$CalendarStateCopyWithImpl<$Res, CalendarState>; + @useResult + $Res call({ + List programs, + ProgramEntity? activeProgram, + List weeks, + List workouts, + List plans, + List exercises, + }); + + $ProgramEntityCopyWith<$Res>? get activeProgram; +} + +/// @nodoc +class _$CalendarStateCopyWithImpl<$Res, $Val extends CalendarState> + implements $CalendarStateCopyWith<$Res> { + _$CalendarStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of CalendarState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? programs = null, + Object? activeProgram = freezed, + Object? weeks = null, + Object? workouts = null, + Object? plans = null, + Object? exercises = null, + }) { + return _then( + _value.copyWith( + programs: null == programs + ? _value.programs + : programs // ignore: cast_nullable_to_non_nullable + as List, + activeProgram: freezed == activeProgram + ? _value.activeProgram + : activeProgram // ignore: cast_nullable_to_non_nullable + as ProgramEntity?, + weeks: null == weeks + ? _value.weeks + : weeks // ignore: cast_nullable_to_non_nullable + as List, + workouts: null == workouts + ? _value.workouts + : workouts // ignore: cast_nullable_to_non_nullable + as List, + plans: null == plans + ? _value.plans + : plans // ignore: cast_nullable_to_non_nullable + as List, + exercises: null == exercises + ? _value.exercises + : exercises // ignore: cast_nullable_to_non_nullable + as List, + ) + as $Val, + ); + } + + /// Create a copy of CalendarState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $ProgramEntityCopyWith<$Res>? get activeProgram { + if (_value.activeProgram == null) { + return null; + } + + return $ProgramEntityCopyWith<$Res>(_value.activeProgram!, (value) { + return _then(_value.copyWith(activeProgram: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$CalendarStateImplCopyWith<$Res> + implements $CalendarStateCopyWith<$Res> { + factory _$$CalendarStateImplCopyWith( + _$CalendarStateImpl value, + $Res Function(_$CalendarStateImpl) then, + ) = __$$CalendarStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + List programs, + ProgramEntity? activeProgram, + List weeks, + List workouts, + List plans, + List exercises, + }); + + @override + $ProgramEntityCopyWith<$Res>? get activeProgram; +} + +/// @nodoc +class __$$CalendarStateImplCopyWithImpl<$Res> + extends _$CalendarStateCopyWithImpl<$Res, _$CalendarStateImpl> + implements _$$CalendarStateImplCopyWith<$Res> { + __$$CalendarStateImplCopyWithImpl( + _$CalendarStateImpl _value, + $Res Function(_$CalendarStateImpl) _then, + ) : super(_value, _then); + + /// Create a copy of CalendarState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? programs = null, + Object? activeProgram = freezed, + Object? weeks = null, + Object? workouts = null, + Object? plans = null, + Object? exercises = null, + }) { + return _then( + _$CalendarStateImpl( + programs: null == programs + ? _value._programs + : programs // ignore: cast_nullable_to_non_nullable + as List, + activeProgram: freezed == activeProgram + ? _value.activeProgram + : activeProgram // ignore: cast_nullable_to_non_nullable + as ProgramEntity?, + weeks: null == weeks + ? _value._weeks + : weeks // ignore: cast_nullable_to_non_nullable + as List, + workouts: null == workouts + ? _value._workouts + : workouts // ignore: cast_nullable_to_non_nullable + as List, + plans: null == plans + ? _value._plans + : plans // ignore: cast_nullable_to_non_nullable + as List, + exercises: null == exercises + ? _value._exercises + : exercises // ignore: cast_nullable_to_non_nullable + as List, + ), + ); + } +} + +/// @nodoc + +class _$CalendarStateImpl implements _CalendarState { + const _$CalendarStateImpl({ + final List programs = const [], + this.activeProgram, + final List weeks = const [], + final List workouts = const [], + final List plans = const [], + final List exercises = const [], + }) : _programs = programs, + _weeks = weeks, + _workouts = workouts, + _plans = plans, + _exercises = exercises; + + final List _programs; + @override + @JsonKey() + List get programs { + if (_programs is EqualUnmodifiableListView) return _programs; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_programs); + } + + @override + final ProgramEntity? activeProgram; + final List _weeks; + @override + @JsonKey() + List get weeks { + if (_weeks is EqualUnmodifiableListView) return _weeks; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_weeks); + } + + final List _workouts; + @override + @JsonKey() + List get workouts { + if (_workouts is EqualUnmodifiableListView) return _workouts; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_workouts); + } + + final List _plans; + @override + @JsonKey() + List get plans { + if (_plans is EqualUnmodifiableListView) return _plans; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_plans); + } + + final List _exercises; + @override + @JsonKey() + List get exercises { + if (_exercises is EqualUnmodifiableListView) return _exercises; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_exercises); + } + + @override + String toString() { + return 'CalendarState(programs: $programs, activeProgram: $activeProgram, weeks: $weeks, workouts: $workouts, plans: $plans, exercises: $exercises)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CalendarStateImpl && + const DeepCollectionEquality().equals(other._programs, _programs) && + (identical(other.activeProgram, activeProgram) || + other.activeProgram == activeProgram) && + const DeepCollectionEquality().equals(other._weeks, _weeks) && + const DeepCollectionEquality().equals(other._workouts, _workouts) && + const DeepCollectionEquality().equals(other._plans, _plans) && + const DeepCollectionEquality().equals( + other._exercises, + _exercises, + )); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_programs), + activeProgram, + const DeepCollectionEquality().hash(_weeks), + const DeepCollectionEquality().hash(_workouts), + const DeepCollectionEquality().hash(_plans), + const DeepCollectionEquality().hash(_exercises), + ); + + /// Create a copy of CalendarState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$CalendarStateImplCopyWith<_$CalendarStateImpl> get copyWith => + __$$CalendarStateImplCopyWithImpl<_$CalendarStateImpl>(this, _$identity); +} + +abstract class _CalendarState implements CalendarState { + const factory _CalendarState({ + final List programs, + final ProgramEntity? activeProgram, + final List weeks, + final List workouts, + final List plans, + final List exercises, + }) = _$CalendarStateImpl; + + @override + List get programs; + @override + ProgramEntity? get activeProgram; + @override + List get weeks; + @override + List get workouts; + @override + List get plans; + @override + List get exercises; + + /// Create a copy of CalendarState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$CalendarStateImplCopyWith<_$CalendarStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/presentation/calendar/widgets/program_selector.dart b/lib/presentation/calendar/widgets/program_selector.dart new file mode 100644 index 0000000..df418e8 --- /dev/null +++ b/lib/presentation/calendar/widgets/program_selector.dart @@ -0,0 +1,252 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:trainhub_flutter/core/constants/ui_constants.dart'; +import 'package:trainhub_flutter/core/theme/app_colors.dart'; +import 'package:trainhub_flutter/domain/entities/program.dart'; + +class ProgramSelector extends StatelessWidget { + final List programs; + final ProgramEntity? activeProgram; + final ValueChanged onProgramSelected; + final VoidCallback onCreateProgram; + final VoidCallback onDuplicateProgram; + final VoidCallback onDeleteProgram; + + const ProgramSelector({ + super.key, + required this.programs, + required this.activeProgram, + required this.onProgramSelected, + required this.onCreateProgram, + required this.onDuplicateProgram, + required this.onDeleteProgram, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric( + horizontal: UIConstants.pagePadding, + vertical: UIConstants.spacing12, + ), + decoration: const BoxDecoration( + color: AppColors.surfaceContainer, + border: Border( + bottom: BorderSide(color: AppColors.border), + ), + ), + child: Row( + children: [ + // Program icon + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: AppColors.accentMuted, + borderRadius: BorderRadius.circular(UIConstants.smallBorderRadius), + ), + child: const Icon( + Icons.calendar_month_rounded, + color: AppColors.accent, + size: 18, + ), + ), + const SizedBox(width: UIConstants.spacing12), + + // Program dropdown + Expanded( + child: _ProgramDropdown( + programs: programs, + activeProgram: activeProgram, + onProgramSelected: onProgramSelected, + ), + ), + + const SizedBox(width: UIConstants.spacing12), + + // Action buttons + _ToolbarButton( + icon: Icons.add_rounded, + label: 'New Program', + onPressed: onCreateProgram, + isPrimary: true, + ), + const SizedBox(width: UIConstants.spacing8), + _ToolbarButton( + icon: Icons.copy_rounded, + label: 'Duplicate', + onPressed: activeProgram != null ? onDuplicateProgram : null, + ), + const SizedBox(width: UIConstants.spacing8), + _ToolbarButton( + icon: Icons.delete_outline_rounded, + label: 'Delete', + onPressed: activeProgram != null ? onDeleteProgram : null, + isDestructive: true, + ), + ], + ), + ); + } +} + +class _ProgramDropdown extends StatelessWidget { + final List programs; + final ProgramEntity? activeProgram; + final ValueChanged onProgramSelected; + + const _ProgramDropdown({ + required this.programs, + required this.activeProgram, + required this.onProgramSelected, + }); + + @override + Widget build(BuildContext context) { + return Container( + height: 38, + padding: const EdgeInsets.symmetric(horizontal: UIConstants.spacing12), + decoration: BoxDecoration( + color: AppColors.zinc950, + borderRadius: BorderRadius.circular(UIConstants.smallBorderRadius), + border: Border.all(color: AppColors.border), + ), + child: DropdownButtonHideUnderline( + child: DropdownButton( + value: activeProgram?.id, + isExpanded: true, + icon: const Icon( + Icons.unfold_more_rounded, + size: 18, + color: AppColors.textMuted, + ), + dropdownColor: AppColors.surfaceContainer, + borderRadius: BorderRadius.circular(UIConstants.smallBorderRadius), + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w500, + color: AppColors.textPrimary, + ), + hint: Text( + 'Select a program', + style: GoogleFonts.inter( + fontSize: 14, + color: AppColors.textMuted, + ), + ), + items: programs.map((p) { + return DropdownMenuItem( + value: p.id, + child: Text( + p.name, + overflow: TextOverflow.ellipsis, + ), + ); + }).toList(), + onChanged: (id) { + if (id != null) { + final program = programs.firstWhere((p) => p.id == id); + onProgramSelected(program); + } + }, + ), + ), + ); + } +} + +class _ToolbarButton extends StatefulWidget { + final IconData icon; + final String label; + final VoidCallback? onPressed; + final bool isPrimary; + final bool isDestructive; + + const _ToolbarButton({ + required this.icon, + required this.label, + required this.onPressed, + this.isPrimary = false, + this.isDestructive = false, + }); + + @override + State<_ToolbarButton> createState() => _ToolbarButtonState(); +} + +class _ToolbarButtonState extends State<_ToolbarButton> { + bool _isHovered = false; + + @override + Widget build(BuildContext context) { + final isDisabled = widget.onPressed == null; + + Color bgColor; + Color fgColor; + Color borderColor; + + if (isDisabled) { + bgColor = Colors.transparent; + fgColor = AppColors.zinc600; + borderColor = AppColors.border; + } else if (widget.isPrimary) { + bgColor = _isHovered ? AppColors.accent : AppColors.accentMuted; + fgColor = _isHovered ? AppColors.zinc950 : AppColors.accent; + borderColor = AppColors.accent.withValues(alpha: 0.3); + } else if (widget.isDestructive) { + bgColor = + _isHovered ? AppColors.destructiveMuted : Colors.transparent; + fgColor = + _isHovered ? AppColors.destructive : AppColors.textSecondary; + borderColor = _isHovered ? AppColors.destructive.withValues(alpha: 0.3) : AppColors.border; + } else { + bgColor = _isHovered ? AppColors.surfaceContainerHigh : Colors.transparent; + fgColor = _isHovered ? AppColors.textPrimary : AppColors.textSecondary; + borderColor = AppColors.border; + } + + return MouseRegion( + onEnter: (_) => setState(() => _isHovered = true), + onExit: (_) => setState(() => _isHovered = false), + child: Tooltip( + message: widget.label, + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: widget.onPressed, + borderRadius: + BorderRadius.circular(UIConstants.smallBorderRadius), + child: AnimatedContainer( + duration: UIConstants.animationDuration, + height: 34, + padding: const EdgeInsets.symmetric( + horizontal: UIConstants.spacing12, + ), + decoration: BoxDecoration( + color: bgColor, + borderRadius: + BorderRadius.circular(UIConstants.smallBorderRadius), + border: Border.all(color: borderColor), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(widget.icon, size: 16, color: fgColor), + const SizedBox(width: UIConstants.spacing4), + Text( + widget.label, + style: GoogleFonts.inter( + fontSize: 13, + fontWeight: FontWeight.w500, + color: fgColor, + ), + ), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/presentation/calendar/widgets/program_week_view.dart b/lib/presentation/calendar/widgets/program_week_view.dart new file mode 100644 index 0000000..4c8245a --- /dev/null +++ b/lib/presentation/calendar/widgets/program_week_view.dart @@ -0,0 +1,244 @@ +import 'package:flutter/material.dart'; +import 'package:trainhub_flutter/core/utils/id_generator.dart'; +import 'package:trainhub_flutter/domain/entities/program_week.dart'; +import 'package:trainhub_flutter/domain/entities/program_workout.dart'; +import 'package:trainhub_flutter/domain/entities/training_plan.dart'; + +class ProgramWeekView extends StatelessWidget { + final ProgramWeekEntity week; + final List workouts; + final List availablePlans; + final Function(ProgramWorkoutEntity) onAddWorkout; + final Function(String) onDeleteWorkout; + + const ProgramWeekView({ + super.key, + required this.week, + required this.workouts, + required this.availablePlans, + required this.onAddWorkout, + required this.onDeleteWorkout, + }); + + @override + Widget build(BuildContext context) { + return Card( + margin: const EdgeInsets.only(bottom: 24), + elevation: 2, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Week ${week.position}", + style: Theme.of(context).textTheme.headlineSmall, + ), + const Divider(), + SizedBox( + height: 500, // Fixed height for the week grid, or make it dynamic + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: List.generate(7, (dayIndex) { + final dayNum = dayIndex + 1; + final dayWorkouts = workouts + .where((w) => w.day == dayNum.toString()) + .toList(); + + return Expanded( + child: Container( + decoration: BoxDecoration( + border: dayIndex < 6 + ? const Border( + right: BorderSide( + color: Colors.grey, + width: 0.5, + ), + ) + : null, + color: dayIndex % 2 == 0 + ? Theme.of(context).colorScheme.surfaceContainerLow + : null, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Theme.of( + context, + ).colorScheme.surfaceContainerHigh, + border: const Border( + bottom: BorderSide( + color: Colors.grey, + width: 0.5, + ), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + _getDayName(dayNum), + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + InkWell( + onTap: () => + _showAddWorkoutSheet(context, dayNum), + borderRadius: BorderRadius.circular(16), + child: const Icon( + Icons.add_circle_outline, + size: 20, + ), + ), + ], + ), + ), + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.all(4), + child: Column( + children: [ + if (dayWorkouts.isEmpty) + Padding( + padding: const EdgeInsets.only(top: 16.0), + child: Center( + child: Text( + "Rest", + style: TextStyle( + color: Theme.of( + context, + ).colorScheme.onSurfaceVariant, + fontSize: 12, + ), + ), + ), + ) + else + ...dayWorkouts.map( + (w) => Card( + margin: const EdgeInsets.only( + bottom: 4, + ), + child: ListTile( + contentPadding: + const EdgeInsets.symmetric( + horizontal: 8, + vertical: 0, + ), + visualDensity: VisualDensity.compact, + title: Text( + w.name ?? 'Untitled', + style: const TextStyle( + fontSize: 12, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + trailing: InkWell( + onTap: () => onDeleteWorkout(w.id), + borderRadius: BorderRadius.circular( + 12, + ), + child: const Icon( + Icons.close, + size: 14, + ), + ), + onTap: () { + // Optional: Edit on tap + }, + ), + ), + ), + ], + ), + ), + ), + ], + ), + ), + ); + }), + ), + ), + ], + ), + ), + ); + } + + String _getDayName(int day) { + const days = [ + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', + 'Sunday', + ]; + if (day >= 1 && day <= 7) return days[day - 1]; + return 'Day $day'; + } + + void _showAddWorkoutSheet(BuildContext context, int dayNum) { + showModalBottomSheet( + context: context, + builder: (context) => Container( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 8.0, + ), + child: Text( + "Select Training Plan", + style: Theme.of(context).textTheme.titleMedium, + ), + ), + const Divider(), + if (availablePlans.isEmpty) + const Padding( + padding: EdgeInsets.all(16.0), + child: Center( + child: Text("No training plans available. Create one first!"), + ), + ), + ...availablePlans + .map( + (plan) => ListTile( + leading: const Icon(Icons.fitness_center), + title: Text(plan.name), + subtitle: Text("${plan.totalExercises} exercises"), + onTap: () { + final newWorkout = ProgramWorkoutEntity( + id: IdGenerator.generate(), + programId: week.programId, + weekId: week.id, + day: dayNum.toString(), + type: 'workout', + name: plan.name, + refId: plan.id, + description: "${plan.sections.length} sections", + completed: false, + ); + onAddWorkout(newWorkout); + Navigator.pop(context); + }, + ), + ) + .toList(), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/chat/chat_controller.dart b/lib/presentation/chat/chat_controller.dart new file mode 100644 index 0000000..6e6f3a3 --- /dev/null +++ b/lib/presentation/chat/chat_controller.dart @@ -0,0 +1,109 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:trainhub_flutter/injection.dart'; +import 'package:trainhub_flutter/domain/repositories/chat_repository.dart'; +import 'package:trainhub_flutter/presentation/chat/chat_state.dart'; + +part 'chat_controller.g.dart'; + +@riverpod +class ChatController extends _$ChatController { + late final ChatRepository _repo; + + @override + Future build() async { + _repo = getIt(); + final sessions = await _repo.getAllSessions(); + return ChatState(sessions: sessions); + } + + Future createSession() async { + final session = await _repo.createSession(); + final sessions = await _repo.getAllSessions(); + state = AsyncValue.data( + ChatState(sessions: sessions, activeSession: session), + ); + } + + Future loadSession(String id) async { + final session = await _repo.getSession(id); + if (session == null) return; + final messages = await _repo.getMessages(id); + final current = state.valueOrNull ?? const ChatState(); + state = AsyncValue.data( + current.copyWith(activeSession: session, messages: messages), + ); + } + + Future deleteSession(String id) async { + await _repo.deleteSession(id); + final sessions = await _repo.getAllSessions(); + final current = state.valueOrNull ?? const ChatState(); + state = AsyncValue.data( + current.copyWith( + sessions: sessions, + activeSession: + current.activeSession?.id == id ? null : current.activeSession, + messages: current.activeSession?.id == id ? [] : current.messages, + ), + ); + } + + Future sendMessage(String content) async { + final current = state.valueOrNull; + if (current == null) return; + String sessionId; + if (current.activeSession == null) { + final session = await _repo.createSession(); + sessionId = session.id; + final sessions = await _repo.getAllSessions(); + state = AsyncValue.data( + current.copyWith(sessions: sessions, activeSession: session), + ); + } else { + sessionId = current.activeSession!.id; + } + await _repo.addMessage( + sessionId: sessionId, + role: 'user', + content: content, + ); + final messagesAfterUser = await _repo.getMessages(sessionId); + state = AsyncValue.data( + state.valueOrNull!.copyWith(messages: messagesAfterUser, isTyping: true), + ); + await Future.delayed(const Duration(seconds: 1)); + final String response = _getMockResponse(content); + await _repo.addMessage( + sessionId: sessionId, + role: 'assistant', + content: response, + ); + final messagesAfterAi = await _repo.getMessages(sessionId); + if (messagesAfterAi.length <= 2) { + final title = content.length > 30 + ? '${content.substring(0, 30)}...' + : content; + await _repo.updateSessionTitle(sessionId, title); + } + final sessions = await _repo.getAllSessions(); + state = AsyncValue.data( + state.valueOrNull!.copyWith( + messages: messagesAfterAi, + isTyping: false, + sessions: sessions, + ), + ); + } + + String _getMockResponse(String input) { + final String lower = input.toLowerCase(); + if (lower.contains('plan') || lower.contains('program')) { + return "I can help you design a training plan! What are your goals? Strength, hypertrophy, or endurance?"; + } else if (lower.contains('squat') || lower.contains('bench')) { + return "Compound movements are great. Remember to maintain proper form. For squats, keep your chest up and knees tracking over toes."; + } else if (lower.contains('nutrition') || lower.contains('eat')) { + return "Nutrition is key. Aim for 1.6-2.2g of protein per kg of bodyweight if you're training hard."; + } + return "I'm your AI training assistant. How can I help you today?"; + } +} diff --git a/lib/presentation/chat/chat_controller.g.dart b/lib/presentation/chat/chat_controller.g.dart new file mode 100644 index 0000000..adc9aff --- /dev/null +++ b/lib/presentation/chat/chat_controller.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'chat_controller.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$chatControllerHash() => r'44a3d0e906eaad16f7a9c292fe847b8bd144c835'; + +/// See also [ChatController]. +@ProviderFor(ChatController) +final chatControllerProvider = + AutoDisposeAsyncNotifierProvider.internal( + ChatController.new, + name: r'chatControllerProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$chatControllerHash, + dependencies: null, + allTransitiveDependencies: null, + ); + +typedef _$ChatController = AutoDisposeAsyncNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/presentation/chat/chat_page.dart b/lib/presentation/chat/chat_page.dart new file mode 100644 index 0000000..4fcb123 --- /dev/null +++ b/lib/presentation/chat/chat_page.dart @@ -0,0 +1,766 @@ +import 'dart:math' as math; + +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:trainhub_flutter/core/constants/ui_constants.dart'; +import 'package:trainhub_flutter/core/theme/app_colors.dart'; +import 'package:trainhub_flutter/domain/entities/chat_message.dart'; +import 'package:trainhub_flutter/domain/entities/chat_session.dart'; +import 'package:trainhub_flutter/presentation/chat/chat_controller.dart'; +import 'package:trainhub_flutter/presentation/chat/chat_state.dart'; + +@RoutePage() +class ChatPage extends ConsumerStatefulWidget { + const ChatPage({super.key}); + + @override + ConsumerState createState() => _ChatPageState(); +} + +class _ChatPageState extends ConsumerState { + final TextEditingController _inputController = TextEditingController(); + final ScrollController _scrollController = ScrollController(); + final FocusNode _inputFocusNode = FocusNode(); + String? _hoveredSessionId; + + @override + void dispose() { + _inputController.dispose(); + _scrollController.dispose(); + _inputFocusNode.dispose(); + super.dispose(); + } + + void _scrollToBottom() { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (_scrollController.hasClients) { + _scrollController.animateTo( + _scrollController.position.maxScrollExtent, + duration: const Duration(milliseconds: 300), + curve: Curves.easeOut, + ); + } + }); + } + + void _sendMessage(ChatController controller) { + final text = _inputController.text.trim(); + if (text.isNotEmpty) { + controller.sendMessage(text); + _inputController.clear(); + _inputFocusNode.requestFocus(); + } + } + + String _formatTimestamp(String timestamp) { + try { + final dt = DateTime.parse(timestamp); + final now = DateTime.now(); + final hour = dt.hour.toString().padLeft(2, '0'); + final minute = dt.minute.toString().padLeft(2, '0'); + if (dt.year == now.year && + dt.month == now.month && + dt.day == now.day) { + return '$hour:$minute'; + } + return '${dt.day}/${dt.month} $hour:$minute'; + } catch (_) { + return ''; + } + } + + @override + Widget build(BuildContext context) { + final state = ref.watch(chatControllerProvider); + final controller = ref.read(chatControllerProvider.notifier); + + ref.listen(chatControllerProvider, (prev, next) { + if (next.hasValue && + (prev?.value?.messages.length ?? 0) < + next.value!.messages.length) { + _scrollToBottom(); + } + if (next.hasValue && next.value!.isTyping && !(prev?.value?.isTyping ?? false)) { + _scrollToBottom(); + } + }); + + return Scaffold( + backgroundColor: AppColors.surface, + body: Row( + children: [ + // --- Side Panel --- + _buildSidePanel(state, controller), + + // --- Main Chat Area --- + Expanded( + child: _buildChatArea(state, controller), + ), + ], + ), + ); + } + + // --------------------------------------------------------------------------- + // Side Panel + // --------------------------------------------------------------------------- + Widget _buildSidePanel( + AsyncValue asyncState, + ChatController controller, + ) { + return Container( + width: 250, + decoration: const BoxDecoration( + color: AppColors.surfaceContainer, + border: Border( + right: BorderSide(color: AppColors.border, width: 1), + ), + ), + child: Column( + children: [ + // New Chat button + Padding( + padding: const EdgeInsets.all(UIConstants.spacing12), + child: SizedBox( + width: double.infinity, + child: _NewChatButton(onPressed: controller.createSession), + ), + ), + + const Divider(height: 1, color: AppColors.border), + + // Session list + Expanded( + child: asyncState.when( + data: (data) { + if (data.sessions.isEmpty) { + return Center( + child: Padding( + padding: const EdgeInsets.all(UIConstants.spacing24), + child: Text( + 'No conversations yet', + style: TextStyle( + color: AppColors.textMuted, + fontSize: 13, + ), + ), + ), + ); + } + return ListView.builder( + padding: const EdgeInsets.symmetric( + vertical: UIConstants.spacing8, + ), + itemCount: data.sessions.length, + itemBuilder: (context, index) { + final session = data.sessions[index]; + final isActive = + session.id == data.activeSession?.id; + return _buildSessionTile( + session: session, + isActive: isActive, + controller: controller, + ); + }, + ); + }, + error: (_, __) => Center( + child: Text( + 'Error loading sessions', + style: TextStyle(color: AppColors.textMuted, fontSize: 13), + ), + ), + loading: () => const Center( + child: SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + color: AppColors.textMuted, + ), + ), + ), + ), + ), + ], + ), + ); + } + + Widget _buildSessionTile({ + required ChatSessionEntity session, + required bool isActive, + required ChatController controller, + }) { + final isHovered = _hoveredSessionId == session.id; + + return MouseRegion( + onEnter: (_) => setState(() => _hoveredSessionId = session.id), + onExit: (_) => setState(() { + if (_hoveredSessionId == session.id) { + _hoveredSessionId = null; + } + }), + child: GestureDetector( + onTap: () => controller.loadSession(session.id), + child: AnimatedContainer( + duration: const Duration(milliseconds: 150), + margin: const EdgeInsets.symmetric( + horizontal: UIConstants.spacing8, + vertical: 2, + ), + padding: const EdgeInsets.symmetric( + horizontal: UIConstants.spacing12, + vertical: UIConstants.spacing8, + ), + decoration: BoxDecoration( + color: isActive + ? AppColors.zinc700.withValues(alpha: 0.7) + : isHovered + ? AppColors.zinc800.withValues(alpha: 0.6) + : Colors.transparent, + borderRadius: BorderRadius.circular(UIConstants.smallBorderRadius), + border: isActive + ? Border.all( + color: AppColors.accent.withValues(alpha: 0.3), + width: 1, + ) + : null, + ), + child: Row( + children: [ + Icon( + Icons.chat_bubble_outline_rounded, + size: 14, + color: isActive ? AppColors.accent : AppColors.textMuted, + ), + const SizedBox(width: UIConstants.spacing8), + Expanded( + child: Text( + session.title ?? 'New Chat', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: isActive + ? AppColors.textPrimary + : AppColors.textSecondary, + fontSize: 13, + fontWeight: + isActive ? FontWeight.w500 : FontWeight.normal, + ), + ), + ), + // Delete button appears on hover + AnimatedOpacity( + duration: const Duration(milliseconds: 150), + opacity: isHovered ? 1.0 : 0.0, + child: IgnorePointer( + ignoring: !isHovered, + child: SizedBox( + width: 24, + height: 24, + child: IconButton( + padding: EdgeInsets.zero, + iconSize: 14, + splashRadius: 14, + icon: const Icon( + Icons.delete_outline_rounded, + color: AppColors.textMuted, + ), + onPressed: () => controller.deleteSession(session.id), + tooltip: 'Delete', + ), + ), + ), + ), + ], + ), + ), + ), + ); + } + + // --------------------------------------------------------------------------- + // Chat Area + // --------------------------------------------------------------------------- + Widget _buildChatArea( + AsyncValue asyncState, + ChatController controller, + ) { + return Column( + children: [ + // Messages + Expanded( + child: asyncState.when( + data: (data) { + if (data.messages.isEmpty) { + return _buildEmptyState(); + } + return ListView.builder( + controller: _scrollController, + padding: const EdgeInsets.symmetric( + horizontal: UIConstants.spacing24, + vertical: UIConstants.spacing16, + ), + itemCount: + data.messages.length + (data.isTyping ? 1 : 0), + itemBuilder: (context, index) { + if (index == data.messages.length) { + return const _TypingIndicator(); + } + final msg = data.messages[index]; + return _MessageBubble( + message: msg, + formattedTime: _formatTimestamp(msg.createdAt), + ); + }, + ); + }, + error: (e, _) => Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.error_outline_rounded, + color: AppColors.destructive, + size: 40, + ), + const SizedBox(height: UIConstants.spacing12), + Text( + 'Something went wrong', + style: TextStyle( + color: AppColors.textSecondary, + fontSize: 14, + ), + ), + ], + ), + ), + loading: () => const Center( + child: SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator( + strokeWidth: 2, + color: AppColors.textMuted, + ), + ), + ), + ), + ), + + // Input area + _buildInputBar(asyncState, controller), + ], + ); + } + + Widget _buildEmptyState() { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: 64, + height: 64, + decoration: BoxDecoration( + color: AppColors.surfaceContainerHigh, + borderRadius: BorderRadius.circular(16), + ), + child: const Icon( + Icons.auto_awesome_outlined, + size: 32, + color: AppColors.textMuted, + ), + ), + const SizedBox(height: UIConstants.spacing16), + const Text( + 'Ask me anything about your training!', + style: TextStyle( + color: AppColors.textSecondary, + fontSize: 15, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: UIConstants.spacing8), + const Text( + 'Start a conversation to get personalized advice.', + style: TextStyle( + color: AppColors.textMuted, + fontSize: 13, + ), + ), + ], + ), + ); + } + + // --------------------------------------------------------------------------- + // Input Bar + // --------------------------------------------------------------------------- + Widget _buildInputBar( + AsyncValue asyncState, + ChatController controller, + ) { + final isTyping = asyncState.valueOrNull?.isTyping ?? false; + + return Container( + decoration: const BoxDecoration( + color: AppColors.surface, + border: Border( + top: BorderSide(color: AppColors.border, width: 1), + ), + ), + padding: const EdgeInsets.symmetric( + horizontal: UIConstants.spacing16, + vertical: UIConstants.spacing12, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Expanded( + child: Container( + decoration: BoxDecoration( + color: AppColors.surfaceContainer, + borderRadius: + BorderRadius.circular(UIConstants.borderRadius), + border: Border.all(color: AppColors.border, width: 1), + ), + child: TextField( + controller: _inputController, + focusNode: _inputFocusNode, + style: const TextStyle( + color: AppColors.textPrimary, + fontSize: 14, + ), + maxLines: 4, + minLines: 1, + decoration: InputDecoration( + hintText: 'Type a message...', + hintStyle: TextStyle( + color: AppColors.textMuted, + fontSize: 14, + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: UIConstants.spacing16, + vertical: UIConstants.spacing12, + ), + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + ), + onSubmitted: (_) => _sendMessage(controller), + textInputAction: TextInputAction.send, + ), + ), + ), + const SizedBox(width: UIConstants.spacing8), + SizedBox( + width: 40, + height: 40, + child: Material( + color: isTyping ? AppColors.zinc700 : AppColors.accent, + borderRadius: + BorderRadius.circular(UIConstants.borderRadius), + child: InkWell( + borderRadius: + BorderRadius.circular(UIConstants.borderRadius), + onTap: isTyping ? null : () => _sendMessage(controller), + child: Icon( + Icons.arrow_upward_rounded, + color: isTyping ? AppColors.textMuted : AppColors.zinc950, + size: 20, + ), + ), + ), + ), + ], + ), + ); + } +} + +// ============================================================================= +// New Chat Button +// ============================================================================= +class _NewChatButton extends StatefulWidget { + const _NewChatButton({required this.onPressed}); + + final VoidCallback onPressed; + + @override + State<_NewChatButton> createState() => _NewChatButtonState(); +} + +class _NewChatButtonState extends State<_NewChatButton> { + bool _isHovered = false; + + @override + Widget build(BuildContext context) { + return MouseRegion( + onEnter: (_) => setState(() => _isHovered = true), + onExit: (_) => setState(() => _isHovered = false), + child: AnimatedContainer( + duration: const Duration(milliseconds: 150), + decoration: BoxDecoration( + color: _isHovered + ? AppColors.zinc700 + : AppColors.surfaceContainerHigh, + borderRadius: BorderRadius.circular(UIConstants.smallBorderRadius), + border: Border.all(color: AppColors.border, width: 1), + ), + child: Material( + color: Colors.transparent, + child: InkWell( + borderRadius: + BorderRadius.circular(UIConstants.smallBorderRadius), + onTap: widget.onPressed, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: UIConstants.spacing12, + vertical: UIConstants.spacing8, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + Icon( + Icons.add_rounded, + size: 16, + color: AppColors.textSecondary, + ), + SizedBox(width: UIConstants.spacing8), + Text( + 'New Chat', + style: TextStyle( + color: AppColors.textSecondary, + fontSize: 13, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ), + ), + ), + ); + } +} + +// ============================================================================= +// Message Bubble +// ============================================================================= +class _MessageBubble extends StatelessWidget { + const _MessageBubble({ + required this.message, + required this.formattedTime, + }); + + final ChatMessageEntity message; + final String formattedTime; + + @override + Widget build(BuildContext context) { + final isUser = message.isUser; + final maxWidth = MediaQuery.of(context).size.width * 0.55; + + return Padding( + padding: const EdgeInsets.only(bottom: UIConstants.spacing12), + child: Row( + mainAxisAlignment: + isUser ? MainAxisAlignment.end : MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (!isUser) ...[ + Container( + width: 28, + height: 28, + decoration: BoxDecoration( + color: AppColors.surfaceContainerHigh, + borderRadius: BorderRadius.circular(8), + ), + child: const Icon( + Icons.auto_awesome_rounded, + size: 14, + color: AppColors.accent, + ), + ), + const SizedBox(width: UIConstants.spacing8), + ], + Flexible( + child: Column( + crossAxisAlignment: + isUser ? CrossAxisAlignment.end : CrossAxisAlignment.start, + children: [ + Container( + constraints: BoxConstraints(maxWidth: maxWidth), + padding: const EdgeInsets.symmetric( + horizontal: UIConstants.spacing16, + vertical: UIConstants.spacing12, + ), + decoration: BoxDecoration( + color: isUser + ? AppColors.zinc700 + : AppColors.surfaceContainer, + borderRadius: BorderRadius.only( + topLeft: const Radius.circular(16), + topRight: const Radius.circular(16), + bottomLeft: + isUser ? const Radius.circular(16) : const Radius.circular(4), + bottomRight: + isUser ? const Radius.circular(4) : const Radius.circular(16), + ), + border: isUser + ? null + : Border.all(color: AppColors.border, width: 1), + ), + child: SelectableText( + message.content, + style: TextStyle( + color: AppColors.textPrimary, + fontSize: 14, + height: 1.5, + ), + ), + ), + const SizedBox(height: 4), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Text( + formattedTime, + style: const TextStyle( + color: AppColors.textMuted, + fontSize: 11, + ), + ), + ), + ], + ), + ), + if (isUser) ...[ + const SizedBox(width: UIConstants.spacing8), + Container( + width: 28, + height: 28, + decoration: BoxDecoration( + color: AppColors.accent.withValues(alpha: 0.15), + borderRadius: BorderRadius.circular(8), + ), + child: const Icon( + Icons.person_rounded, + size: 14, + color: AppColors.accent, + ), + ), + ], + ], + ), + ); + } +} + +// ============================================================================= +// Typing Indicator (3 animated bouncing dots) +// ============================================================================= +class _TypingIndicator extends StatefulWidget { + const _TypingIndicator(); + + @override + State<_TypingIndicator> createState() => _TypingIndicatorState(); +} + +class _TypingIndicatorState extends State<_TypingIndicator> + with SingleTickerProviderStateMixin { + late final AnimationController _controller; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 1200), + )..repeat(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(bottom: UIConstants.spacing12), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 28, + height: 28, + decoration: BoxDecoration( + color: AppColors.surfaceContainerHigh, + borderRadius: BorderRadius.circular(8), + ), + child: const Icon( + Icons.auto_awesome_rounded, + size: 14, + color: AppColors.accent, + ), + ), + const SizedBox(width: UIConstants.spacing8), + Container( + padding: const EdgeInsets.symmetric( + horizontal: UIConstants.spacing16, + vertical: UIConstants.spacing12, + ), + decoration: BoxDecoration( + color: AppColors.surfaceContainer, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + bottomLeft: Radius.circular(4), + bottomRight: Radius.circular(16), + ), + border: Border.all(color: AppColors.border, width: 1), + ), + child: AnimatedBuilder( + animation: _controller, + builder: (context, _) { + return Row( + mainAxisSize: MainAxisSize.min, + children: List.generate(3, (index) { + // Stagger each dot by 0.2 of the animation cycle + final delay = index * 0.2; + final t = (_controller.value - delay) % 1.0; + // Bounce: use a sin curve over the first half, rest at 0 + final bounce = + t < 0.5 ? math.sin(t * math.pi * 2) * 4.0 : 0.0; + + return Padding( + padding: EdgeInsets.only( + left: index == 0 ? 0 : 4, + ), + child: Transform.translate( + offset: Offset(0, -bounce.abs()), + child: Container( + width: 7, + height: 7, + decoration: BoxDecoration( + color: AppColors.textMuted, + shape: BoxShape.circle, + ), + ), + ), + ); + }), + ); + }, + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/chat/chat_state.dart b/lib/presentation/chat/chat_state.dart new file mode 100644 index 0000000..a7c1c00 --- /dev/null +++ b/lib/presentation/chat/chat_state.dart @@ -0,0 +1,15 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:trainhub_flutter/domain/entities/chat_session.dart'; +import 'package:trainhub_flutter/domain/entities/chat_message.dart'; + +part 'chat_state.freezed.dart'; + +@freezed +class ChatState with _$ChatState { + const factory ChatState({ + @Default([]) List sessions, + ChatSessionEntity? activeSession, + @Default([]) List messages, + @Default(false) bool isTyping, + }) = _ChatState; +} diff --git a/lib/presentation/chat/chat_state.freezed.dart b/lib/presentation/chat/chat_state.freezed.dart new file mode 100644 index 0000000..00d402d --- /dev/null +++ b/lib/presentation/chat/chat_state.freezed.dart @@ -0,0 +1,261 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'chat_state.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$ChatState { + List get sessions => throw _privateConstructorUsedError; + ChatSessionEntity? get activeSession => throw _privateConstructorUsedError; + List get messages => throw _privateConstructorUsedError; + bool get isTyping => throw _privateConstructorUsedError; + + /// Create a copy of ChatState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ChatStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ChatStateCopyWith<$Res> { + factory $ChatStateCopyWith(ChatState value, $Res Function(ChatState) then) = + _$ChatStateCopyWithImpl<$Res, ChatState>; + @useResult + $Res call({ + List sessions, + ChatSessionEntity? activeSession, + List messages, + bool isTyping, + }); + + $ChatSessionEntityCopyWith<$Res>? get activeSession; +} + +/// @nodoc +class _$ChatStateCopyWithImpl<$Res, $Val extends ChatState> + implements $ChatStateCopyWith<$Res> { + _$ChatStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ChatState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? sessions = null, + Object? activeSession = freezed, + Object? messages = null, + Object? isTyping = null, + }) { + return _then( + _value.copyWith( + sessions: null == sessions + ? _value.sessions + : sessions // ignore: cast_nullable_to_non_nullable + as List, + activeSession: freezed == activeSession + ? _value.activeSession + : activeSession // ignore: cast_nullable_to_non_nullable + as ChatSessionEntity?, + messages: null == messages + ? _value.messages + : messages // ignore: cast_nullable_to_non_nullable + as List, + isTyping: null == isTyping + ? _value.isTyping + : isTyping // ignore: cast_nullable_to_non_nullable + as bool, + ) + as $Val, + ); + } + + /// Create a copy of ChatState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $ChatSessionEntityCopyWith<$Res>? get activeSession { + if (_value.activeSession == null) { + return null; + } + + return $ChatSessionEntityCopyWith<$Res>(_value.activeSession!, (value) { + return _then(_value.copyWith(activeSession: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$ChatStateImplCopyWith<$Res> + implements $ChatStateCopyWith<$Res> { + factory _$$ChatStateImplCopyWith( + _$ChatStateImpl value, + $Res Function(_$ChatStateImpl) then, + ) = __$$ChatStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + List sessions, + ChatSessionEntity? activeSession, + List messages, + bool isTyping, + }); + + @override + $ChatSessionEntityCopyWith<$Res>? get activeSession; +} + +/// @nodoc +class __$$ChatStateImplCopyWithImpl<$Res> + extends _$ChatStateCopyWithImpl<$Res, _$ChatStateImpl> + implements _$$ChatStateImplCopyWith<$Res> { + __$$ChatStateImplCopyWithImpl( + _$ChatStateImpl _value, + $Res Function(_$ChatStateImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ChatState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? sessions = null, + Object? activeSession = freezed, + Object? messages = null, + Object? isTyping = null, + }) { + return _then( + _$ChatStateImpl( + sessions: null == sessions + ? _value._sessions + : sessions // ignore: cast_nullable_to_non_nullable + as List, + activeSession: freezed == activeSession + ? _value.activeSession + : activeSession // ignore: cast_nullable_to_non_nullable + as ChatSessionEntity?, + messages: null == messages + ? _value._messages + : messages // ignore: cast_nullable_to_non_nullable + as List, + isTyping: null == isTyping + ? _value.isTyping + : isTyping // ignore: cast_nullable_to_non_nullable + as bool, + ), + ); + } +} + +/// @nodoc + +class _$ChatStateImpl implements _ChatState { + const _$ChatStateImpl({ + final List sessions = const [], + this.activeSession, + final List messages = const [], + this.isTyping = false, + }) : _sessions = sessions, + _messages = messages; + + final List _sessions; + @override + @JsonKey() + List get sessions { + if (_sessions is EqualUnmodifiableListView) return _sessions; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_sessions); + } + + @override + final ChatSessionEntity? activeSession; + final List _messages; + @override + @JsonKey() + List get messages { + if (_messages is EqualUnmodifiableListView) return _messages; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_messages); + } + + @override + @JsonKey() + final bool isTyping; + + @override + String toString() { + return 'ChatState(sessions: $sessions, activeSession: $activeSession, messages: $messages, isTyping: $isTyping)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ChatStateImpl && + const DeepCollectionEquality().equals(other._sessions, _sessions) && + (identical(other.activeSession, activeSession) || + other.activeSession == activeSession) && + const DeepCollectionEquality().equals(other._messages, _messages) && + (identical(other.isTyping, isTyping) || + other.isTyping == isTyping)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_sessions), + activeSession, + const DeepCollectionEquality().hash(_messages), + isTyping, + ); + + /// Create a copy of ChatState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ChatStateImplCopyWith<_$ChatStateImpl> get copyWith => + __$$ChatStateImplCopyWithImpl<_$ChatStateImpl>(this, _$identity); +} + +abstract class _ChatState implements ChatState { + const factory _ChatState({ + final List sessions, + final ChatSessionEntity? activeSession, + final List messages, + final bool isTyping, + }) = _$ChatStateImpl; + + @override + List get sessions; + @override + ChatSessionEntity? get activeSession; + @override + List get messages; + @override + bool get isTyping; + + /// Create a copy of ChatState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ChatStateImplCopyWith<_$ChatStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/presentation/common/dialogs/confirm_dialog.dart b/lib/presentation/common/dialogs/confirm_dialog.dart new file mode 100644 index 0000000..66485dc --- /dev/null +++ b/lib/presentation/common/dialogs/confirm_dialog.dart @@ -0,0 +1,105 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:trainhub_flutter/core/constants/ui_constants.dart'; +import 'package:trainhub_flutter/core/theme/app_colors.dart'; + +class ConfirmDialog { + ConfirmDialog._(); + + static Future show( + BuildContext context, { + required String title, + required String message, + String confirmLabel = 'Delete', + Color? confirmColor, + }) { + final color = confirmColor ?? AppColors.destructive; + + return showDialog( + context: context, + builder: (context) => Dialog( + backgroundColor: AppColors.surfaceContainer, + shape: RoundedRectangleBorder( + borderRadius: UIConstants.cardBorderRadius, + side: const BorderSide(color: AppColors.border), + ), + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: UIConstants.dialogWidth), + child: Padding( + padding: const EdgeInsets.all(UIConstants.pagePadding), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: GoogleFonts.inter( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), + ), + const SizedBox(height: UIConstants.spacing8), + Text( + message, + style: GoogleFonts.inter( + fontSize: 14, + color: AppColors.textSecondary, + ), + ), + const SizedBox(height: UIConstants.spacing24), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + style: TextButton.styleFrom( + foregroundColor: AppColors.textSecondary, + shape: RoundedRectangleBorder( + borderRadius: UIConstants.smallCardBorderRadius, + ), + padding: const EdgeInsets.symmetric( + horizontal: UIConstants.spacing16, + vertical: UIConstants.spacing12, + ), + ), + child: Text( + 'Cancel', + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + ), + const SizedBox(width: UIConstants.spacing8), + FilledButton( + onPressed: () => Navigator.of(context).pop(true), + style: FilledButton.styleFrom( + backgroundColor: color, + foregroundColor: AppColors.zinc50, + shape: RoundedRectangleBorder( + borderRadius: UIConstants.smallCardBorderRadius, + ), + padding: const EdgeInsets.symmetric( + horizontal: UIConstants.spacing16, + vertical: UIConstants.spacing12, + ), + ), + child: Text( + confirmLabel, + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/presentation/common/dialogs/text_input_dialog.dart b/lib/presentation/common/dialogs/text_input_dialog.dart new file mode 100644 index 0000000..2cc6b7a --- /dev/null +++ b/lib/presentation/common/dialogs/text_input_dialog.dart @@ -0,0 +1,143 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:trainhub_flutter/core/constants/ui_constants.dart'; +import 'package:trainhub_flutter/core/theme/app_colors.dart'; + +class TextInputDialog { + TextInputDialog._(); + + static Future show( + BuildContext context, { + required String title, + String? hintText, + String? initialValue, + String confirmLabel = 'Create', + }) { + final controller = TextEditingController(text: initialValue); + + return showDialog( + context: context, + builder: (context) => Dialog( + backgroundColor: AppColors.surfaceContainer, + shape: RoundedRectangleBorder( + borderRadius: UIConstants.cardBorderRadius, + side: const BorderSide(color: AppColors.border), + ), + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: UIConstants.dialogWidth), + child: Padding( + padding: const EdgeInsets.all(UIConstants.pagePadding), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: GoogleFonts.inter( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), + ), + const SizedBox(height: UIConstants.spacing16), + TextField( + controller: controller, + autofocus: true, + style: GoogleFonts.inter( + fontSize: 14, + color: AppColors.textPrimary, + ), + cursorColor: AppColors.accent, + decoration: InputDecoration( + hintText: hintText, + hintStyle: GoogleFonts.inter( + fontSize: 14, + color: AppColors.textMuted, + ), + filled: true, + fillColor: AppColors.zinc950, + contentPadding: const EdgeInsets.symmetric( + horizontal: UIConstants.spacing12, + vertical: UIConstants.spacing12, + ), + border: OutlineInputBorder( + borderRadius: UIConstants.smallCardBorderRadius, + borderSide: const BorderSide(color: AppColors.border), + ), + enabledBorder: OutlineInputBorder( + borderRadius: UIConstants.smallCardBorderRadius, + borderSide: const BorderSide(color: AppColors.border), + ), + focusedBorder: OutlineInputBorder( + borderRadius: UIConstants.smallCardBorderRadius, + borderSide: const BorderSide(color: AppColors.accent), + ), + ), + onSubmitted: (value) { + final text = value.trim(); + if (text.isNotEmpty) { + Navigator.of(context).pop(text); + } + }, + ), + const SizedBox(height: UIConstants.spacing24), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + style: TextButton.styleFrom( + foregroundColor: AppColors.textSecondary, + shape: RoundedRectangleBorder( + borderRadius: UIConstants.smallCardBorderRadius, + ), + padding: const EdgeInsets.symmetric( + horizontal: UIConstants.spacing16, + vertical: UIConstants.spacing12, + ), + ), + child: Text( + 'Cancel', + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + ), + const SizedBox(width: UIConstants.spacing8), + FilledButton( + onPressed: () { + final text = controller.text.trim(); + if (text.isNotEmpty) { + Navigator.of(context).pop(text); + } + }, + style: FilledButton.styleFrom( + backgroundColor: AppColors.accent, + foregroundColor: AppColors.zinc950, + shape: RoundedRectangleBorder( + borderRadius: UIConstants.smallCardBorderRadius, + ), + padding: const EdgeInsets.symmetric( + horizontal: UIConstants.spacing16, + vertical: UIConstants.spacing12, + ), + ), + child: Text( + confirmLabel, + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/presentation/common/widgets/app_empty_state.dart b/lib/presentation/common/widgets/app_empty_state.dart new file mode 100644 index 0000000..551af34 --- /dev/null +++ b/lib/presentation/common/widgets/app_empty_state.dart @@ -0,0 +1,94 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:trainhub_flutter/core/constants/ui_constants.dart'; +import 'package:trainhub_flutter/core/theme/app_colors.dart'; + +class AppEmptyState extends StatelessWidget { + const AppEmptyState({ + super.key, + required this.icon, + required this.title, + this.subtitle, + this.actionLabel, + this.onAction, + }); + + final IconData icon; + final String title; + final String? subtitle; + final String? actionLabel; + final VoidCallback? onAction; + + @override + Widget build(BuildContext context) { + return Center( + child: Padding( + padding: const EdgeInsets.all(UIConstants.pagePadding), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 64, + height: 64, + decoration: BoxDecoration( + color: AppColors.surfaceContainer, + borderRadius: BorderRadius.circular(UIConstants.borderRadius), + border: Border.all(color: AppColors.border), + ), + child: Icon( + icon, + size: 28, + color: AppColors.textMuted, + ), + ), + const SizedBox(height: UIConstants.spacing16), + Text( + title, + style: GoogleFonts.inter( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), + textAlign: TextAlign.center, + ), + if (subtitle != null) ...[ + const SizedBox(height: UIConstants.spacing8), + Text( + subtitle!, + style: GoogleFonts.inter( + fontSize: 14, + color: AppColors.textSecondary, + ), + textAlign: TextAlign.center, + ), + ], + if (actionLabel != null && onAction != null) ...[ + const SizedBox(height: UIConstants.spacing24), + FilledButton( + onPressed: onAction, + style: FilledButton.styleFrom( + backgroundColor: AppColors.accent, + foregroundColor: AppColors.zinc950, + shape: RoundedRectangleBorder( + borderRadius: UIConstants.smallCardBorderRadius, + ), + padding: const EdgeInsets.symmetric( + horizontal: UIConstants.spacing24, + vertical: UIConstants.spacing12, + ), + ), + child: Text( + actionLabel!, + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ], + ), + ), + ); + } +} diff --git a/lib/presentation/common/widgets/app_stat_card.dart b/lib/presentation/common/widgets/app_stat_card.dart new file mode 100644 index 0000000..511afff --- /dev/null +++ b/lib/presentation/common/widgets/app_stat_card.dart @@ -0,0 +1,85 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:trainhub_flutter/core/constants/ui_constants.dart'; +import 'package:trainhub_flutter/core/theme/app_colors.dart'; + +class AppStatCard extends StatelessWidget { + const AppStatCard({ + super.key, + required this.title, + required this.value, + required this.accentColor, + this.icon, + }); + + final String title; + final String value; + final Color accentColor; + final IconData? icon; + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: AppColors.surfaceContainer, + borderRadius: UIConstants.cardBorderRadius, + border: Border.all(color: AppColors.border), + ), + child: ClipRRect( + borderRadius: UIConstants.cardBorderRadius, + child: Row( + children: [ + Container( + width: 4, + height: 72, + color: accentColor, + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: UIConstants.cardPadding, + vertical: UIConstants.spacing12, + ), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + title, + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w500, + color: AppColors.textMuted, + ), + ), + const SizedBox(height: UIConstants.spacing4), + Text( + value, + style: GoogleFonts.inter( + fontSize: 24, + fontWeight: FontWeight.w700, + color: AppColors.textPrimary, + ), + ), + ], + ), + ), + if (icon != null) + Icon( + icon, + size: 20, + color: accentColor, + ), + ], + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/home/home_controller.dart b/lib/presentation/home/home_controller.dart new file mode 100644 index 0000000..de35682 --- /dev/null +++ b/lib/presentation/home/home_controller.dart @@ -0,0 +1,32 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:trainhub_flutter/injection.dart'; +import 'package:trainhub_flutter/domain/repositories/program_repository.dart'; +import 'package:trainhub_flutter/presentation/home/home_state.dart'; + +part 'home_controller.g.dart'; + +@riverpod +class HomeController extends _$HomeController { + @override + Future build() async { + final ProgramRepository programRepo = getIt(); + final programs = await programRepo.getAllPrograms(); + if (programs.isEmpty) return const HomeState(); + final activeProgram = programs.first; + final workouts = await programRepo.getWorkouts(activeProgram.id); + final completed = workouts.where((w) => w.completed).toList(); + final next = workouts.where((w) => !w.completed).firstOrNull; + return HomeState( + activeProgramName: activeProgram.name, + completedWorkouts: completed.length, + totalWorkouts: workouts.length, + nextWorkoutName: next?.name, + recentActivity: completed.reversed.take(5).toList(), + ); + } + + Future refresh() async { + state = const AsyncValue.loading(); + state = await AsyncValue.guard(() => build()); + } +} diff --git a/lib/presentation/home/home_controller.g.dart b/lib/presentation/home/home_controller.g.dart new file mode 100644 index 0000000..3a02b08 --- /dev/null +++ b/lib/presentation/home/home_controller.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'home_controller.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$homeControllerHash() => r'9704c0237cda64f1c13b4fd6db4fbc6eca9988f8'; + +/// See also [HomeController]. +@ProviderFor(HomeController) +final homeControllerProvider = + AutoDisposeAsyncNotifierProvider.internal( + HomeController.new, + name: r'homeControllerProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$homeControllerHash, + dependencies: null, + allTransitiveDependencies: null, + ); + +typedef _$HomeController = AutoDisposeAsyncNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/presentation/home/home_page.dart b/lib/presentation/home/home_page.dart new file mode 100644 index 0000000..773bbb3 --- /dev/null +++ b/lib/presentation/home/home_page.dart @@ -0,0 +1,699 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:trainhub_flutter/core/constants/ui_constants.dart'; +import 'package:trainhub_flutter/core/theme/app_colors.dart'; +import 'package:trainhub_flutter/domain/entities/program_workout.dart'; +import 'package:trainhub_flutter/presentation/common/widgets/app_empty_state.dart'; +import 'package:trainhub_flutter/presentation/common/widgets/app_stat_card.dart'; +import 'package:trainhub_flutter/presentation/home/home_controller.dart'; +import 'package:trainhub_flutter/presentation/home/home_state.dart'; + +@RoutePage() +class HomePage extends ConsumerWidget { + const HomePage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final asyncState = ref.watch(homeControllerProvider); + + return asyncState.when( + loading: () => const Center(child: CircularProgressIndicator()), + error: (e, _) => Center( + child: Padding( + padding: const EdgeInsets.all(UIConstants.pagePadding), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.error_outline, + size: 48, + color: AppColors.destructive, + ), + const SizedBox(height: UIConstants.spacing16), + Text( + 'Something went wrong', + style: GoogleFonts.inter( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), + ), + const SizedBox(height: UIConstants.spacing8), + Text( + '$e', + style: GoogleFonts.inter( + fontSize: 13, + color: AppColors.textMuted, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: UIConstants.spacing24), + FilledButton.icon( + onPressed: () => + ref.read(homeControllerProvider.notifier).refresh(), + icon: const Icon(Icons.refresh, size: 18), + label: const Text('Retry'), + ), + ], + ), + ), + ), + data: (data) { + if (data.activeProgramName == null) { + return AppEmptyState( + icon: Icons.calendar_today_outlined, + title: 'No active program', + subtitle: + 'Head to Calendar to create or select a training program to get started.', + actionLabel: 'Go to Calendar', + onAction: () { + AutoTabsRouter.of(context).setActiveIndex(3); + }, + ); + } + return _HomeContent(data: data); + }, + ); + } +} + +class _HomeContent extends StatelessWidget { + final HomeState data; + + const _HomeContent({required this.data}); + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + padding: const EdgeInsets.all(UIConstants.pagePadding), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // -- Welcome header -- + _WelcomeHeader(programName: data.activeProgramName!), + const SizedBox(height: UIConstants.spacing24), + + // -- Stat cards row -- + _StatCardsRow( + completed: data.completedWorkouts, + total: data.totalWorkouts, + ), + const SizedBox(height: UIConstants.spacing24), + + // -- Next workout banner -- + if (data.nextWorkoutName != null) ...[ + _NextWorkoutBanner(workoutName: data.nextWorkoutName!), + const SizedBox(height: UIConstants.spacing24), + ], + + // -- Quick actions -- + _QuickActionsRow(), + const SizedBox(height: UIConstants.spacing32), + + // -- Recent activity -- + _RecentActivitySection(activity: data.recentActivity), + ], + ), + ); + } +} + +// --------------------------------------------------------------------------- +// Welcome header +// --------------------------------------------------------------------------- +class _WelcomeHeader extends StatelessWidget { + final String programName; + + const _WelcomeHeader({required this.programName}); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Welcome back', + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w500, + color: AppColors.textMuted, + ), + ), + const SizedBox(height: UIConstants.spacing4), + Row( + children: [ + Expanded( + child: Text( + programName, + style: GoogleFonts.inter( + fontSize: 28, + fontWeight: FontWeight.w700, + color: AppColors.textPrimary, + ), + ), + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: UIConstants.spacing12, + vertical: 6, + ), + decoration: BoxDecoration( + color: AppColors.accentMuted, + borderRadius: UIConstants.smallCardBorderRadius, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.fitness_center, + size: 14, + color: AppColors.accent, + ), + const SizedBox(width: 6), + Text( + 'Active Program', + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w600, + color: AppColors.accent, + ), + ), + ], + ), + ), + ], + ), + ], + ); + } +} + +// --------------------------------------------------------------------------- +// Stat cards row +// --------------------------------------------------------------------------- +class _StatCardsRow extends StatelessWidget { + final int completed; + final int total; + + const _StatCardsRow({required this.completed, required this.total}); + + @override + Widget build(BuildContext context) { + final progress = total == 0 ? 0 : (completed / total * 100).round(); + + return Row( + children: [ + Expanded( + child: AppStatCard( + title: 'Completed', + value: '$completed', + icon: Icons.check_circle_outline, + accentColor: AppColors.success, + ), + ), + const SizedBox(width: UIConstants.spacing16), + Expanded( + child: AppStatCard( + title: 'Total Workouts', + value: '$total', + icon: Icons.list_alt, + accentColor: AppColors.info, + ), + ), + const SizedBox(width: UIConstants.spacing16), + Expanded( + child: AppStatCard( + title: 'Progress', + value: '$progress%', + icon: Icons.trending_up, + accentColor: AppColors.purple, + ), + ), + ], + ); + } +} + +// --------------------------------------------------------------------------- +// Next workout banner +// --------------------------------------------------------------------------- +class _NextWorkoutBanner extends StatelessWidget { + final String workoutName; + + const _NextWorkoutBanner({required this.workoutName}); + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(UIConstants.cardPadding), + decoration: BoxDecoration( + color: AppColors.surfaceContainer, + borderRadius: UIConstants.cardBorderRadius, + border: Border.all(color: AppColors.border), + ), + child: Row( + children: [ + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: AppColors.accentMuted, + borderRadius: UIConstants.smallCardBorderRadius, + ), + child: const Icon( + Icons.play_arrow_rounded, + color: AppColors.accent, + size: 22, + ), + ), + const SizedBox(width: UIConstants.spacing12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Up Next', + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w500, + color: AppColors.textMuted, + ), + ), + const SizedBox(height: 2), + Text( + workoutName, + style: GoogleFonts.inter( + fontSize: 15, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), + ), + ], + ), + ), + Icon( + Icons.chevron_right, + color: AppColors.textMuted, + size: 20, + ), + ], + ), + ); + } +} + +// --------------------------------------------------------------------------- +// Quick actions row +// --------------------------------------------------------------------------- +class _QuickActionsRow extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Quick Actions', + style: GoogleFonts.inter( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), + ), + const SizedBox(height: UIConstants.spacing12), + Row( + children: [ + _QuickActionButton( + icon: Icons.play_arrow_rounded, + label: 'New Workout', + color: AppColors.accent, + onTap: () { + AutoTabsRouter.of(context).setActiveIndex(1); + }, + ), + const SizedBox(width: UIConstants.spacing12), + _QuickActionButton( + icon: Icons.description_outlined, + label: 'View Plans', + color: AppColors.info, + onTap: () { + AutoTabsRouter.of(context).setActiveIndex(1); + }, + ), + const SizedBox(width: UIConstants.spacing12), + _QuickActionButton( + icon: Icons.chat_bubble_outline, + label: 'AI Chat', + color: AppColors.purple, + onTap: () { + AutoTabsRouter.of(context).setActiveIndex(4); + }, + ), + ], + ), + ], + ); + } +} + +class _QuickActionButton extends StatefulWidget { + final IconData icon; + final String label; + final Color color; + final VoidCallback onTap; + + const _QuickActionButton({ + required this.icon, + required this.label, + required this.color, + required this.onTap, + }); + + @override + State<_QuickActionButton> createState() => _QuickActionButtonState(); +} + +class _QuickActionButtonState extends State<_QuickActionButton> { + bool _isHovered = false; + + @override + Widget build(BuildContext context) { + return MouseRegion( + onEnter: (_) => setState(() => _isHovered = true), + onExit: (_) => setState(() => _isHovered = false), + child: AnimatedContainer( + duration: UIConstants.animationDuration, + decoration: BoxDecoration( + color: _isHovered + ? widget.color.withValues(alpha: 0.08) + : Colors.transparent, + borderRadius: UIConstants.smallCardBorderRadius, + border: Border.all( + color: _isHovered ? widget.color.withValues(alpha: 0.4) : AppColors.border, + ), + ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: widget.onTap, + borderRadius: UIConstants.smallCardBorderRadius, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: UIConstants.spacing24, + vertical: UIConstants.spacing12, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + widget.icon, + size: 18, + color: widget.color, + ), + const SizedBox(width: UIConstants.spacing8), + Text( + widget.label, + style: GoogleFonts.inter( + fontSize: 13, + fontWeight: FontWeight.w500, + color: _isHovered + ? widget.color + : AppColors.textSecondary, + ), + ), + ], + ), + ), + ), + ), + ), + ); + } +} + +// --------------------------------------------------------------------------- +// Recent activity section +// --------------------------------------------------------------------------- +class _RecentActivitySection extends StatelessWidget { + final List activity; + + const _RecentActivitySection({required this.activity}); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + 'Recent Activity', + style: GoogleFonts.inter( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), + ), + ), + if (activity.isNotEmpty) + Text( + '${activity.length} workout${activity.length == 1 ? '' : 's'}', + style: GoogleFonts.inter( + fontSize: 12, + color: AppColors.textMuted, + ), + ), + ], + ), + const SizedBox(height: UIConstants.spacing12), + if (activity.isEmpty) + _EmptyActivity() + else + _ActivityList(activity: activity), + ], + ); + } +} + +class _EmptyActivity extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + padding: const EdgeInsets.symmetric( + vertical: 40, + horizontal: UIConstants.spacing24, + ), + decoration: BoxDecoration( + color: AppColors.surfaceContainer, + borderRadius: UIConstants.cardBorderRadius, + border: Border.all(color: AppColors.border), + ), + child: Column( + children: [ + Icon( + Icons.history, + size: 32, + color: AppColors.textMuted, + ), + const SizedBox(height: UIConstants.spacing12), + Text( + 'No completed workouts yet', + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w500, + color: AppColors.textSecondary, + ), + ), + const SizedBox(height: UIConstants.spacing4), + Text( + 'Your recent workout history will appear here.', + style: GoogleFonts.inter( + fontSize: 13, + color: AppColors.textMuted, + ), + ), + ], + ), + ); + } +} + +class _ActivityList extends StatelessWidget { + final List activity; + + const _ActivityList({required this.activity}); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: AppColors.surfaceContainer, + borderRadius: UIConstants.cardBorderRadius, + border: Border.all(color: AppColors.border), + ), + child: ClipRRect( + borderRadius: UIConstants.cardBorderRadius, + child: Column( + children: [ + for (int i = 0; i < activity.length; i++) ...[ + if (i > 0) + const Divider( + height: 1, + thickness: 1, + color: AppColors.border, + ), + _ActivityItem(workout: activity[i]), + ], + ], + ), + ), + ); + } +} + +class _ActivityItem extends StatefulWidget { + final ProgramWorkoutEntity workout; + + const _ActivityItem({required this.workout}); + + @override + State<_ActivityItem> createState() => _ActivityItemState(); +} + +class _ActivityItemState extends State<_ActivityItem> { + bool _isHovered = false; + + Color get _typeColor { + switch (widget.workout.type.toLowerCase()) { + case 'strength': + return AppColors.accent; + case 'cardio': + return AppColors.info; + case 'flexibility': + case 'mobility': + return AppColors.purple; + case 'rest': + return AppColors.textMuted; + default: + return AppColors.success; + } + } + + IconData get _typeIcon { + switch (widget.workout.type.toLowerCase()) { + case 'strength': + return Icons.fitness_center; + case 'cardio': + return Icons.directions_run; + case 'flexibility': + case 'mobility': + return Icons.self_improvement; + case 'rest': + return Icons.bedtime_outlined; + default: + return Icons.check_circle; + } + } + + @override + Widget build(BuildContext context) { + return MouseRegion( + onEnter: (_) => setState(() => _isHovered = true), + onExit: (_) => setState(() => _isHovered = false), + child: AnimatedContainer( + duration: UIConstants.animationDuration, + color: _isHovered + ? AppColors.surfaceContainerHigh.withValues(alpha: 0.5) + : Colors.transparent, + padding: const EdgeInsets.symmetric( + horizontal: UIConstants.cardPadding, + vertical: UIConstants.spacing12, + ), + child: Row( + children: [ + // Leading icon with color coding + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: widget.workout.completed + ? _typeColor.withValues(alpha: 0.15) + : AppColors.zinc800, + borderRadius: UIConstants.smallCardBorderRadius, + ), + child: Icon( + widget.workout.completed ? _typeIcon : Icons.circle_outlined, + size: 18, + color: widget.workout.completed + ? _typeColor + : AppColors.textMuted, + ), + ), + const SizedBox(width: UIConstants.spacing12), + + // Workout info + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.workout.name ?? 'Workout', + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w500, + color: AppColors.textPrimary, + ), + ), + const SizedBox(height: 2), + Text( + 'Week ${widget.workout.weekId} · Day ${widget.workout.day}', + style: GoogleFonts.inter( + fontSize: 12, + color: AppColors.textMuted, + ), + ), + ], + ), + ), + + // Type badge + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: _typeColor.withValues(alpha: 0.12), + borderRadius: BorderRadius.circular(6), + ), + child: Text( + widget.workout.type, + style: GoogleFonts.inter( + fontSize: 11, + fontWeight: FontWeight.w500, + color: _typeColor, + ), + ), + ), + const SizedBox(width: UIConstants.spacing12), + + // Status indicator + if (widget.workout.completed) + const Icon( + Icons.check_circle, + size: 18, + color: AppColors.success, + ) + else + const Icon( + Icons.radio_button_unchecked, + size: 18, + color: AppColors.textMuted, + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/home/home_state.dart b/lib/presentation/home/home_state.dart new file mode 100644 index 0000000..259a982 --- /dev/null +++ b/lib/presentation/home/home_state.dart @@ -0,0 +1,15 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:trainhub_flutter/domain/entities/program_workout.dart'; + +part 'home_state.freezed.dart'; + +@freezed +class HomeState with _$HomeState { + const factory HomeState({ + String? activeProgramName, + @Default(0) int completedWorkouts, + @Default(0) int totalWorkouts, + String? nextWorkoutName, + @Default([]) List recentActivity, + }) = _HomeState; +} diff --git a/lib/presentation/home/home_state.freezed.dart b/lib/presentation/home/home_state.freezed.dart new file mode 100644 index 0000000..9a485d9 --- /dev/null +++ b/lib/presentation/home/home_state.freezed.dart @@ -0,0 +1,261 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'home_state.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$HomeState { + String? get activeProgramName => throw _privateConstructorUsedError; + int get completedWorkouts => throw _privateConstructorUsedError; + int get totalWorkouts => throw _privateConstructorUsedError; + String? get nextWorkoutName => throw _privateConstructorUsedError; + List get recentActivity => + throw _privateConstructorUsedError; + + /// Create a copy of HomeState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $HomeStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $HomeStateCopyWith<$Res> { + factory $HomeStateCopyWith(HomeState value, $Res Function(HomeState) then) = + _$HomeStateCopyWithImpl<$Res, HomeState>; + @useResult + $Res call({ + String? activeProgramName, + int completedWorkouts, + int totalWorkouts, + String? nextWorkoutName, + List recentActivity, + }); +} + +/// @nodoc +class _$HomeStateCopyWithImpl<$Res, $Val extends HomeState> + implements $HomeStateCopyWith<$Res> { + _$HomeStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of HomeState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? activeProgramName = freezed, + Object? completedWorkouts = null, + Object? totalWorkouts = null, + Object? nextWorkoutName = freezed, + Object? recentActivity = null, + }) { + return _then( + _value.copyWith( + activeProgramName: freezed == activeProgramName + ? _value.activeProgramName + : activeProgramName // ignore: cast_nullable_to_non_nullable + as String?, + completedWorkouts: null == completedWorkouts + ? _value.completedWorkouts + : completedWorkouts // ignore: cast_nullable_to_non_nullable + as int, + totalWorkouts: null == totalWorkouts + ? _value.totalWorkouts + : totalWorkouts // ignore: cast_nullable_to_non_nullable + as int, + nextWorkoutName: freezed == nextWorkoutName + ? _value.nextWorkoutName + : nextWorkoutName // ignore: cast_nullable_to_non_nullable + as String?, + recentActivity: null == recentActivity + ? _value.recentActivity + : recentActivity // ignore: cast_nullable_to_non_nullable + as List, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$HomeStateImplCopyWith<$Res> + implements $HomeStateCopyWith<$Res> { + factory _$$HomeStateImplCopyWith( + _$HomeStateImpl value, + $Res Function(_$HomeStateImpl) then, + ) = __$$HomeStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + String? activeProgramName, + int completedWorkouts, + int totalWorkouts, + String? nextWorkoutName, + List recentActivity, + }); +} + +/// @nodoc +class __$$HomeStateImplCopyWithImpl<$Res> + extends _$HomeStateCopyWithImpl<$Res, _$HomeStateImpl> + implements _$$HomeStateImplCopyWith<$Res> { + __$$HomeStateImplCopyWithImpl( + _$HomeStateImpl _value, + $Res Function(_$HomeStateImpl) _then, + ) : super(_value, _then); + + /// Create a copy of HomeState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? activeProgramName = freezed, + Object? completedWorkouts = null, + Object? totalWorkouts = null, + Object? nextWorkoutName = freezed, + Object? recentActivity = null, + }) { + return _then( + _$HomeStateImpl( + activeProgramName: freezed == activeProgramName + ? _value.activeProgramName + : activeProgramName // ignore: cast_nullable_to_non_nullable + as String?, + completedWorkouts: null == completedWorkouts + ? _value.completedWorkouts + : completedWorkouts // ignore: cast_nullable_to_non_nullable + as int, + totalWorkouts: null == totalWorkouts + ? _value.totalWorkouts + : totalWorkouts // ignore: cast_nullable_to_non_nullable + as int, + nextWorkoutName: freezed == nextWorkoutName + ? _value.nextWorkoutName + : nextWorkoutName // ignore: cast_nullable_to_non_nullable + as String?, + recentActivity: null == recentActivity + ? _value._recentActivity + : recentActivity // ignore: cast_nullable_to_non_nullable + as List, + ), + ); + } +} + +/// @nodoc + +class _$HomeStateImpl implements _HomeState { + const _$HomeStateImpl({ + this.activeProgramName, + this.completedWorkouts = 0, + this.totalWorkouts = 0, + this.nextWorkoutName, + final List recentActivity = const [], + }) : _recentActivity = recentActivity; + + @override + final String? activeProgramName; + @override + @JsonKey() + final int completedWorkouts; + @override + @JsonKey() + final int totalWorkouts; + @override + final String? nextWorkoutName; + final List _recentActivity; + @override + @JsonKey() + List get recentActivity { + if (_recentActivity is EqualUnmodifiableListView) return _recentActivity; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_recentActivity); + } + + @override + String toString() { + return 'HomeState(activeProgramName: $activeProgramName, completedWorkouts: $completedWorkouts, totalWorkouts: $totalWorkouts, nextWorkoutName: $nextWorkoutName, recentActivity: $recentActivity)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$HomeStateImpl && + (identical(other.activeProgramName, activeProgramName) || + other.activeProgramName == activeProgramName) && + (identical(other.completedWorkouts, completedWorkouts) || + other.completedWorkouts == completedWorkouts) && + (identical(other.totalWorkouts, totalWorkouts) || + other.totalWorkouts == totalWorkouts) && + (identical(other.nextWorkoutName, nextWorkoutName) || + other.nextWorkoutName == nextWorkoutName) && + const DeepCollectionEquality().equals( + other._recentActivity, + _recentActivity, + )); + } + + @override + int get hashCode => Object.hash( + runtimeType, + activeProgramName, + completedWorkouts, + totalWorkouts, + nextWorkoutName, + const DeepCollectionEquality().hash(_recentActivity), + ); + + /// Create a copy of HomeState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$HomeStateImplCopyWith<_$HomeStateImpl> get copyWith => + __$$HomeStateImplCopyWithImpl<_$HomeStateImpl>(this, _$identity); +} + +abstract class _HomeState implements HomeState { + const factory _HomeState({ + final String? activeProgramName, + final int completedWorkouts, + final int totalWorkouts, + final String? nextWorkoutName, + final List recentActivity, + }) = _$HomeStateImpl; + + @override + String? get activeProgramName; + @override + int get completedWorkouts; + @override + int get totalWorkouts; + @override + String? get nextWorkoutName; + @override + List get recentActivity; + + /// Create a copy of HomeState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$HomeStateImplCopyWith<_$HomeStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/presentation/plan_editor/plan_editor_controller.dart b/lib/presentation/plan_editor/plan_editor_controller.dart new file mode 100644 index 0000000..05a2ba6 --- /dev/null +++ b/lib/presentation/plan_editor/plan_editor_controller.dart @@ -0,0 +1,177 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:trainhub_flutter/core/utils/id_generator.dart'; +import 'package:trainhub_flutter/injection.dart'; +import 'package:trainhub_flutter/domain/entities/exercise.dart'; +import 'package:trainhub_flutter/domain/entities/training_exercise.dart'; +import 'package:trainhub_flutter/domain/entities/training_plan.dart'; +import 'package:trainhub_flutter/domain/entities/training_section.dart'; +import 'package:trainhub_flutter/domain/repositories/exercise_repository.dart'; +import 'package:trainhub_flutter/domain/repositories/training_plan_repository.dart'; +import 'package:trainhub_flutter/presentation/plan_editor/plan_editor_state.dart'; + +part 'plan_editor_controller.g.dart'; + +@riverpod +class PlanEditorController extends _$PlanEditorController { + late final TrainingPlanRepository _planRepo; + + @override + Future build(String planId) async { + _planRepo = getIt(); + final ExerciseRepository exerciseRepo = getIt(); + + final plan = await _planRepo.getById(planId); + final exercises = await exerciseRepo.getAll(); + + return PlanEditorState(plan: plan, availableExercises: exercises); + } + + void updatePlanName(String name) { + final current = state.valueOrNull; + if (current == null) return; + state = AsyncValue.data( + current.copyWith(plan: current.plan.copyWith(name: name), isDirty: true), + ); + } + + void addSection() { + final current = state.valueOrNull; + if (current == null) return; + final newSection = TrainingSectionEntity( + id: IdGenerator.generate(), + name: 'New Section', + ); + state = AsyncValue.data( + current.copyWith( + plan: current.plan.copyWith( + sections: [...current.plan.sections, newSection], + ), + isDirty: true, + ), + ); + } + + void deleteSection(int index) { + final current = state.valueOrNull; + if (current == null) return; + final sections = List.from(current.plan.sections); + sections.removeAt(index); + state = AsyncValue.data( + current.copyWith( + plan: current.plan.copyWith(sections: sections), + isDirty: true, + ), + ); + } + + void updateSectionName(int sectionIndex, String name) { + final current = state.valueOrNull; + if (current == null) return; + final sections = List.from(current.plan.sections); + sections[sectionIndex] = sections[sectionIndex].copyWith(name: name); + state = AsyncValue.data( + current.copyWith( + plan: current.plan.copyWith(sections: sections), + isDirty: true, + ), + ); + } + + void addExerciseToSection(int sectionIndex, ExerciseEntity exercise) { + final current = state.valueOrNull; + if (current == null) return; + final sections = List.from(current.plan.sections); + final newExercise = TrainingExerciseEntity( + instanceId: IdGenerator.generate(), + exerciseId: exercise.id, + name: exercise.name, + ); + sections[sectionIndex] = sections[sectionIndex].copyWith( + exercises: [...sections[sectionIndex].exercises, newExercise], + ); + state = AsyncValue.data( + current.copyWith( + plan: current.plan.copyWith(sections: sections), + isDirty: true, + ), + ); + } + + void removeExerciseFromSection(int sectionIndex, int exerciseIndex) { + final current = state.valueOrNull; + if (current == null) return; + final sections = List.from(current.plan.sections); + final exercises = List.from( + sections[sectionIndex].exercises, + ); + exercises.removeAt(exerciseIndex); + sections[sectionIndex] = sections[sectionIndex].copyWith( + exercises: exercises, + ); + state = AsyncValue.data( + current.copyWith( + plan: current.plan.copyWith(sections: sections), + isDirty: true, + ), + ); + } + + void updateExerciseParams( + int sectionIndex, + int exerciseIndex, { + int? sets, + int? value, + bool? isTime, + int? rest, + }) { + final current = state.valueOrNull; + if (current == null) return; + final sections = List.from(current.plan.sections); + final exercises = List.from( + sections[sectionIndex].exercises, + ); + exercises[exerciseIndex] = exercises[exerciseIndex].copyWith( + sets: sets ?? exercises[exerciseIndex].sets, + value: value ?? exercises[exerciseIndex].value, + isTime: isTime ?? exercises[exerciseIndex].isTime, + rest: rest ?? exercises[exerciseIndex].rest, + ); + sections[sectionIndex] = sections[sectionIndex].copyWith( + exercises: exercises, + ); + state = AsyncValue.data( + current.copyWith( + plan: current.plan.copyWith(sections: sections), + isDirty: true, + ), + ); + } + + void reorderExercise(int sectionIndex, int oldIndex, int newIndex) { + final current = state.valueOrNull; + if (current == null) return; + final sections = List.from(current.plan.sections); + final exercises = List.from( + sections[sectionIndex].exercises, + ); + if (oldIndex < newIndex) newIndex -= 1; + final item = exercises.removeAt(oldIndex); + exercises.insert(newIndex, item); + sections[sectionIndex] = sections[sectionIndex].copyWith( + exercises: exercises, + ); + state = AsyncValue.data( + current.copyWith( + plan: current.plan.copyWith(sections: sections), + isDirty: true, + ), + ); + } + + Future save() async { + final current = state.valueOrNull; + if (current == null) return; + await _planRepo.update(current.plan); + state = AsyncValue.data(current.copyWith(isDirty: false)); + } +} diff --git a/lib/presentation/plan_editor/plan_editor_controller.g.dart b/lib/presentation/plan_editor/plan_editor_controller.g.dart new file mode 100644 index 0000000..1d901a5 --- /dev/null +++ b/lib/presentation/plan_editor/plan_editor_controller.g.dart @@ -0,0 +1,175 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'plan_editor_controller.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$planEditorControllerHash() => + r'4045493829126f28b3a58695b68ade53519c1412'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +abstract class _$PlanEditorController + extends BuildlessAutoDisposeAsyncNotifier { + late final String planId; + + FutureOr build(String planId); +} + +/// See also [PlanEditorController]. +@ProviderFor(PlanEditorController) +const planEditorControllerProvider = PlanEditorControllerFamily(); + +/// See also [PlanEditorController]. +class PlanEditorControllerFamily extends Family> { + /// See also [PlanEditorController]. + const PlanEditorControllerFamily(); + + /// See also [PlanEditorController]. + PlanEditorControllerProvider call(String planId) { + return PlanEditorControllerProvider(planId); + } + + @override + PlanEditorControllerProvider getProviderOverride( + covariant PlanEditorControllerProvider provider, + ) { + return call(provider.planId); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'planEditorControllerProvider'; +} + +/// See also [PlanEditorController]. +class PlanEditorControllerProvider + extends + AutoDisposeAsyncNotifierProviderImpl< + PlanEditorController, + PlanEditorState + > { + /// See also [PlanEditorController]. + PlanEditorControllerProvider(String planId) + : this._internal( + () => PlanEditorController()..planId = planId, + from: planEditorControllerProvider, + name: r'planEditorControllerProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$planEditorControllerHash, + dependencies: PlanEditorControllerFamily._dependencies, + allTransitiveDependencies: + PlanEditorControllerFamily._allTransitiveDependencies, + planId: planId, + ); + + PlanEditorControllerProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.planId, + }) : super.internal(); + + final String planId; + + @override + FutureOr runNotifierBuild( + covariant PlanEditorController notifier, + ) { + return notifier.build(planId); + } + + @override + Override overrideWith(PlanEditorController Function() create) { + return ProviderOverride( + origin: this, + override: PlanEditorControllerProvider._internal( + () => create()..planId = planId, + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + planId: planId, + ), + ); + } + + @override + AutoDisposeAsyncNotifierProviderElement + createElement() { + return _PlanEditorControllerProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is PlanEditorControllerProvider && other.planId == planId; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, planId.hashCode); + + return _SystemHash.finish(hash); + } +} + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +mixin PlanEditorControllerRef + on AutoDisposeAsyncNotifierProviderRef { + /// The parameter `planId` of this provider. + String get planId; +} + +class _PlanEditorControllerProviderElement + extends + AutoDisposeAsyncNotifierProviderElement< + PlanEditorController, + PlanEditorState + > + with PlanEditorControllerRef { + _PlanEditorControllerProviderElement(super.provider); + + @override + String get planId => (origin as PlanEditorControllerProvider).planId; +} + +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/presentation/plan_editor/plan_editor_page.dart b/lib/presentation/plan_editor/plan_editor_page.dart new file mode 100644 index 0000000..68bcc28 --- /dev/null +++ b/lib/presentation/plan_editor/plan_editor_page.dart @@ -0,0 +1,80 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:trainhub_flutter/presentation/plan_editor/plan_editor_controller.dart'; +import 'package:trainhub_flutter/presentation/plan_editor/widgets/plan_section_card.dart'; + +@RoutePage() +class PlanEditorPage extends ConsumerWidget { + final String planId; + + const PlanEditorPage({super.key, @PathParam('planId') required this.planId}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final state = ref.watch(planEditorControllerProvider(planId)); + final controller = ref.read(planEditorControllerProvider(planId).notifier); + + return Scaffold( + appBar: AppBar( + title: state.when( + data: (data) => TextField( + controller: TextEditingController(text: data.plan.name) + ..selection = TextSelection.fromPosition( + TextPosition(offset: data.plan.name.length), + ), + decoration: const InputDecoration( + border: InputBorder.none, + hintText: 'Plan Name', + ), + style: Theme.of(context).textTheme.titleLarge, + onChanged: controller.updatePlanName, + ), + error: (_, __) => const Text('Error'), + loading: () => const Text('Loading...'), + ), + actions: [ + state.maybeWhen( + data: (data) => IconButton( + icon: Icon( + Icons.save, + color: data.isDirty ? Theme.of(context).primaryColor : null, + ), + onPressed: data.isDirty ? () => controller.save() : null, + ), + orElse: () => const SizedBox.shrink(), + ), + ], + ), + body: state.when( + data: (data) => ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: data.plan.sections.length + 1, + itemBuilder: (context, index) { + if (index == data.plan.sections.length) { + return Center( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: ElevatedButton.icon( + onPressed: controller.addSection, + icon: const Icon(Icons.add), + label: const Text('Add Section'), + ), + ), + ); + } + final section = data.plan.sections[index]; + return PlanSectionCard( + section: section, + sectionIndex: index, + plan: data.plan, + availableExercises: data.availableExercises, + ); + }, + ), + error: (e, s) => Center(child: Text('Error: $e')), + loading: () => const Center(child: CircularProgressIndicator()), + ), + ); + } +} diff --git a/lib/presentation/plan_editor/plan_editor_state.dart b/lib/presentation/plan_editor/plan_editor_state.dart new file mode 100644 index 0000000..b032d7d --- /dev/null +++ b/lib/presentation/plan_editor/plan_editor_state.dart @@ -0,0 +1,14 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:trainhub_flutter/domain/entities/training_plan.dart'; +import 'package:trainhub_flutter/domain/entities/exercise.dart'; + +part 'plan_editor_state.freezed.dart'; + +@freezed +class PlanEditorState with _$PlanEditorState { + const factory PlanEditorState({ + required TrainingPlanEntity plan, + @Default(false) bool isDirty, + @Default([]) List availableExercises, + }) = _PlanEditorState; +} diff --git a/lib/presentation/plan_editor/plan_editor_state.freezed.dart b/lib/presentation/plan_editor/plan_editor_state.freezed.dart new file mode 100644 index 0000000..9dae1fc --- /dev/null +++ b/lib/presentation/plan_editor/plan_editor_state.freezed.dart @@ -0,0 +1,235 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'plan_editor_state.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$PlanEditorState { + TrainingPlanEntity get plan => throw _privateConstructorUsedError; + bool get isDirty => throw _privateConstructorUsedError; + List get availableExercises => + throw _privateConstructorUsedError; + + /// Create a copy of PlanEditorState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $PlanEditorStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $PlanEditorStateCopyWith<$Res> { + factory $PlanEditorStateCopyWith( + PlanEditorState value, + $Res Function(PlanEditorState) then, + ) = _$PlanEditorStateCopyWithImpl<$Res, PlanEditorState>; + @useResult + $Res call({ + TrainingPlanEntity plan, + bool isDirty, + List availableExercises, + }); + + $TrainingPlanEntityCopyWith<$Res> get plan; +} + +/// @nodoc +class _$PlanEditorStateCopyWithImpl<$Res, $Val extends PlanEditorState> + implements $PlanEditorStateCopyWith<$Res> { + _$PlanEditorStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of PlanEditorState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? plan = null, + Object? isDirty = null, + Object? availableExercises = null, + }) { + return _then( + _value.copyWith( + plan: null == plan + ? _value.plan + : plan // ignore: cast_nullable_to_non_nullable + as TrainingPlanEntity, + isDirty: null == isDirty + ? _value.isDirty + : isDirty // ignore: cast_nullable_to_non_nullable + as bool, + availableExercises: null == availableExercises + ? _value.availableExercises + : availableExercises // ignore: cast_nullable_to_non_nullable + as List, + ) + as $Val, + ); + } + + /// Create a copy of PlanEditorState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $TrainingPlanEntityCopyWith<$Res> get plan { + return $TrainingPlanEntityCopyWith<$Res>(_value.plan, (value) { + return _then(_value.copyWith(plan: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$PlanEditorStateImplCopyWith<$Res> + implements $PlanEditorStateCopyWith<$Res> { + factory _$$PlanEditorStateImplCopyWith( + _$PlanEditorStateImpl value, + $Res Function(_$PlanEditorStateImpl) then, + ) = __$$PlanEditorStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + TrainingPlanEntity plan, + bool isDirty, + List availableExercises, + }); + + @override + $TrainingPlanEntityCopyWith<$Res> get plan; +} + +/// @nodoc +class __$$PlanEditorStateImplCopyWithImpl<$Res> + extends _$PlanEditorStateCopyWithImpl<$Res, _$PlanEditorStateImpl> + implements _$$PlanEditorStateImplCopyWith<$Res> { + __$$PlanEditorStateImplCopyWithImpl( + _$PlanEditorStateImpl _value, + $Res Function(_$PlanEditorStateImpl) _then, + ) : super(_value, _then); + + /// Create a copy of PlanEditorState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? plan = null, + Object? isDirty = null, + Object? availableExercises = null, + }) { + return _then( + _$PlanEditorStateImpl( + plan: null == plan + ? _value.plan + : plan // ignore: cast_nullable_to_non_nullable + as TrainingPlanEntity, + isDirty: null == isDirty + ? _value.isDirty + : isDirty // ignore: cast_nullable_to_non_nullable + as bool, + availableExercises: null == availableExercises + ? _value._availableExercises + : availableExercises // ignore: cast_nullable_to_non_nullable + as List, + ), + ); + } +} + +/// @nodoc + +class _$PlanEditorStateImpl implements _PlanEditorState { + const _$PlanEditorStateImpl({ + required this.plan, + this.isDirty = false, + final List availableExercises = const [], + }) : _availableExercises = availableExercises; + + @override + final TrainingPlanEntity plan; + @override + @JsonKey() + final bool isDirty; + final List _availableExercises; + @override + @JsonKey() + List get availableExercises { + if (_availableExercises is EqualUnmodifiableListView) + return _availableExercises; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_availableExercises); + } + + @override + String toString() { + return 'PlanEditorState(plan: $plan, isDirty: $isDirty, availableExercises: $availableExercises)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PlanEditorStateImpl && + (identical(other.plan, plan) || other.plan == plan) && + (identical(other.isDirty, isDirty) || other.isDirty == isDirty) && + const DeepCollectionEquality().equals( + other._availableExercises, + _availableExercises, + )); + } + + @override + int get hashCode => Object.hash( + runtimeType, + plan, + isDirty, + const DeepCollectionEquality().hash(_availableExercises), + ); + + /// Create a copy of PlanEditorState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$PlanEditorStateImplCopyWith<_$PlanEditorStateImpl> get copyWith => + __$$PlanEditorStateImplCopyWithImpl<_$PlanEditorStateImpl>( + this, + _$identity, + ); +} + +abstract class _PlanEditorState implements PlanEditorState { + const factory _PlanEditorState({ + required final TrainingPlanEntity plan, + final bool isDirty, + final List availableExercises, + }) = _$PlanEditorStateImpl; + + @override + TrainingPlanEntity get plan; + @override + bool get isDirty; + @override + List get availableExercises; + + /// Create a copy of PlanEditorState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$PlanEditorStateImplCopyWith<_$PlanEditorStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/presentation/plan_editor/widgets/plan_exercise_tile.dart b/lib/presentation/plan_editor/widgets/plan_exercise_tile.dart new file mode 100644 index 0000000..f310900 --- /dev/null +++ b/lib/presentation/plan_editor/widgets/plan_exercise_tile.dart @@ -0,0 +1,165 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:trainhub_flutter/domain/entities/training_exercise.dart'; +import 'package:trainhub_flutter/domain/entities/training_plan.dart'; +import 'package:trainhub_flutter/presentation/plan_editor/plan_editor_controller.dart'; + +class PlanExerciseTile extends ConsumerWidget { + final TrainingExerciseEntity exercise; + final int sectionIndex; + final int exerciseIndex; + final TrainingPlanEntity plan; + + const PlanExerciseTile({ + super.key, + required this.exercise, + required this.sectionIndex, + required this.exerciseIndex, + required this.plan, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final controller = ref.read(planEditorControllerProvider(plan.id).notifier); + + return Card( + margin: const EdgeInsets.symmetric(vertical: 4), + elevation: 0, + color: Theme.of(context).colorScheme.surfaceContainerLow, + shape: RoundedRectangleBorder( + side: BorderSide(color: Theme.of(context).colorScheme.outlineVariant), + borderRadius: BorderRadius.circular(8), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + Row( + children: [ + Expanded( + child: Text( + exercise.name, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ), + IconButton( + icon: const Icon(Icons.close, size: 18), + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + onPressed: () => controller.removeExerciseFromSection( + sectionIndex, + exerciseIndex, + ), + ), + ], + ), + const SizedBox(height: 8), + Row( + children: [ + _buildNumberInput( + context, + label: 'Sets', + value: exercise.sets, + onChanged: (val) => controller.updateExerciseParams( + sectionIndex, + exerciseIndex, + sets: val, + ), + ), + const SizedBox(width: 8), + _buildNumberInput( + context, + label: exercise.isTime ? 'Secs' : 'Reps', + value: exercise.value, + onChanged: (val) => controller.updateExerciseParams( + sectionIndex, + exerciseIndex, + value: val, + ), + ), + const SizedBox(width: 8), + _buildNumberInput( + context, + label: 'Rest(s)', + value: exercise.rest, + onChanged: (val) => controller.updateExerciseParams( + sectionIndex, + exerciseIndex, + rest: val, + ), + ), + const SizedBox(width: 8), + // Toggle Time/Reps + InkWell( + onTap: () => controller.updateExerciseParams( + sectionIndex, + exerciseIndex, + isTime: !exercise.isTime, + ), + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context).colorScheme.outline, + ), + borderRadius: BorderRadius.circular(4), + color: exercise.isTime + ? Theme.of(context).colorScheme.primaryContainer + : null, + ), + child: Icon( + Icons.timer, + size: 16, + color: exercise.isTime + ? Theme.of(context).colorScheme.onPrimaryContainer + : null, + ), + ), + ), + ], + ), + ], + ), + ), + ); + } + + Widget _buildNumberInput( + BuildContext context, { + required String label, + required int value, + required Function(int) onChanged, + }) { + return Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(label, style: Theme.of(context).textTheme.labelSmall), + SizedBox( + height: 40, + child: TextField( + controller: TextEditingController(text: value.toString()) + ..selection = TextSelection.fromPosition( + TextPosition(offset: value.toString().length), + ), + keyboardType: TextInputType.number, + decoration: const InputDecoration( + border: OutlineInputBorder(), + contentPadding: EdgeInsets.symmetric( + horizontal: 8, + vertical: 0, + ), + ), + onChanged: (val) { + final intVal = int.tryParse(val); + if (intVal != null) { + onChanged(intVal); + } + }, + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/plan_editor/widgets/plan_section_card.dart b/lib/presentation/plan_editor/widgets/plan_section_card.dart new file mode 100644 index 0000000..ab5b667 --- /dev/null +++ b/lib/presentation/plan_editor/widgets/plan_section_card.dart @@ -0,0 +1,140 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:trainhub_flutter/domain/entities/exercise.dart'; +import 'package:trainhub_flutter/domain/entities/training_plan.dart'; +import 'package:trainhub_flutter/domain/entities/training_section.dart'; +import 'package:trainhub_flutter/presentation/plan_editor/plan_editor_controller.dart'; +import 'package:trainhub_flutter/presentation/plan_editor/widgets/plan_exercise_tile.dart'; + +class PlanSectionCard extends ConsumerWidget { + final TrainingSectionEntity section; + final int sectionIndex; + final TrainingPlanEntity plan; + final List availableExercises; + + const PlanSectionCard({ + super.key, + required this.section, + required this.sectionIndex, + required this.plan, + required this.availableExercises, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final controller = ref.read(planEditorControllerProvider(plan.id).notifier); + + return Card( + margin: const EdgeInsets.only(bottom: 16), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: TextField( + controller: TextEditingController(text: section.name) + ..selection = TextSelection.fromPosition( + TextPosition(offset: section.name.length), + ), + decoration: const InputDecoration( + border: InputBorder.none, + hintText: 'Section Name', + isDense: true, + ), + style: Theme.of(context).textTheme.titleMedium, + onChanged: (val) => + controller.updateSectionName(sectionIndex, val), + ), + ), + IconButton( + icon: const Icon(Icons.delete_outline), + onPressed: () => controller.deleteSection(sectionIndex), + ), + ], + ), + const Divider(), + ReorderableListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: section.exercises.length, + onReorder: (oldIndex, newIndex) => + controller.reorderExercise(sectionIndex, oldIndex, newIndex), + itemBuilder: (context, index) { + final exercise = section.exercises[index]; + return PlanExerciseTile( + key: ValueKey(exercise.instanceId), + exercise: exercise, + sectionIndex: sectionIndex, + exerciseIndex: index, + plan: plan, + ); + }, + ), + const SizedBox(height: 8), + Center( + child: TextButton.icon( + onPressed: () { + _showExercisePicker(context, (exercise) { + controller.addExerciseToSection(sectionIndex, exercise); + }); + }, + icon: const Icon(Icons.add), + label: const Text('Add Exercise'), + ), + ), + ], + ), + ), + ); + } + + void _showExercisePicker( + BuildContext context, + Function(ExerciseEntity) onSelect, + ) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + useSafeArea: true, + builder: (context) => DraggableScrollableSheet( + expand: false, + initialChildSize: 0.7, + minChildSize: 0.5, + maxChildSize: 0.95, + builder: (context, scrollController) { + return Column( + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + 'Select Exercise', + style: Theme.of(context).textTheme.titleLarge, + ), + ), + Expanded( + child: ListView.builder( + controller: scrollController, + itemCount: availableExercises.length, + itemBuilder: (context, index) { + final exercise = availableExercises[index]; + return ListTile( + title: Text(exercise.name), + subtitle: Text(exercise.muscleGroup ?? ''), + onTap: () { + onSelect(exercise); + Navigator.pop(context); + }, + ); + }, + ), + ), + ], + ); + }, + ), + ); + } +} diff --git a/lib/presentation/shell/shell_page.dart b/lib/presentation/shell/shell_page.dart new file mode 100644 index 0000000..439a2ad --- /dev/null +++ b/lib/presentation/shell/shell_page.dart @@ -0,0 +1,76 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:trainhub_flutter/core/theme/app_colors.dart'; +import 'package:trainhub_flutter/core/router/app_router.dart'; + +@RoutePage() +class ShellPage extends StatelessWidget { + const ShellPage({super.key}); + + @override + Widget build(BuildContext context) { + return AutoTabsRouter( + routes: const [ + HomeRoute(), + TrainingsRoute(), + AnalysisRoute(), + CalendarRoute(), + ChatRoute(), + ], + builder: (context, child) { + final tabsRouter = AutoTabsRouter.of(context); + return Scaffold( + body: Row( + children: [ + // NavigationRail with logo area at top + NavigationRail( + selectedIndex: tabsRouter.activeIndex, + onDestinationSelected: tabsRouter.setActiveIndex, + labelType: NavigationRailLabelType.all, + leading: Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Column( + children: [ + Icon(Icons.fitness_center, color: AppColors.accent, size: 28), + const SizedBox(height: 4), + Text('TrainHub', style: TextStyle(color: AppColors.textPrimary, fontSize: 10, fontWeight: FontWeight.w600)), + ], + ), + ), + destinations: const [ + NavigationRailDestination( + icon: Icon(Icons.dashboard_outlined), + selectedIcon: Icon(Icons.dashboard), + label: Text('Home'), + ), + NavigationRailDestination( + icon: Icon(Icons.fitness_center_outlined), + selectedIcon: Icon(Icons.fitness_center), + label: Text('Trainings'), + ), + NavigationRailDestination( + icon: Icon(Icons.video_library_outlined), + selectedIcon: Icon(Icons.video_library), + label: Text('Analysis'), + ), + NavigationRailDestination( + icon: Icon(Icons.calendar_today_outlined), + selectedIcon: Icon(Icons.calendar_today), + label: Text('Calendar'), + ), + NavigationRailDestination( + icon: Icon(Icons.chat_bubble_outline), + selectedIcon: Icon(Icons.chat_bubble), + label: Text('AI Chat'), + ), + ], + ), + const VerticalDivider(thickness: 1, width: 1), + Expanded(child: child), + ], + ), + ); + }, + ); + } +} diff --git a/lib/presentation/trainings/trainings_controller.dart b/lib/presentation/trainings/trainings_controller.dart new file mode 100644 index 0000000..a594c78 --- /dev/null +++ b/lib/presentation/trainings/trainings_controller.dart @@ -0,0 +1,64 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:trainhub_flutter/injection.dart'; +import 'package:trainhub_flutter/domain/entities/exercise.dart'; +import 'package:trainhub_flutter/domain/entities/training_plan.dart'; +import 'package:trainhub_flutter/domain/repositories/exercise_repository.dart'; +import 'package:trainhub_flutter/domain/repositories/training_plan_repository.dart'; +import 'package:trainhub_flutter/presentation/trainings/trainings_state.dart'; + +part 'trainings_controller.g.dart'; + +@riverpod +class TrainingsController extends _$TrainingsController { + late final TrainingPlanRepository _planRepo; + late final ExerciseRepository _exerciseRepo; + + @override + Future build() async { + _planRepo = getIt(); + _exerciseRepo = getIt(); + final plans = await _planRepo.getAll(); + final exercises = await _exerciseRepo.getAll(); + return TrainingsState(plans: plans, exercises: exercises); + } + + Future createPlan(String name) async { + final plan = await _planRepo.create(name); + await _reload(); + return plan; + } + + Future deletePlan(String id) async { + await _planRepo.delete(id); + await _reload(); + } + + Future addExercise({ + required String name, + String? instructions, + String? tags, + String? videoUrl, + }) async { + await _exerciseRepo.create( + name: name, + instructions: instructions, + tags: tags, + videoUrl: videoUrl, + ); + await _reload(); + } + + Future updateExercise(ExerciseEntity exercise) async { + await _exerciseRepo.update(exercise); + await _reload(); + } + + Future deleteExercise(String id) async { + await _exerciseRepo.delete(id); + await _reload(); + } + + Future _reload() async { + state = await AsyncValue.guard(() => build()); + } +} diff --git a/lib/presentation/trainings/trainings_controller.g.dart b/lib/presentation/trainings/trainings_controller.g.dart new file mode 100644 index 0000000..7d33ed0 --- /dev/null +++ b/lib/presentation/trainings/trainings_controller.g.dart @@ -0,0 +1,30 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'trainings_controller.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$trainingsControllerHash() => + r'15c54eb8211e3b2549af6ef25a9cb451a7a9988a'; + +/// See also [TrainingsController]. +@ProviderFor(TrainingsController) +final trainingsControllerProvider = + AutoDisposeAsyncNotifierProvider< + TrainingsController, + TrainingsState + >.internal( + TrainingsController.new, + name: r'trainingsControllerProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$trainingsControllerHash, + dependencies: null, + allTransitiveDependencies: null, + ); + +typedef _$TrainingsController = AutoDisposeAsyncNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/presentation/trainings/trainings_page.dart b/lib/presentation/trainings/trainings_page.dart new file mode 100644 index 0000000..5979346 --- /dev/null +++ b/lib/presentation/trainings/trainings_page.dart @@ -0,0 +1,493 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:trainhub_flutter/core/theme/app_colors.dart'; +import 'package:trainhub_flutter/core/constants/ui_constants.dart'; +import 'package:trainhub_flutter/core/router/app_router.dart'; +import 'package:trainhub_flutter/domain/entities/training_plan.dart'; +import 'package:trainhub_flutter/domain/entities/exercise.dart'; +import 'package:trainhub_flutter/presentation/trainings/trainings_controller.dart'; +import 'package:trainhub_flutter/presentation/common/widgets/app_empty_state.dart'; +import 'package:trainhub_flutter/presentation/common/dialogs/text_input_dialog.dart'; +import 'package:trainhub_flutter/presentation/common/dialogs/confirm_dialog.dart'; + +@RoutePage() +class TrainingsPage extends ConsumerWidget { + const TrainingsPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final asyncState = ref.watch(trainingsControllerProvider); + + return DefaultTabController( + length: 2, + child: Column( + children: [ + Container( + decoration: BoxDecoration( + color: AppColors.surfaceContainer, + border: const Border(bottom: BorderSide(color: AppColors.border)), + ), + child: const TabBar( + tabs: [ + Tab(text: 'Training Plans'), + Tab(text: 'Exercises'), + ], + ), + ), + Expanded( + child: asyncState.when( + loading: () => const Center(child: CircularProgressIndicator()), + error: (err, _) => Center(child: Text('Error: $err')), + data: (state) => TabBarView( + children: [ + _PlansTab(plans: state.plans, ref: ref), + _ExercisesTab(exercises: state.exercises, ref: ref), + ], + ), + ), + ), + ], + ), + ); + } +} + +class _PlansTab extends StatelessWidget { + final List plans; + final WidgetRef ref; + + const _PlansTab({required this.plans, required this.ref}); + + @override + Widget build(BuildContext context) { + if (plans.isEmpty) { + return AppEmptyState( + icon: Icons.fitness_center, + title: 'No training plans yet', + subtitle: 'Create your first training plan to get started', + actionLabel: 'Create Plan', + onAction: () => _createPlan(context), + ); + } + + return Column( + children: [ + Padding( + padding: const EdgeInsets.all(UIConstants.spacing16), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + FilledButton.icon( + onPressed: () => _createPlan(context), + icon: const Icon(Icons.add, size: 18), + label: const Text('New Plan'), + ), + ], + ), + ), + Expanded( + child: ListView.builder( + padding: const EdgeInsets.symmetric( + horizontal: UIConstants.spacing16, + ), + itemCount: plans.length, + itemBuilder: (context, index) { + final plan = plans[index]; + return _PlanListItem( + plan: plan, + onEdit: () { + context.router.push(PlanEditorRoute(planId: plan.id)); + }, + onStart: () { + context.router.push(WorkoutSessionRoute(planId: plan.id)); + }, + onDelete: () => _deletePlan(context, plan), + ); + }, + ), + ), + ], + ); + } + + Future _createPlan(BuildContext context) async { + final name = await TextInputDialog.show( + context, + title: 'New Plan Name', + hintText: 'e.g. Push Pull Legs', + ); + if (name != null && name.isNotEmpty) { + final plan = await ref + .read(trainingsControllerProvider.notifier) + .createPlan(name); + if (context.mounted) { + context.router.push(PlanEditorRoute(planId: plan.id)); + } + } + } + + Future _deletePlan( + BuildContext context, + TrainingPlanEntity plan, + ) async { + final confirmed = await ConfirmDialog.show( + context, + title: 'Delete Plan?', + message: 'Are you sure you want to delete "${plan.name}"?', + ); + if (confirmed == true) { + ref.read(trainingsControllerProvider.notifier).deletePlan(plan.id); + } + } +} + +class _PlanListItem extends StatefulWidget { + final TrainingPlanEntity plan; + final VoidCallback onEdit; + final VoidCallback onStart; + final VoidCallback onDelete; + + const _PlanListItem({ + required this.plan, + required this.onEdit, + required this.onStart, + required this.onDelete, + }); + + @override + State<_PlanListItem> createState() => _PlanListItemState(); +} + +class _PlanListItemState extends State<_PlanListItem> { + bool _isHovered = false; + + @override + Widget build(BuildContext context) { + return MouseRegion( + onEnter: (_) => setState(() => _isHovered = true), + onExit: (_) => setState(() => _isHovered = false), + child: Card( + margin: const EdgeInsets.only(bottom: UIConstants.spacing8), + child: Padding( + padding: const EdgeInsets.all(UIConstants.spacing12), + child: Row( + children: [ + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: AppColors.accentMuted, + borderRadius: BorderRadius.circular(8), + ), + child: const Icon( + Icons.description_outlined, + color: AppColors.accent, + size: 20, + ), + ), + const SizedBox(width: UIConstants.spacing12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.plan.name, + style: const TextStyle( + fontWeight: FontWeight.w600, + fontSize: 14, + ), + ), + const SizedBox(height: 2), + Text( + '${widget.plan.sections.length} sections, ${widget.plan.totalExercises} exercises', + style: const TextStyle( + color: AppColors.textMuted, + fontSize: 12, + ), + ), + ], + ), + ), + if (_isHovered) ...[ + IconButton( + icon: const Icon( + Icons.delete_outline, + color: AppColors.destructive, + size: 18, + ), + onPressed: widget.onDelete, + tooltip: 'Delete', + ), + const SizedBox(width: 4), + ], + OutlinedButton( + onPressed: widget.onEdit, + child: const Text('Edit'), + ), + const SizedBox(width: UIConstants.spacing8), + FilledButton.icon( + onPressed: widget.onStart, + icon: const Icon(Icons.play_arrow, size: 18), + label: const Text('Start'), + ), + ], + ), + ), + ), + ); + } +} + +class _ExercisesTab extends StatelessWidget { + final List exercises; + final WidgetRef ref; + + const _ExercisesTab({required this.exercises, required this.ref}); + + @override + Widget build(BuildContext context) { + return Scaffold( + floatingActionButton: FloatingActionButton( + onPressed: () => _showExerciseDialog(context), + backgroundColor: AppColors.zinc50, + foregroundColor: AppColors.zinc950, + child: const Icon(Icons.add), + ), + body: exercises.isEmpty + ? const AppEmptyState( + icon: Icons.fitness_center, + title: 'No exercises yet', + subtitle: 'Add exercises to use in your training plans', + ) + : GridView.builder( + padding: const EdgeInsets.all(UIConstants.spacing16), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + childAspectRatio: 3.0, + crossAxisSpacing: UIConstants.spacing12, + mainAxisSpacing: UIConstants.spacing12, + ), + itemCount: exercises.length, + itemBuilder: (context, index) { + final exercise = exercises[index]; + return _ExerciseCard( + exercise: exercise, + onEdit: () => + _showExerciseDialog(context, exercise: exercise), + onDelete: () => ref + .read(trainingsControllerProvider.notifier) + .deleteExercise(exercise.id), + ); + }, + ), + ); + } + + void _showExerciseDialog(BuildContext context, {ExerciseEntity? exercise}) { + final nameCtrl = TextEditingController(text: exercise?.name); + final instructionsCtrl = TextEditingController( + text: exercise?.instructions, + ); + final tagsCtrl = TextEditingController(text: exercise?.tags); + final videoUrlCtrl = TextEditingController(text: exercise?.videoUrl); + + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(exercise == null ? 'New Exercise' : 'Edit Exercise'), + content: SizedBox( + width: UIConstants.dialogWidth, + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: nameCtrl, + decoration: const InputDecoration(labelText: 'Name'), + autofocus: true, + ), + const SizedBox(height: UIConstants.spacing12), + TextField( + controller: instructionsCtrl, + decoration: const InputDecoration(labelText: 'Instructions'), + maxLines: 2, + ), + const SizedBox(height: UIConstants.spacing12), + TextField( + controller: tagsCtrl, + decoration: const InputDecoration( + labelText: 'Tags (comma separated)', + ), + ), + const SizedBox(height: UIConstants.spacing12), + TextField( + controller: videoUrlCtrl, + decoration: const InputDecoration(labelText: 'Video URL'), + ), + ], + ), + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Cancel'), + ), + FilledButton( + onPressed: () { + if (nameCtrl.text.isEmpty) return; + if (exercise == null) { + ref + .read(trainingsControllerProvider.notifier) + .addExercise( + name: nameCtrl.text, + instructions: instructionsCtrl.text, + tags: tagsCtrl.text, + videoUrl: videoUrlCtrl.text, + ); + } else { + ref + .read(trainingsControllerProvider.notifier) + .updateExercise( + exercise.copyWith( + name: nameCtrl.text, + instructions: instructionsCtrl.text, + tags: tagsCtrl.text, + videoUrl: videoUrlCtrl.text, + ), + ); + } + Navigator.pop(context); + }, + child: const Text('Save'), + ), + ], + ), + ); + } +} + +class _ExerciseCard extends StatefulWidget { + final ExerciseEntity exercise; + final VoidCallback onEdit; + final VoidCallback onDelete; + + const _ExerciseCard({ + required this.exercise, + required this.onEdit, + required this.onDelete, + }); + + @override + State<_ExerciseCard> createState() => _ExerciseCardState(); +} + +class _ExerciseCardState extends State<_ExerciseCard> { + bool _isHovered = false; + + @override + Widget build(BuildContext context) { + return MouseRegion( + onEnter: (_) => setState(() => _isHovered = true), + onExit: (_) => setState(() => _isHovered = false), + child: Card( + child: InkWell( + onTap: widget.onEdit, + borderRadius: UIConstants.cardBorderRadius, + child: Padding( + padding: const EdgeInsets.all(UIConstants.spacing12), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + widget.exercise.name, + style: const TextStyle( + fontWeight: FontWeight.w600, + fontSize: 14, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + if (widget.exercise.instructions != null && + widget.exercise.instructions!.isNotEmpty) ...[ + const SizedBox(height: 4), + Text( + widget.exercise.instructions!, + style: const TextStyle( + color: AppColors.textMuted, + fontSize: 12, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + if (widget.exercise.tags != null && + widget.exercise.tags!.isNotEmpty) ...[ + const SizedBox(height: 6), + Wrap( + spacing: 4, + children: widget.exercise.tags! + .split(',') + .take(3) + .map( + (tag) => Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, + vertical: 2, + ), + decoration: BoxDecoration( + color: AppColors.zinc800, + borderRadius: BorderRadius.circular(4), + ), + child: Text( + tag.trim(), + style: const TextStyle( + fontSize: 10, + color: AppColors.textSecondary, + ), + ), + ), + ) + .toList(), + ), + ], + ], + ), + ), + if (widget.exercise.videoUrl != null && + widget.exercise.videoUrl!.isNotEmpty) + const Padding( + padding: EdgeInsets.only(right: 8), + child: Icon( + Icons.videocam, + size: 16, + color: AppColors.info, + ), + ), + if (_isHovered) ...[ + IconButton( + icon: const Icon(Icons.edit, size: 16), + onPressed: widget.onEdit, + ), + IconButton( + icon: const Icon( + Icons.delete_outline, + size: 16, + color: AppColors.destructive, + ), + onPressed: widget.onDelete, + ), + ], + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/presentation/trainings/trainings_state.dart b/lib/presentation/trainings/trainings_state.dart new file mode 100644 index 0000000..d88062e --- /dev/null +++ b/lib/presentation/trainings/trainings_state.dart @@ -0,0 +1,13 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:trainhub_flutter/domain/entities/training_plan.dart'; +import 'package:trainhub_flutter/domain/entities/exercise.dart'; + +part 'trainings_state.freezed.dart'; + +@freezed +class TrainingsState with _$TrainingsState { + const factory TrainingsState({ + @Default([]) List plans, + @Default([]) List exercises, + }) = _TrainingsState; +} diff --git a/lib/presentation/trainings/trainings_state.freezed.dart b/lib/presentation/trainings/trainings_state.freezed.dart new file mode 100644 index 0000000..65147cb --- /dev/null +++ b/lib/presentation/trainings/trainings_state.freezed.dart @@ -0,0 +1,192 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'trainings_state.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$TrainingsState { + List get plans => throw _privateConstructorUsedError; + List get exercises => throw _privateConstructorUsedError; + + /// Create a copy of TrainingsState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $TrainingsStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $TrainingsStateCopyWith<$Res> { + factory $TrainingsStateCopyWith( + TrainingsState value, + $Res Function(TrainingsState) then, + ) = _$TrainingsStateCopyWithImpl<$Res, TrainingsState>; + @useResult + $Res call({List plans, List exercises}); +} + +/// @nodoc +class _$TrainingsStateCopyWithImpl<$Res, $Val extends TrainingsState> + implements $TrainingsStateCopyWith<$Res> { + _$TrainingsStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of TrainingsState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? plans = null, Object? exercises = null}) { + return _then( + _value.copyWith( + plans: null == plans + ? _value.plans + : plans // ignore: cast_nullable_to_non_nullable + as List, + exercises: null == exercises + ? _value.exercises + : exercises // ignore: cast_nullable_to_non_nullable + as List, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$TrainingsStateImplCopyWith<$Res> + implements $TrainingsStateCopyWith<$Res> { + factory _$$TrainingsStateImplCopyWith( + _$TrainingsStateImpl value, + $Res Function(_$TrainingsStateImpl) then, + ) = __$$TrainingsStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({List plans, List exercises}); +} + +/// @nodoc +class __$$TrainingsStateImplCopyWithImpl<$Res> + extends _$TrainingsStateCopyWithImpl<$Res, _$TrainingsStateImpl> + implements _$$TrainingsStateImplCopyWith<$Res> { + __$$TrainingsStateImplCopyWithImpl( + _$TrainingsStateImpl _value, + $Res Function(_$TrainingsStateImpl) _then, + ) : super(_value, _then); + + /// Create a copy of TrainingsState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? plans = null, Object? exercises = null}) { + return _then( + _$TrainingsStateImpl( + plans: null == plans + ? _value._plans + : plans // ignore: cast_nullable_to_non_nullable + as List, + exercises: null == exercises + ? _value._exercises + : exercises // ignore: cast_nullable_to_non_nullable + as List, + ), + ); + } +} + +/// @nodoc + +class _$TrainingsStateImpl implements _TrainingsState { + const _$TrainingsStateImpl({ + final List plans = const [], + final List exercises = const [], + }) : _plans = plans, + _exercises = exercises; + + final List _plans; + @override + @JsonKey() + List get plans { + if (_plans is EqualUnmodifiableListView) return _plans; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_plans); + } + + final List _exercises; + @override + @JsonKey() + List get exercises { + if (_exercises is EqualUnmodifiableListView) return _exercises; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_exercises); + } + + @override + String toString() { + return 'TrainingsState(plans: $plans, exercises: $exercises)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TrainingsStateImpl && + const DeepCollectionEquality().equals(other._plans, _plans) && + const DeepCollectionEquality().equals( + other._exercises, + _exercises, + )); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_plans), + const DeepCollectionEquality().hash(_exercises), + ); + + /// Create a copy of TrainingsState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$TrainingsStateImplCopyWith<_$TrainingsStateImpl> get copyWith => + __$$TrainingsStateImplCopyWithImpl<_$TrainingsStateImpl>( + this, + _$identity, + ); +} + +abstract class _TrainingsState implements TrainingsState { + const factory _TrainingsState({ + final List plans, + final List exercises, + }) = _$TrainingsStateImpl; + + @override + List get plans; + @override + List get exercises; + + /// Create a copy of TrainingsState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$TrainingsStateImplCopyWith<_$TrainingsStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/presentation/workout_session/widgets/activity_card.dart b/lib/presentation/workout_session/widgets/activity_card.dart new file mode 100644 index 0000000..d4b8fa6 --- /dev/null +++ b/lib/presentation/workout_session/widgets/activity_card.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; +import 'package:trainhub_flutter/domain/entities/workout_activity.dart'; + +class ActivityCard extends StatelessWidget { + final WorkoutActivityEntity activity; + + const ActivityCard({super.key, required this.activity}); + + @override + Widget build(BuildContext context) { + final isRest = activity.type == 'rest'; + + return Card( + elevation: 4, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + child: Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + children: [ + Text( + activity.name, + style: Theme.of(context).textTheme.headlineMedium, + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + if (!isRest) ...[ + Text( + "${activity.sectionName} • Set ${activity.setIndex}/${activity.totalSets}", + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: Theme.of(context).colorScheme.secondary, + ), + ), + const SizedBox(height: 16), + if (activity.originalExercise != null) + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildInfo( + context, + "Sets", + "${activity.originalExercise!.sets}", + ), + const SizedBox(width: 24), + _buildInfo( + context, + activity.originalExercise!.isTime ? "Secs" : "Reps", + "${activity.originalExercise!.value}", + ), + ], + ), + ] else + Text( + "Resting...", + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: Theme.of(context).colorScheme.secondary, + ), + ), + ], + ), + ), + ); + } + + Widget _buildInfo(BuildContext context, String label, String value) { + return Column( + children: [ + Text(value, style: Theme.of(context).textTheme.headlineSmall), + Text(label, style: Theme.of(context).textTheme.labelMedium), + ], + ); + } +} diff --git a/lib/presentation/workout_session/widgets/session_controls.dart b/lib/presentation/workout_session/widgets/session_controls.dart new file mode 100644 index 0000000..b479e1d --- /dev/null +++ b/lib/presentation/workout_session/widgets/session_controls.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; + +class SessionControls extends StatelessWidget { + final bool isRunning; + final bool isFinished; + final VoidCallback onPause; + final VoidCallback onPlay; + final VoidCallback onNext; + final VoidCallback onPrevious; + + const SessionControls({ + super.key, + required this.isRunning, + required this.isFinished, + required this.onPause, + required this.onPlay, + required this.onNext, + required this.onPrevious, + }); + + @override + Widget build(BuildContext context) { + if (isFinished) { + return ElevatedButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Finish Workout'), + ); + } + + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + IconButton.filledTonal( + onPressed: onPrevious, + icon: const Icon(Icons.skip_previous), + iconSize: 32, + ), + const SizedBox(width: 24), + IconButton.filled( + onPressed: isRunning ? onPause : onPlay, + icon: Icon(isRunning ? Icons.pause : Icons.play_arrow), + iconSize: 48, + style: IconButton.styleFrom(padding: const EdgeInsets.all(16)), + ), + const SizedBox(width: 24), + IconButton.filledTonal( + onPressed: onNext, + icon: const Icon(Icons.skip_next), + iconSize: 32, + ), + ], + ); + } +} diff --git a/lib/presentation/workout_session/widgets/session_progress_bar.dart b/lib/presentation/workout_session/widgets/session_progress_bar.dart new file mode 100644 index 0000000..ab70ebd --- /dev/null +++ b/lib/presentation/workout_session/widgets/session_progress_bar.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; + +class SessionProgressBar extends StatelessWidget { + final double progress; + + const SessionProgressBar({super.key, required this.progress}); + + @override + Widget build(BuildContext context) { + return LinearProgressIndicator( + value: progress, + minHeight: 8, + backgroundColor: Theme.of(context).colorScheme.surfaceContainerHighest, + ); + } +} diff --git a/lib/presentation/workout_session/workout_session_controller.dart b/lib/presentation/workout_session/workout_session_controller.dart new file mode 100644 index 0000000..e558e65 --- /dev/null +++ b/lib/presentation/workout_session/workout_session_controller.dart @@ -0,0 +1,182 @@ +import 'dart:async'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:trainhub_flutter/domain/entities/training_plan.dart'; +import 'package:trainhub_flutter/domain/entities/workout_activity.dart'; +import 'package:trainhub_flutter/presentation/workout_session/workout_session_state.dart'; +import 'package:trainhub_flutter/injection.dart'; +import 'package:trainhub_flutter/domain/repositories/training_plan_repository.dart'; + +part 'workout_session_controller.g.dart'; + +@riverpod +class WorkoutSessionController extends _$WorkoutSessionController { + Timer? _timer; + + @override + Future build(String planId) async { + final planRepo = getIt(); + final plan = await planRepo.getById(planId); + + final activities = _buildSequence(plan); + ref.onDispose(() => _timer?.cancel()); + + final initialState = WorkoutSessionState(activities: activities); + + if (activities.isNotEmpty) { + final first = activities.first; + return initialState.copyWith(timeRemaining: first.duration); + } + return initialState; + } + + List _buildSequence(TrainingPlanEntity plan) { + final List seq = []; + for (final section in plan.sections) { + for (final ex in section.exercises) { + for (int s = 1; s <= ex.sets; s++) { + seq.add( + WorkoutActivityEntity( + id: '${ex.instanceId}-s$s-work', + name: ex.name, + type: 'work', + duration: ex.isTime ? ex.value : 0, + originalExercise: ex, + sectionName: section.name, + setIndex: s, + totalSets: ex.sets, + ), + ); + final bool isLastOfWorkout = + s == ex.sets && + section.exercises.last == ex && + plan.sections.last == section; + if (ex.rest > 0 && !isLastOfWorkout) { + seq.add( + WorkoutActivityEntity( + id: '${ex.instanceId}-s$s-rest', + name: 'Rest', + type: 'rest', + duration: ex.rest, + sectionName: section.name, + ), + ); + } + } + } + } + return seq; + } + + void startTimer() { + if (_timer != null && _timer!.isActive) return; + _timer = Timer.periodic(const Duration(seconds: 1), _tick); + final currentState = state.value; + if (currentState != null) { + state = AsyncValue.data(currentState.copyWith(isRunning: true)); + } + } + + void pauseTimer() { + _timer?.cancel(); + _timer = null; + final currentState = state.value; + if (currentState != null) { + state = AsyncValue.data(currentState.copyWith(isRunning: false)); + } + } + + void _tick(Timer timer) { + if (state.value?.isFinished ?? true) return; + var currentState = state.value!; + + var newState = currentState.copyWith( + totalTimeElapsed: currentState.totalTimeElapsed + 1, + ); + final activity = newState.currentActivity; + + if (activity != null && activity.duration > 0 && newState.isRunning) { + if (newState.timeRemaining > 0) { + newState = newState.copyWith(timeRemaining: newState.timeRemaining - 1); + } else { + state = AsyncValue.data(newState); // update interim state before next + _goNext(newState); + return; + } + } + state = AsyncValue.data(newState); + } + + void next() { + final currentState = state.value; + if (currentState != null) _goNext(currentState); + } + + void _goNext(WorkoutSessionState currentState) { + if (currentState.currentIndex < currentState.activities.length - 1) { + final nextIndex = currentState.currentIndex + 1; + final nextActivity = currentState.activities[nextIndex]; + + final newState = currentState.copyWith( + currentIndex: nextIndex, + timeRemaining: nextActivity.duration, + ); + + state = AsyncValue.data(newState); + + if (nextActivity.isRest) { + startTimer(); + } else { + pauseTimer(); + } + } else { + _finish(); + } + } + + void previous() { + final currentState = state.value; + if (currentState != null && currentState.currentIndex > 0) { + final prevIndex = currentState.currentIndex - 1; + final prevActivity = currentState.activities[prevIndex]; + + state = AsyncValue.data( + currentState.copyWith( + currentIndex: prevIndex, + timeRemaining: prevActivity.duration, + ), + ); + + pauseTimer(); + } + } + + void jumpTo(int index) { + final currentState = state.value; + if (currentState != null && + index >= 0 && + index < currentState.activities.length) { + final activity = currentState.activities[index]; + + state = AsyncValue.data( + currentState.copyWith( + currentIndex: index, + timeRemaining: activity.duration, + ), + ); + + if (activity.isRest) { + startTimer(); + } else { + pauseTimer(); + } + } + } + + void _finish() { + pauseTimer(); + final currentState = state.value; + if (currentState != null) { + state = AsyncValue.data(currentState.copyWith(isFinished: true)); + } + } +} diff --git a/lib/presentation/workout_session/workout_session_controller.g.dart b/lib/presentation/workout_session/workout_session_controller.g.dart new file mode 100644 index 0000000..9df754c --- /dev/null +++ b/lib/presentation/workout_session/workout_session_controller.g.dart @@ -0,0 +1,179 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'workout_session_controller.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$workoutSessionControllerHash() => + r'd3f53d72c80963634c6edaeb44aa5b04c9ffba6d'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +abstract class _$WorkoutSessionController + extends BuildlessAutoDisposeAsyncNotifier { + late final String planId; + + FutureOr build(String planId); +} + +/// See also [WorkoutSessionController]. +@ProviderFor(WorkoutSessionController) +const workoutSessionControllerProvider = WorkoutSessionControllerFamily(); + +/// See also [WorkoutSessionController]. +class WorkoutSessionControllerFamily + extends Family> { + /// See also [WorkoutSessionController]. + const WorkoutSessionControllerFamily(); + + /// See also [WorkoutSessionController]. + WorkoutSessionControllerProvider call(String planId) { + return WorkoutSessionControllerProvider(planId); + } + + @override + WorkoutSessionControllerProvider getProviderOverride( + covariant WorkoutSessionControllerProvider provider, + ) { + return call(provider.planId); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'workoutSessionControllerProvider'; +} + +/// See also [WorkoutSessionController]. +class WorkoutSessionControllerProvider + extends + AutoDisposeAsyncNotifierProviderImpl< + WorkoutSessionController, + WorkoutSessionState + > { + /// See also [WorkoutSessionController]. + WorkoutSessionControllerProvider(String planId) + : this._internal( + () => WorkoutSessionController()..planId = planId, + from: workoutSessionControllerProvider, + name: r'workoutSessionControllerProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$workoutSessionControllerHash, + dependencies: WorkoutSessionControllerFamily._dependencies, + allTransitiveDependencies: + WorkoutSessionControllerFamily._allTransitiveDependencies, + planId: planId, + ); + + WorkoutSessionControllerProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.planId, + }) : super.internal(); + + final String planId; + + @override + FutureOr runNotifierBuild( + covariant WorkoutSessionController notifier, + ) { + return notifier.build(planId); + } + + @override + Override overrideWith(WorkoutSessionController Function() create) { + return ProviderOverride( + origin: this, + override: WorkoutSessionControllerProvider._internal( + () => create()..planId = planId, + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + planId: planId, + ), + ); + } + + @override + AutoDisposeAsyncNotifierProviderElement< + WorkoutSessionController, + WorkoutSessionState + > + createElement() { + return _WorkoutSessionControllerProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is WorkoutSessionControllerProvider && other.planId == planId; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, planId.hashCode); + + return _SystemHash.finish(hash); + } +} + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +mixin WorkoutSessionControllerRef + on AutoDisposeAsyncNotifierProviderRef { + /// The parameter `planId` of this provider. + String get planId; +} + +class _WorkoutSessionControllerProviderElement + extends + AutoDisposeAsyncNotifierProviderElement< + WorkoutSessionController, + WorkoutSessionState + > + with WorkoutSessionControllerRef { + _WorkoutSessionControllerProviderElement(super.provider); + + @override + String get planId => (origin as WorkoutSessionControllerProvider).planId; +} + +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/presentation/workout_session/workout_session_page.dart b/lib/presentation/workout_session/workout_session_page.dart new file mode 100644 index 0000000..f282a3c --- /dev/null +++ b/lib/presentation/workout_session/workout_session_page.dart @@ -0,0 +1,595 @@ +import 'dart:math'; +import 'dart:ui'; + +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:trainhub_flutter/core/constants/ui_constants.dart'; +import 'package:trainhub_flutter/core/theme/app_colors.dart'; +import 'package:trainhub_flutter/presentation/workout_session/workout_session_controller.dart'; +import 'package:trainhub_flutter/presentation/workout_session/workout_session_state.dart'; +import 'package:trainhub_flutter/presentation/workout_session/widgets/activity_card.dart'; +import 'package:trainhub_flutter/presentation/workout_session/widgets/session_controls.dart'; +import 'package:trainhub_flutter/presentation/workout_session/widgets/session_progress_bar.dart'; + +@RoutePage() +class WorkoutSessionPage extends ConsumerWidget { + final String planId; + + const WorkoutSessionPage({ + super.key, + @PathParam('planId') required this.planId, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final asyncState = ref.watch(workoutSessionControllerProvider(planId)); + + return Scaffold( + backgroundColor: AppColors.zinc950, + body: asyncState.when( + loading: () => const Center( + child: CircularProgressIndicator( + color: AppColors.accent, + strokeWidth: 2, + ), + ), + error: (err, stack) => Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.error_outline_rounded, + color: AppColors.destructive, + size: 48, + ), + const SizedBox(height: UIConstants.spacing16), + Text( + 'Failed to load workout', + style: TextStyle( + color: AppColors.textPrimary, + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: UIConstants.spacing8), + Text( + '$err', + style: TextStyle( + color: AppColors.textMuted, + fontSize: 14, + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + data: (state) { + final controller = ref.read( + workoutSessionControllerProvider(planId).notifier, + ); + + if (state.isFinished) { + return _CompletionScreen( + totalTimeElapsed: state.totalTimeElapsed, + ); + } + + final isRest = state.currentActivity?.isRest ?? false; + + return _ActiveSessionView( + state: state, + isRest: isRest, + controller: controller, + ); + }, + ), + ); + } +} + +// --------------------------------------------------------------------------- +// Active session view (gradient background + timer + controls) +// --------------------------------------------------------------------------- +class _ActiveSessionView extends StatelessWidget { + final WorkoutSessionState state; + final bool isRest; + final WorkoutSessionController controller; + + const _ActiveSessionView({ + required this.state, + required this.isRest, + required this.controller, + }); + + @override + Widget build(BuildContext context) { + // Compute the time progress for the circular ring. + final activity = state.currentActivity; + final double timeProgress; + if (activity != null && activity.duration > 0) { + timeProgress = 1.0 - (state.timeRemaining / activity.duration); + } else { + timeProgress = 0.0; + } + + final accentTint = isRest + ? AppColors.info.withValues(alpha: 0.06) + : AppColors.accent.withValues(alpha: 0.06); + final ringColor = isRest ? AppColors.info : AppColors.accent; + + return Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + AppColors.zinc950, + accentTint, + AppColors.zinc950, + ], + stops: const [0.0, 0.5, 1.0], + ), + ), + child: Column( + children: [ + // -- Top progress bar -- + SessionProgressBar(progress: state.progress), + + // -- Elapsed time badge -- + Padding( + padding: const EdgeInsets.only( + top: UIConstants.spacing16, + right: UIConstants.spacing24, + ), + child: Align( + alignment: Alignment.centerRight, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: UIConstants.spacing12, + vertical: UIConstants.spacing4, + ), + decoration: BoxDecoration( + color: AppColors.zinc800.withValues(alpha: 0.6), + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: AppColors.border.withValues(alpha: 0.5), + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.timer_outlined, + size: 14, + color: AppColors.textMuted, + ), + const SizedBox(width: 4), + Text( + _formatDuration(state.totalTimeElapsed), + style: const TextStyle( + color: AppColors.textSecondary, + fontSize: 13, + fontWeight: FontWeight.w500, + fontFeatures: [FontFeature.tabularFigures()], + ), + ), + ], + ), + ), + ), + ), + + // -- Central content -- + Expanded( + child: Center( + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric( + horizontal: UIConstants.spacing24, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Activity info card + if (activity != null) + ActivityCard(activity: activity), + + const SizedBox(height: UIConstants.spacing32), + + // Circular progress ring + timer + _CircularTimerDisplay( + timeRemaining: state.timeRemaining, + progress: timeProgress, + ringColor: ringColor, + isRunning: state.isRunning, + isTimeBased: activity?.isTimeBased ?? false, + ), + + const SizedBox(height: UIConstants.spacing24), + + // "Up next" pill + if (state.nextActivity != null) + _UpNextPill( + nextActivityName: state.nextActivity!.name, + isNextRest: state.nextActivity!.isRest, + ), + ], + ), + ), + ), + ), + + // -- Bottom controls -- + SessionControls( + isRunning: state.isRunning, + isFinished: state.isFinished, + onPause: controller.pauseTimer, + onPlay: controller.startTimer, + onNext: controller.next, + onPrevious: controller.previous, + ), + + const SizedBox(height: UIConstants.spacing24), + ], + ), + ); + } + + String _formatDuration(int seconds) { + final m = seconds ~/ 60; + final s = seconds % 60; + return '${m.toString().padLeft(2, '0')}:${s.toString().padLeft(2, '0')}'; + } +} + +// --------------------------------------------------------------------------- +// Circular timer with arc progress ring +// --------------------------------------------------------------------------- +class _CircularTimerDisplay extends StatelessWidget { + final int timeRemaining; + final double progress; + final Color ringColor; + final bool isRunning; + final bool isTimeBased; + + const _CircularTimerDisplay({ + required this.timeRemaining, + required this.progress, + required this.ringColor, + required this.isRunning, + required this.isTimeBased, + }); + + @override + Widget build(BuildContext context) { + const double size = 220; + const double strokeWidth = 6.0; + + return SizedBox( + width: size, + height: size, + child: Stack( + alignment: Alignment.center, + children: [ + // Background track + SizedBox( + width: size, + height: size, + child: CircularProgressIndicator( + value: 1.0, + strokeWidth: strokeWidth, + color: AppColors.zinc800.withValues(alpha: 0.5), + strokeCap: StrokeCap.round, + ), + ), + + // Progress arc + if (isTimeBased) + SizedBox( + width: size, + height: size, + child: CircularProgressIndicator( + value: progress.clamp(0.0, 1.0), + strokeWidth: strokeWidth, + color: ringColor, + backgroundColor: Colors.transparent, + strokeCap: StrokeCap.round, + ), + ), + + // Glow behind the timer text + Container( + width: size * 0.7, + height: size * 0.7, + decoration: BoxDecoration( + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: ringColor.withValues(alpha: 0.08), + blurRadius: 40, + spreadRadius: 10, + ), + ], + ), + ), + + // Timer text + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + _formatTime(timeRemaining), + style: TextStyle( + color: AppColors.textPrimary, + fontSize: 52, + fontWeight: FontWeight.w300, + letterSpacing: 2, + fontFeatures: const [FontFeature.tabularFigures()], + fontFamily: 'monospace', + ), + ), + if (!isTimeBased) + Padding( + padding: const EdgeInsets.only(top: 4), + child: Text( + 'UNTIMED', + style: TextStyle( + color: AppColors.textMuted, + fontSize: 11, + fontWeight: FontWeight.w600, + letterSpacing: 2, + ), + ), + ), + if (isTimeBased && !isRunning) + Padding( + padding: const EdgeInsets.only(top: 4), + child: Text( + 'PAUSED', + style: TextStyle( + color: AppColors.textMuted, + fontSize: 11, + fontWeight: FontWeight.w600, + letterSpacing: 2, + ), + ), + ), + ], + ), + ], + ), + ); + } + + String _formatTime(int seconds) { + final m = seconds ~/ 60; + final s = seconds % 60; + return '${m.toString().padLeft(2, '0')}:${s.toString().padLeft(2, '0')}'; + } +} + +// --------------------------------------------------------------------------- +// "Up next" pill +// --------------------------------------------------------------------------- +class _UpNextPill extends StatelessWidget { + final String nextActivityName; + final bool isNextRest; + + const _UpNextPill({ + required this.nextActivityName, + required this.isNextRest, + }); + + @override + Widget build(BuildContext context) { + final pillColor = isNextRest + ? AppColors.info.withValues(alpha: 0.12) + : AppColors.accent.withValues(alpha: 0.12); + final pillBorderColor = isNextRest + ? AppColors.info.withValues(alpha: 0.25) + : AppColors.accent.withValues(alpha: 0.25); + final labelColor = isNextRest ? AppColors.info : AppColors.accent; + + return Container( + padding: const EdgeInsets.symmetric( + horizontal: UIConstants.spacing16, + vertical: UIConstants.spacing8, + ), + decoration: BoxDecoration( + color: pillColor, + borderRadius: BorderRadius.circular(24), + border: Border.all(color: pillBorderColor), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'UP NEXT', + style: TextStyle( + color: labelColor, + fontSize: 10, + fontWeight: FontWeight.w700, + letterSpacing: 1.5, + ), + ), + const SizedBox(width: UIConstants.spacing8), + Container( + width: 3, + height: 3, + decoration: BoxDecoration( + color: AppColors.textMuted, + shape: BoxShape.circle, + ), + ), + const SizedBox(width: UIConstants.spacing8), + Flexible( + child: Text( + nextActivityName, + style: const TextStyle( + color: AppColors.textSecondary, + fontSize: 13, + fontWeight: FontWeight.w500, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ); + } +} + +// --------------------------------------------------------------------------- +// Completion screen +// --------------------------------------------------------------------------- +class _CompletionScreen extends StatelessWidget { + final int totalTimeElapsed; + + const _CompletionScreen({required this.totalTimeElapsed}); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + AppColors.zinc950, + AppColors.success.withValues(alpha: 0.06), + AppColors.zinc950, + ], + stops: const [0.0, 0.45, 1.0], + ), + ), + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Checkmark circle + Container( + width: 96, + height: 96, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppColors.success.withValues(alpha: 0.12), + border: Border.all( + color: AppColors.success.withValues(alpha: 0.3), + width: 2, + ), + boxShadow: [ + BoxShadow( + color: AppColors.success.withValues(alpha: 0.15), + blurRadius: 40, + spreadRadius: 8, + ), + ], + ), + child: const Icon( + Icons.check_rounded, + color: AppColors.success, + size: 48, + ), + ), + + const SizedBox(height: UIConstants.spacing24), + + const Text( + 'Workout Complete', + style: TextStyle( + color: AppColors.textPrimary, + fontSize: 28, + fontWeight: FontWeight.w600, + letterSpacing: -0.5, + ), + ), + + const SizedBox(height: UIConstants.spacing8), + + Text( + 'Great job! You crushed it.', + style: TextStyle( + color: AppColors.textSecondary, + fontSize: 15, + ), + ), + + const SizedBox(height: UIConstants.spacing32), + + // Total time card + Container( + padding: const EdgeInsets.symmetric( + horizontal: UIConstants.spacing24, + vertical: UIConstants.spacing16, + ), + decoration: BoxDecoration( + color: AppColors.surfaceContainer.withValues(alpha: 0.6), + borderRadius: BorderRadius.circular(UIConstants.borderRadius), + border: Border.all( + color: AppColors.border.withValues(alpha: 0.5), + ), + ), + child: Column( + children: [ + Text( + 'TOTAL TIME', + style: TextStyle( + color: AppColors.textMuted, + fontSize: 11, + fontWeight: FontWeight.w600, + letterSpacing: 1.5, + ), + ), + const SizedBox(height: UIConstants.spacing4), + Text( + _formatDuration(totalTimeElapsed), + style: TextStyle( + color: AppColors.textPrimary, + fontSize: 36, + fontWeight: FontWeight.w300, + fontFeatures: const [FontFeature.tabularFigures()], + fontFamily: 'monospace', + ), + ), + ], + ), + ), + + const SizedBox(height: UIConstants.spacing32), + + // Finish button + SizedBox( + width: 200, + height: 48, + child: FilledButton( + onPressed: () => context.router.maybePop(), + style: FilledButton.styleFrom( + backgroundColor: AppColors.success, + foregroundColor: AppColors.zinc950, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(24), + ), + textStyle: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.w600, + ), + ), + child: const Text('Finish'), + ), + ), + ], + ), + ), + ); + } + + String _formatDuration(int seconds) { + final h = seconds ~/ 3600; + final m = (seconds % 3600) ~/ 60; + final s = seconds % 60; + if (h > 0) { + return '${h.toString().padLeft(2, '0')}:${m.toString().padLeft(2, '0')}:${s.toString().padLeft(2, '0')}'; + } + return '${m.toString().padLeft(2, '0')}:${s.toString().padLeft(2, '0')}'; + } +} diff --git a/lib/presentation/workout_session/workout_session_state.dart b/lib/presentation/workout_session/workout_session_state.dart new file mode 100644 index 0000000..d83a928 --- /dev/null +++ b/lib/presentation/workout_session/workout_session_state.dart @@ -0,0 +1,29 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:trainhub_flutter/domain/entities/workout_activity.dart'; + +part 'workout_session_state.freezed.dart'; + +@freezed +class WorkoutSessionState with _$WorkoutSessionState { + const factory WorkoutSessionState({ + required List activities, + @Default(0) int currentIndex, + @Default(0) int timeRemaining, + @Default(0) int totalTimeElapsed, + @Default(false) bool isRunning, + @Default(false) bool isFinished, + }) = _WorkoutSessionState; + + const WorkoutSessionState._(); + + WorkoutActivityEntity? get currentActivity => + currentIndex < activities.length ? activities[currentIndex] : null; + + WorkoutActivityEntity? get nextActivity => + currentIndex + 1 < activities.length + ? activities[currentIndex + 1] + : null; + + double get progress => + activities.isEmpty ? 0.0 : currentIndex / activities.length; +} diff --git a/lib/presentation/workout_session/workout_session_state.freezed.dart b/lib/presentation/workout_session/workout_session_state.freezed.dart new file mode 100644 index 0000000..239c458 --- /dev/null +++ b/lib/presentation/workout_session/workout_session_state.freezed.dart @@ -0,0 +1,293 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'workout_session_state.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$WorkoutSessionState { + List get activities => + throw _privateConstructorUsedError; + int get currentIndex => throw _privateConstructorUsedError; + int get timeRemaining => throw _privateConstructorUsedError; + int get totalTimeElapsed => throw _privateConstructorUsedError; + bool get isRunning => throw _privateConstructorUsedError; + bool get isFinished => throw _privateConstructorUsedError; + + /// Create a copy of WorkoutSessionState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $WorkoutSessionStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $WorkoutSessionStateCopyWith<$Res> { + factory $WorkoutSessionStateCopyWith( + WorkoutSessionState value, + $Res Function(WorkoutSessionState) then, + ) = _$WorkoutSessionStateCopyWithImpl<$Res, WorkoutSessionState>; + @useResult + $Res call({ + List activities, + int currentIndex, + int timeRemaining, + int totalTimeElapsed, + bool isRunning, + bool isFinished, + }); +} + +/// @nodoc +class _$WorkoutSessionStateCopyWithImpl<$Res, $Val extends WorkoutSessionState> + implements $WorkoutSessionStateCopyWith<$Res> { + _$WorkoutSessionStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of WorkoutSessionState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? activities = null, + Object? currentIndex = null, + Object? timeRemaining = null, + Object? totalTimeElapsed = null, + Object? isRunning = null, + Object? isFinished = null, + }) { + return _then( + _value.copyWith( + activities: null == activities + ? _value.activities + : activities // ignore: cast_nullable_to_non_nullable + as List, + currentIndex: null == currentIndex + ? _value.currentIndex + : currentIndex // ignore: cast_nullable_to_non_nullable + as int, + timeRemaining: null == timeRemaining + ? _value.timeRemaining + : timeRemaining // ignore: cast_nullable_to_non_nullable + as int, + totalTimeElapsed: null == totalTimeElapsed + ? _value.totalTimeElapsed + : totalTimeElapsed // ignore: cast_nullable_to_non_nullable + as int, + isRunning: null == isRunning + ? _value.isRunning + : isRunning // ignore: cast_nullable_to_non_nullable + as bool, + isFinished: null == isFinished + ? _value.isFinished + : isFinished // ignore: cast_nullable_to_non_nullable + as bool, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$WorkoutSessionStateImplCopyWith<$Res> + implements $WorkoutSessionStateCopyWith<$Res> { + factory _$$WorkoutSessionStateImplCopyWith( + _$WorkoutSessionStateImpl value, + $Res Function(_$WorkoutSessionStateImpl) then, + ) = __$$WorkoutSessionStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + List activities, + int currentIndex, + int timeRemaining, + int totalTimeElapsed, + bool isRunning, + bool isFinished, + }); +} + +/// @nodoc +class __$$WorkoutSessionStateImplCopyWithImpl<$Res> + extends _$WorkoutSessionStateCopyWithImpl<$Res, _$WorkoutSessionStateImpl> + implements _$$WorkoutSessionStateImplCopyWith<$Res> { + __$$WorkoutSessionStateImplCopyWithImpl( + _$WorkoutSessionStateImpl _value, + $Res Function(_$WorkoutSessionStateImpl) _then, + ) : super(_value, _then); + + /// Create a copy of WorkoutSessionState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? activities = null, + Object? currentIndex = null, + Object? timeRemaining = null, + Object? totalTimeElapsed = null, + Object? isRunning = null, + Object? isFinished = null, + }) { + return _then( + _$WorkoutSessionStateImpl( + activities: null == activities + ? _value._activities + : activities // ignore: cast_nullable_to_non_nullable + as List, + currentIndex: null == currentIndex + ? _value.currentIndex + : currentIndex // ignore: cast_nullable_to_non_nullable + as int, + timeRemaining: null == timeRemaining + ? _value.timeRemaining + : timeRemaining // ignore: cast_nullable_to_non_nullable + as int, + totalTimeElapsed: null == totalTimeElapsed + ? _value.totalTimeElapsed + : totalTimeElapsed // ignore: cast_nullable_to_non_nullable + as int, + isRunning: null == isRunning + ? _value.isRunning + : isRunning // ignore: cast_nullable_to_non_nullable + as bool, + isFinished: null == isFinished + ? _value.isFinished + : isFinished // ignore: cast_nullable_to_non_nullable + as bool, + ), + ); + } +} + +/// @nodoc + +class _$WorkoutSessionStateImpl extends _WorkoutSessionState { + const _$WorkoutSessionStateImpl({ + required final List activities, + this.currentIndex = 0, + this.timeRemaining = 0, + this.totalTimeElapsed = 0, + this.isRunning = false, + this.isFinished = false, + }) : _activities = activities, + super._(); + + final List _activities; + @override + List get activities { + if (_activities is EqualUnmodifiableListView) return _activities; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_activities); + } + + @override + @JsonKey() + final int currentIndex; + @override + @JsonKey() + final int timeRemaining; + @override + @JsonKey() + final int totalTimeElapsed; + @override + @JsonKey() + final bool isRunning; + @override + @JsonKey() + final bool isFinished; + + @override + String toString() { + return 'WorkoutSessionState(activities: $activities, currentIndex: $currentIndex, timeRemaining: $timeRemaining, totalTimeElapsed: $totalTimeElapsed, isRunning: $isRunning, isFinished: $isFinished)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$WorkoutSessionStateImpl && + const DeepCollectionEquality().equals( + other._activities, + _activities, + ) && + (identical(other.currentIndex, currentIndex) || + other.currentIndex == currentIndex) && + (identical(other.timeRemaining, timeRemaining) || + other.timeRemaining == timeRemaining) && + (identical(other.totalTimeElapsed, totalTimeElapsed) || + other.totalTimeElapsed == totalTimeElapsed) && + (identical(other.isRunning, isRunning) || + other.isRunning == isRunning) && + (identical(other.isFinished, isFinished) || + other.isFinished == isFinished)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_activities), + currentIndex, + timeRemaining, + totalTimeElapsed, + isRunning, + isFinished, + ); + + /// Create a copy of WorkoutSessionState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$WorkoutSessionStateImplCopyWith<_$WorkoutSessionStateImpl> get copyWith => + __$$WorkoutSessionStateImplCopyWithImpl<_$WorkoutSessionStateImpl>( + this, + _$identity, + ); +} + +abstract class _WorkoutSessionState extends WorkoutSessionState { + const factory _WorkoutSessionState({ + required final List activities, + final int currentIndex, + final int timeRemaining, + final int totalTimeElapsed, + final bool isRunning, + final bool isFinished, + }) = _$WorkoutSessionStateImpl; + const _WorkoutSessionState._() : super._(); + + @override + List get activities; + @override + int get currentIndex; + @override + int get timeRemaining; + @override + int get totalTimeElapsed; + @override + bool get isRunning; + @override + bool get isFinished; + + /// Create a copy of WorkoutSessionState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$WorkoutSessionStateImplCopyWith<_$WorkoutSessionStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/providers/analysis_provider.dart b/lib/providers/analysis_provider.dart new file mode 100644 index 0000000..bdc5145 --- /dev/null +++ b/lib/providers/analysis_provider.dart @@ -0,0 +1,117 @@ +import 'package:flutter/material.dart'; +import 'package:drift/drift.dart'; +import 'package:trainhub_flutter/database/database.dart'; + +class AnalysisProvider extends ChangeNotifier { + final AppDatabase database; + + List _sessions = []; + List _currentAnnotations = []; + AnalysisSession? _activeSession; + + List get sessions => _sessions; + List get currentAnnotations => _currentAnnotations; + AnalysisSession? get activeSession => _activeSession; + + AnalysisProvider(this.database) { + _loadSessions(); + } + + Future _loadSessions() async { + _sessions = await database.select(database.analysisSessions).get(); + notifyListeners(); + } + + Future createSession(String name, String videoPath) async { + final id = DateTime.now().toIso8601String(); + final session = AnalysisSessionsCompanion.insert( + id: id, + name: name, + videoPath: Value(videoPath), + date: DateTime.now().toIso8601String(), + ); + await database.into(database.analysisSessions).insert(session); + await _loadSessions(); + await loadSession(id); + } + + Future deleteSession(String id) async { + await (database.delete( + database.analysisSessions, + )..where((t) => t.id.equals(id))).go(); + if (_activeSession?.id == id) { + _activeSession = null; + _currentAnnotations = []; + } + await _loadSessions(); + } + + Future loadSession(String id) async { + final session = await (database.select( + database.analysisSessions, + )..where((t) => t.id.equals(id))).getSingleOrNull(); + if (session != null) { + _activeSession = session; + await _loadAnnotations(id); + } + } + + Future _loadAnnotations(String sessionId) async { + _currentAnnotations = await (database.select( + database.annotations, + )..where((t) => t.sessionId.equals(sessionId))).get(); + notifyListeners(); + } + + Future addAnnotation({ + required String name, + required String description, + required double startTime, + required double endTime, + required String color, + }) async { + if (_activeSession == null) return; + + final id = DateTime.now().toIso8601String(); + await database + .into(database.annotations) + .insert( + AnnotationsCompanion.insert( + id: id, + sessionId: _activeSession!.id, + name: Value(name), + description: Value(description), + startTime: startTime, + endTime: endTime, + color: Value(color), + ), + ); + await _loadAnnotations(_activeSession!.id); + } + + Future updateAnnotation(Annotation annotation) async { + await (database.update( + database.annotations, + )..where((t) => t.id.equals(annotation.id))).write( + AnnotationsCompanion( + name: Value(annotation.name), + description: Value(annotation.description), + startTime: Value(annotation.startTime), + endTime: Value(annotation.endTime), + color: Value(annotation.color), + ), + ); + if (_activeSession != null) { + await _loadAnnotations(_activeSession!.id); + } + } + + Future deleteAnnotation(String id) async { + await (database.delete( + database.annotations, + )..where((t) => t.id.equals(id))).go(); + if (_activeSession != null) { + await _loadAnnotations(_activeSession!.id); + } + } +} diff --git a/lib/providers/chat_provider.dart b/lib/providers/chat_provider.dart new file mode 100644 index 0000000..4bfdab0 --- /dev/null +++ b/lib/providers/chat_provider.dart @@ -0,0 +1,151 @@ +import 'package:flutter/material.dart'; +import 'package:drift/drift.dart'; +import 'package:trainhub_flutter/database/database.dart'; + +class ChatProvider extends ChangeNotifier { + final AppDatabase database; + + List _sessions = []; + List _currentMessages = []; + ChatSession? _activeSession; + + List get sessions => _sessions; + List get currentMessages => _currentMessages; + ChatSession? get activeSession => _activeSession; + + bool _isTyping = false; + bool get isTyping => _isTyping; + + ChatProvider(this.database) { + _loadSessions(); + } + + Future _loadSessions() async { + _sessions = + await (database.select(database.chatSessions)..orderBy([ + (t) => OrderingTerm( + expression: t.createdAt, + mode: OrderingMode.desc, + ), + ])) + .get(); + notifyListeners(); + } + + Future createSession() async { + final id = DateTime.now().toIso8601String(); + final session = ChatSessionsCompanion.insert( + id: id, + title: const Value('New Chat'), + createdAt: DateTime.now().toIso8601String(), + updatedAt: DateTime.now().toIso8601String(), + ); + await database.into(database.chatSessions).insert(session); + await _loadSessions(); + await loadSession(id); + } + + Future deleteSession(String id) async { + await (database.delete( + database.chatSessions, + )..where((t) => t.id.equals(id))).go(); + if (_activeSession?.id == id) { + _activeSession = null; + _currentMessages = []; + } + await _loadSessions(); + } + + Future loadSession(String id) async { + final session = await (database.select( + database.chatSessions, + )..where((t) => t.id.equals(id))).getSingleOrNull(); + if (session != null) { + _activeSession = session; + await _loadMessages(id); + } + } + + Future _loadMessages(String sessionId) async { + _currentMessages = + await (database.select(database.chatMessages) + ..where((t) => t.sessionId.equals(sessionId)) + ..orderBy([ + (t) => OrderingTerm( + expression: t.createdAt, + mode: OrderingMode.asc, + ), + ])) + .get(); + notifyListeners(); + } + + Future sendMessage(String content) async { + if (_activeSession == null) { + await createSession(); + } + final sessionId = _activeSession!.id; + + // User Message + final userMsgId = DateTime.now().toIso8601String(); + await database + .into(database.chatMessages) + .insert( + ChatMessagesCompanion.insert( + id: userMsgId, + sessionId: sessionId, + role: 'user', + content: content, + createdAt: DateTime.now().toIso8601String(), + ), + ); + await _loadMessages(sessionId); + + // AI Response (Mock) + _isTyping = true; + notifyListeners(); + + await Future.delayed(const Duration(seconds: 1)); // Simulate latency + + final aiMsgId = DateTime.now().toIso8601String(); + final response = _getMockResponse(content); + + await database + .into(database.chatMessages) + .insert( + ChatMessagesCompanion.insert( + id: aiMsgId, + sessionId: sessionId, + role: 'assistant', + content: response, + createdAt: DateTime.now().toIso8601String(), + ), + ); + + // Update session title if it's the first message + if (_currentMessages.length <= 2) { + final newTitle = content.length > 30 + ? '${content.substring(0, 30)}...' + : content; + await (database.update(database.chatSessions) + ..where((t) => t.id.equals(sessionId))) + .write(ChatSessionsCompanion(title: Value(newTitle))); + await _loadSessions(); + } + + _isTyping = false; + await _loadMessages(sessionId); + } + + String _getMockResponse(String input) { + input = input.toLowerCase(); + if (input.contains('plan') || input.contains('program')) { + return "I can help you design a training plan! What are your goals? Strength, hypertrophy, or endurance?"; + } else if (input.contains('squat') || input.contains('bench')) { + return "Compound movements are great. Remember to maintain proper form. For squats, keep your chest up and knees tracking over toes."; + } else if (input.contains('nutrition') || input.contains('eat')) { + return "Nutrition is key. Aim for 1.6-2.2g of protein per kg of bodyweight if you're training hard."; + } + return "I'm your AI training assistant. How can I help you today?"; + } +} diff --git a/lib/providers/program_provider.dart b/lib/providers/program_provider.dart new file mode 100644 index 0000000..9ff7b20 --- /dev/null +++ b/lib/providers/program_provider.dart @@ -0,0 +1,235 @@ +import 'package:flutter/material.dart'; +import 'package:drift/drift.dart'; +import 'package:trainhub_flutter/database/database.dart'; + +class ProgramProvider extends ChangeNotifier { + final AppDatabase database; + + List _programs = []; + Program? _activeProgram; + List _activeWeeks = []; + List _activeWorkouts = []; + + List get programs => _programs; + Program? get activeProgram => _activeProgram; + List get activeWeeks => _activeWeeks; + List get activeWorkouts => _activeWorkouts; + + ProgramProvider(this.database) { + _loadPrograms(); + } + + Future _loadPrograms() async { + _programs = + await (database.select(database.programs)..orderBy([ + (t) => OrderingTerm( + expression: t.createdAt, + mode: OrderingMode.desc, + ), + ])) + .get(); + + // Auto-select most recent if none selected, or re-verify active one + if (_activeProgram == null && _programs.isNotEmpty) { + await loadProgram(_programs.first.id); + } else if (_activeProgram != null) { + // Refresh active program data + await loadProgram(_activeProgram!.id); + } else { + notifyListeners(); + } + } + + Future loadProgram(String id) async { + final program = await (database.select( + database.programs, + )..where((t) => t.id.equals(id))).getSingleOrNull(); + if (program != null) { + _activeProgram = program; + await _loadProgramDetails(id); + } else { + _activeProgram = null; + _activeWeeks = []; + _activeWorkouts = []; + notifyListeners(); + } + } + + Future _loadProgramDetails(String programId) async { + _activeWeeks = + await (database.select(database.programWeeks) + ..where((t) => t.programId.equals(programId)) + ..orderBy([(t) => OrderingTerm(expression: t.position)])) + .get(); + + _activeWorkouts = await (database.select( + database.programWorkouts, + )..where((t) => t.programId.equals(programId))).get(); + notifyListeners(); + } + + // --- Program Actions --- + + Future createProgram(String name) async { + final id = DateTime.now().toIso8601String(); + await database + .into(database.programs) + .insert( + ProgramsCompanion.insert( + id: id, + name: name, + createdAt: DateTime.now().toIso8601String(), + ), + ); + await _loadPrograms(); + await loadProgram(id); + } + + Future deleteProgram(String id) async { + await (database.delete( + database.programs, + )..where((t) => t.id.equals(id))).go(); + if (_activeProgram?.id == id) { + _activeProgram = null; + } + await _loadPrograms(); + } + + Future duplicateProgram(String sourceId) async { + final sourceProgram = await (database.select( + database.programs, + )..where((t) => t.id.equals(sourceId))).getSingle(); + + final newId = DateTime.now().toIso8601String(); + await database + .into(database.programs) + .insert( + ProgramsCompanion.insert( + id: newId, + name: '${sourceProgram.name} (Copy)', + createdAt: DateTime.now().toIso8601String(), + ), + ); + + // Duplicate Weeks and Workouts + // Note: implementing deep copy logic + final weeks = await (database.select( + database.programWeeks, + )..where((t) => t.programId.equals(sourceId))).get(); + final workouts = await (database.select( + database.programWorkouts, + )..where((t) => t.programId.equals(sourceId))).get(); + + for (var week in weeks) { + final newWeekId = '${newId}_${week.position}'; // Simple ID gen + await database + .into(database.programWeeks) + .insert( + ProgramWeeksCompanion.insert( + id: newWeekId, + programId: newId, + position: week.position, + notes: Value(week.notes), + ), + ); + + final weekWorkouts = workouts.where((w) => w.weekId == week.id); + for (var workout in weekWorkouts) { + final newWorkoutId = + DateTime.now().toIso8601String() + workout.id; // ensure uniqueness + await database + .into(database.programWorkouts) + .insert( + ProgramWorkoutsCompanion.insert( + id: newWorkoutId, + weekId: newWeekId, + programId: newId, + day: workout.day, + type: workout.type, + refId: Value(workout.refId), + name: Value(workout.name), + description: Value(workout.description), + completed: const Value(false), + ), + ); + } + } + + await _loadPrograms(); + await loadProgram(newId); + } + + // --- Week Actions --- + + Future addWeek() async { + if (_activeProgram == null) return; + final nextPosition = _activeWeeks.isEmpty + ? 1 + : _activeWeeks.last.position + 1; + final id = DateTime.now().toIso8601String(); + + await database + .into(database.programWeeks) + .insert( + ProgramWeeksCompanion.insert( + id: id, + programId: _activeProgram!.id, + position: nextPosition, + ), + ); + await _loadProgramDetails(_activeProgram!.id); + } + + Future deleteWeek(String id) async { + if (_activeProgram == null) return; + await (database.delete( + database.programWeeks, + )..where((t) => t.id.equals(id))).go(); + await _loadProgramDetails(_activeProgram!.id); + } + + Future updateWeekNote(String weekId, String note) async { + await (database.update(database.programWeeks) + ..where((t) => t.id.equals(weekId))) + .write(ProgramWeeksCompanion(notes: Value(note))); + if (_activeProgram != null) await _loadProgramDetails(_activeProgram!.id); + } + + // --- Workout Actions --- + + Future addWorkout(ProgramWorkoutsCompanion workout) async { + await database.into(database.programWorkouts).insert(workout); + if (_activeProgram != null) await _loadProgramDetails(_activeProgram!.id); + } + + Future updateWorkout(ProgramWorkout workout) async { + await (database.update( + database.programWorkouts, + )..where((t) => t.id.equals(workout.id))).write( + ProgramWorkoutsCompanion( + day: Value(workout.day), + type: Value(workout.type), + refId: Value(workout.refId), + name: Value(workout.name), + description: Value(workout.description), + completed: Value(workout.completed), + weekId: Value(workout.weekId), + ), + ); + if (_activeProgram != null) await _loadProgramDetails(_activeProgram!.id); + } + + Future deleteWorkout(String id) async { + await (database.delete( + database.programWorkouts, + )..where((t) => t.id.equals(id))).go(); + if (_activeProgram != null) await _loadProgramDetails(_activeProgram!.id); + } + + Future toggleWorkoutComplete(String id, bool currentStatus) async { + await (database.update(database.programWorkouts) + ..where((t) => t.id.equals(id))) + .write(ProgramWorkoutsCompanion(completed: Value(!currentStatus))); + if (_activeProgram != null) await _loadProgramDetails(_activeProgram!.id); + } +} diff --git a/lib/providers/trainings_provider.dart b/lib/providers/trainings_provider.dart new file mode 100644 index 0000000..69cafe6 --- /dev/null +++ b/lib/providers/trainings_provider.dart @@ -0,0 +1,116 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:drift/drift.dart'; +import 'package:trainhub_flutter/database/database.dart'; +import 'package:trainhub_flutter/models/training_models.dart'; + +class TrainingsProvider extends ChangeNotifier { + final AppDatabase database; + + List _plans = []; + List _exercises = []; + + List get plans => _plans; + List get exercises => _exercises; + + TrainingsProvider(this.database) { + _loadData(); + } + + Future _loadData() async { + final dbPlans = await database.select(database.trainingPlans).get(); + _plans = dbPlans.map((p) { + final sectionsJson = p.sections != null && p.sections!.isNotEmpty + ? jsonDecode(p.sections!) + : []; + // Manual mapping or just pass to model + return TrainingPlanModel.fromJson({ + 'id': p.id, + 'name': p.name, + 'sections': sectionsJson, + }); + }).toList(); + + _exercises = await database.select(database.exercises).get(); + notifyListeners(); + } + + Future createPlan(String name) async { + final id = DateTime.now().toIso8601String(); + await database + .into(database.trainingPlans) + .insert( + TrainingPlansCompanion.insert( + id: id, + name: name, + sections: const Value('[]'), + ), + ); + await _loadData(); + return _plans.firstWhere((p) => p.id == id); + } + + Future updatePlan(TrainingPlanModel plan) async { + await (database.update( + database.trainingPlans, + )..where((t) => t.id.equals(plan.id))).write( + TrainingPlansCompanion( + name: Value(plan.name), + sections: Value( + jsonEncode(plan.sections.map((s) => s.toJson()).toList()), + ), + ), + ); + await _loadData(); + } + + Future deletePlan(String id) async { + await (database.delete( + database.trainingPlans, + )..where((t) => t.id.equals(id))).go(); + await _loadData(); + } + + Future addExercise( + String name, + String instructions, + String tags, + String videoUrl, + ) async { + final id = DateTime.now().toIso8601String(); + await database + .into(database.exercises) + .insert( + ExercisesCompanion.insert( + id: id, + name: name, + instructions: Value(instructions), + tags: Value(tags), // Storing as JSON string + videoUrl: Value(videoUrl), + ), + ); + await _loadData(); + return _exercises.firstWhere((e) => e.id == id); + } + + Future updateExercise(Exercise exercise) async { + await (database.update( + database.exercises, + )..where((t) => t.id.equals(exercise.id))).write( + ExercisesCompanion( + name: Value(exercise.name), + instructions: Value(exercise.instructions), + tags: Value(exercise.tags), + videoUrl: Value(exercise.videoUrl), + ), + ); + await _loadData(); + } + + Future deleteExercise(String id) async { + await (database.delete( + database.exercises, + )..where((t) => t.id.equals(id))).go(); + await _loadData(); + } +} diff --git a/lib/theme.dart b/lib/theme.dart new file mode 100644 index 0000000..0c7c0e2 --- /dev/null +++ b/lib/theme.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +class HTheme { + static final darkTheme = ThemeData( + useMaterial3: true, + brightness: Brightness.dark, + colorScheme: const ColorScheme.dark( + primary: Color(0xFFFAFAFA), // Zinc 50 + onPrimary: Color(0xFF09090B), // Zinc 950 + secondary: Color(0xFFE4E4E7), // Zinc 200 + onSecondary: Color(0xFF09090B), + surface: Color(0xFF09090B), // Zinc 950 + onSurface: Color(0xFFFAFAFA), // Zinc 50 + surfaceContainer: Color(0xFF18181B), // Zinc 900 + ), + scaffoldBackgroundColor: const Color(0xFF09090B), // Zinc 950 + cardTheme: CardThemeData( + color: const Color(0xFF18181B), // Zinc 900 + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + side: BorderSide(color: const Color(0xFF27272A), width: 1), // Zinc 800 + ), + ), + dividerTheme: const DividerThemeData( + color: Color(0xFF27272A), // Zinc 800 + thickness: 1, + ), + textTheme: GoogleFonts.interTextTheme(ThemeData.dark().textTheme).apply( + bodyColor: const Color(0xFFFAFAFA), + displayColor: const Color(0xFFFAFAFA), + ), + iconTheme: const IconThemeData( + color: Color(0xFFA1A1AA), // Zinc 400 + ), + ); +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..c5d8681 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,1159 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f + url: "https://pub.dev" + source: hosted + version: "85.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: f4ad0fea5f102201015c9aae9d93bc02f75dd9491529a8c21f88d17a8523d44c + url: "https://pub.dev" + source: hosted + version: "7.6.0" + analyzer_plugin: + dependency: transitive + description: + name: analyzer_plugin + sha256: a5ab7590c27b779f3d4de67f31c4109dbe13dd7339f86461a6f2a8ab2594d8ce + url: "https://pub.dev" + source: hosted + version: "0.13.4" + animate_do: + dependency: "direct main" + description: + name: animate_do + sha256: b6ff08dc6cf3cb5586a86d7f32a3b5f45502d2e08e3fb4f5a484c8421c9b3fc0 + url: "https://pub.dev" + source: hosted + version: "3.3.9" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + auto_route: + dependency: "direct main" + description: + name: auto_route + sha256: "1d1bd908a1fec327719326d5d0791edd37f16caff6493c01003689fb03315ad7" + url: "https://pub.dev" + source: hosted + version: "9.3.0+1" + auto_route_generator: + dependency: "direct dev" + description: + name: auto_route_generator + sha256: c2e359d8932986d4d1bcad7a428143f81384ce10fef8d4aa5bc29e1f83766a46 + url: "https://pub.dev" + source: hosted + version: "9.3.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + build: + dependency: transitive + description: + name: build + sha256: "51dc711996cbf609b90cbe5b335bbce83143875a9d58e4b5c6d3c4f684d3dda7" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + build_config: + dependency: transitive + description: + name: build_config + sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957 + url: "https://pub.dev" + source: hosted + version: "4.1.1" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: ee4257b3f20c0c90e72ed2b57ad637f694ccba48839a821e87db762548c22a62 + url: "https://pub.dev" + source: hosted + version: "2.5.4" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "382a4d649addbfb7ba71a3631df0ec6a45d5ab9b098638144faf27f02778eb53" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: "85fbbb1036d576d966332a3f5ce83f2ce66a40bea1a94ad2d5fc29a19a0d3792" + url: "https://pub.dev" + source: hosted + version: "9.1.2" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: "7931c90b84bc573fef103548e354258ae4c9d28d140e41961df6843c5d60d4d8" + url: "https://pub.dev" + source: hosted + version: "8.12.3" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + charcode: + dependency: transitive + description: + name: charcode + sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a + url: "https://pub.dev" + source: hosted + version: "1.4.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c + url: "https://pub.dev" + source: hosted + version: "0.4.2" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d" + url: "https://pub.dev" + source: hosted + version: "4.11.1" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + custom_lint_core: + dependency: transitive + description: + name: custom_lint_core + sha256: "31110af3dde9d29fb10828ca33f1dce24d2798477b167675543ce3d208dee8be" + url: "https://pub.dev" + source: hosted + version: "0.7.5" + custom_lint_visitor: + dependency: transitive + description: + name: custom_lint_visitor + sha256: "4a86a0d8415a91fbb8298d6ef03e9034dc8e323a599ddc4120a0e36c433983a2" + url: "https://pub.dev" + source: hosted + version: "1.0.0+7.7.0" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + drift: + dependency: "direct main" + description: + name: drift + sha256: "540cf382a3bfa99b76e51514db5b0ebcd81ce3679b7c1c9cb9478ff3735e47a1" + url: "https://pub.dev" + source: hosted + version: "2.28.2" + drift_dev: + dependency: "direct dev" + description: + name: drift_dev + sha256: "68c138e884527d2bd61df2ade276c3a144df84d1adeb0ab8f3196b5afe021bd4" + url: "https://pub.dev" + source: hosted + version: "2.28.0" + equatable: + dependency: transitive + description: + name: equatable + sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b" + url: "https://pub.dev" + source: hosted + version: "2.0.8" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + file_picker: + dependency: "direct main" + description: + name: file_picker + sha256: "1bbf65dd997458a08b531042ec3794112a6c39c07c37ff22113d2e7e4f81d4e4" + url: "https://pub.dev" + source: hosted + version: "6.2.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + fl_chart: + dependency: "direct main" + description: + name: fl_chart + sha256: "00b74ae680df6b1135bdbea00a7d1fc072a9180b7c3f3702e4b19a9943f5ed7d" + url: "https://pub.dev" + source: hosted + version: "0.66.2" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_animate: + dependency: transitive + description: + name: flutter_animate + sha256: "7befe2d3252728afb77aecaaea1dec88a89d35b9b1d2eea6d04479e8af9117b5" + url: "https://pub.dev" + source: hosted + version: "4.5.2" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + flutter_localizations: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_markdown: + dependency: "direct main" + description: + name: flutter_markdown + sha256: "04c4722cc36ec5af38acc38ece70d22d3c2123c61305d555750a091517bbe504" + url: "https://pub.dev" + source: hosted + version: "0.6.23" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1 + url: "https://pub.dev" + source: hosted + version: "2.0.33" + flutter_riverpod: + dependency: "direct main" + description: + name: flutter_riverpod + sha256: "9532ee6db4a943a1ed8383072a2e3eeda041db5657cdf6d2acecf3c21ecbe7e1" + url: "https://pub.dev" + source: hosted + version: "2.6.1" + flutter_shaders: + dependency: transitive + description: + name: flutter_shaders + sha256: "34794acadd8275d971e02df03afee3dee0f98dbfb8c4837082ad0034f612a3e2" + url: "https://pub.dev" + source: hosted + version: "0.1.3" + flutter_svg: + dependency: transitive + description: + name: flutter_svg + sha256: "87fbd7c534435b6c5d9d98b01e1fd527812b82e68ddd8bd35fc45ed0fa8f0a95" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + freezed: + dependency: "direct dev" + description: + name: freezed + sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c" + url: "https://pub.dev" + source: hosted + version: "2.5.8" + freezed_annotation: + dependency: "direct main" + description: + name: freezed_annotation + sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + url: "https://pub.dev" + source: hosted + version: "2.4.4" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + get_it: + dependency: "direct main" + description: + name: get_it + sha256: ae78de7c3f2304b8d81f2bb6e320833e5e81de942188542328f074978cc0efa9 + url: "https://pub.dev" + source: hosted + version: "8.3.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + go_router: + dependency: "direct main" + description: + name: go_router + sha256: c5fa45fa502ee880839e3b2152d987c44abae26d064a2376d4aad434cf0f7b15 + url: "https://pub.dev" + source: hosted + version: "12.1.3" + google_fonts: + dependency: "direct main" + description: + name: google_fonts + sha256: ba03d03bcaa2f6cb7bd920e3b5027181db75ab524f8891c8bc3aa603885b8055 + url: "https://pub.dev" + source: hosted + version: "6.3.3" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + hooks: + dependency: transitive + description: + name: hooks + sha256: "7a08a0d684cb3b8fb604b78455d5d352f502b68079f7b80b831c62220ab0a4f6" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + html: + dependency: transitive + description: + name: html + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" + url: "https://pub.dev" + source: hosted + version: "0.15.6" + http: + dependency: "direct main" + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + intl: + dependency: transitive + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" + js: + dependency: transitive + description: + name: js + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" + url: "https://pub.dev" + source: hosted + version: "0.7.2" + json_annotation: + dependency: "direct main" + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + json_serializable: + dependency: "direct dev" + description: + name: json_serializable + sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c + url: "https://pub.dev" + source: hosted + version: "6.9.5" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 + url: "https://pub.dev" + source: hosted + version: "3.0.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + lucide_icons_flutter: + dependency: transitive + description: + name: lucide_icons_flutter + sha256: d9ee025290a82f39f3e3b54238b87cab07f100cbc8014126df5d51813d428654 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + markdown: + dependency: transitive + description: + name: markdown + sha256: "935e23e1ff3bc02d390bad4d4be001208ee92cc217cb5b5a6c19bc14aaa318c1" + url: "https://pub.dev" + source: hosted + version: "7.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" + source: hosted + version: "1.17.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + native_toolchain_c: + dependency: transitive + description: + name: native_toolchain_c + sha256: "89e83885ba09da5fdf2cdacc8002a712ca238c28b7f717910b34bcd27b0d03ac" + url: "https://pub.dev" + source: hosted + version: "0.17.4" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52" + url: "https://pub.dev" + source: hosted + version: "9.3.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" + path: + dependency: "direct main" + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e + url: "https://pub.dev" + source: hosted + version: "2.2.22" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + url: "https://pub.dev" + source: hosted + version: "2.6.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" + url: "https://pub.dev" + source: hosted + version: "7.0.2" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.dev" + source: hosted + version: "1.5.2" + provider: + dependency: "direct main" + description: + name: provider + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" + url: "https://pub.dev" + source: hosted + version: "6.1.5+1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + recase: + dependency: transitive + description: + name: recase + sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213 + url: "https://pub.dev" + source: hosted + version: "4.1.0" + riverpod: + dependency: transitive + description: + name: riverpod + sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959" + url: "https://pub.dev" + source: hosted + version: "2.6.1" + riverpod_analyzer_utils: + dependency: transitive + description: + name: riverpod_analyzer_utils + sha256: "837a6dc33f490706c7f4632c516bcd10804ee4d9ccc8046124ca56388715fdf3" + url: "https://pub.dev" + source: hosted + version: "0.5.9" + riverpod_annotation: + dependency: "direct main" + description: + name: riverpod_annotation + sha256: e14b0bf45b71326654e2705d462f21b958f987087be850afd60578fcd502d1b8 + url: "https://pub.dev" + source: hosted + version: "2.6.1" + riverpod_generator: + dependency: "direct dev" + description: + name: riverpod_generator + sha256: "120d3310f687f43e7011bb213b90a436f1bbc300f0e4b251a72c39bccb017a4f" + url: "https://pub.dev" + source: hosted + version: "2.6.4" + screen_retriever: + dependency: transitive + description: + name: screen_retriever + sha256: "6ee02c8a1158e6dae7ca430da79436e3b1c9563c8cf02f524af997c201ac2b90" + url: "https://pub.dev" + source: hosted + version: "0.1.9" + shadcn_ui: + dependency: "direct main" + description: + name: shadcn_ui + sha256: "758cc810e0b1812ac430514fa9b88ffd42e2bd996276a87412784aa4daa2fca2" + url: "https://pub.dev" + source: hosted + version: "0.9.8" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + source_helper: + dependency: transitive + description: + name: source_helper + sha256: a447acb083d3a5ef17f983dd36201aeea33fedadb3228fa831f2f0c92f0f3aca + url: "https://pub.dev" + source: hosted + version: "1.3.7" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + sqlite3: + dependency: transitive + description: + name: sqlite3 + sha256: "3145bd74dcdb4fd6f5c6dda4d4e4490a8087d7f286a14dee5d37087290f0f8a2" + url: "https://pub.dev" + source: hosted + version: "2.9.4" + sqlite3_flutter_libs: + dependency: "direct main" + description: + name: sqlite3_flutter_libs + sha256: "1e800ebe7f85a80a66adacaa6febe4d5f4d8b75f244e9838a27cb2ffc7aec08d" + url: "https://pub.dev" + source: hosted + version: "0.5.41" + sqlparser: + dependency: transitive + description: + name: sqlparser + sha256: "57090342af1ce32bb499aa641f4ecdd2d6231b9403cea537ac059e803cc20d67" + url: "https://pub.dev" + source: hosted + version: "0.41.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + state_notifier: + dependency: transitive + description: + name: state_notifier + sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb + url: "https://pub.dev" + source: hosted + version: "1.0.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.dev" + source: hosted + version: "2.1.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + url: "https://pub.dev" + source: hosted + version: "0.7.7" + timing: + dependency: transitive + description: + name: timing + sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + two_dimensional_scrollables: + dependency: transitive + description: + name: two_dimensional_scrollables + sha256: e9397ae372839aecb3135d246bff5cce5e738604c9afd03d65d06c7a246ae958 + url: "https://pub.dev" + source: hosted + version: "0.3.8" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + uuid: + dependency: "direct main" + description: + name: uuid + sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8 + url: "https://pub.dev" + source: hosted + version: "4.5.2" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6 + url: "https://pub.dev" + source: hosted + version: "1.1.19" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" + url: "https://pub.dev" + source: hosted + version: "1.1.13" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: "201e876b5d52753626af64b6359cd13ac6011b80728731428fd34bc840f71c9b" + url: "https://pub.dev" + source: hosted + version: "1.1.20" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + video_player: + dependency: "direct main" + description: + name: video_player + sha256: "08bfba72e311d48219acad4e191b1f9c27ff8cf928f2c7234874592d9c9d7341" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + video_player_android: + dependency: transitive + description: + name: video_player_android + sha256: e726b33894526cf96a3eefe61af054b0c3e7d254443b3695b3c142dc277291be + url: "https://pub.dev" + source: hosted + version: "2.9.3" + video_player_avfoundation: + dependency: transitive + description: + name: video_player_avfoundation + sha256: f93b93a3baa12ca0ff7d00ca8bc60c1ecd96865568a01ff0c18a99853ee201a5 + url: "https://pub.dev" + source: hosted + version: "2.9.3" + video_player_platform_interface: + dependency: transitive + description: + name: video_player_platform_interface + sha256: "57c5d73173f76d801129d0531c2774052c5a7c11ccb962f1830630decd9f24ec" + url: "https://pub.dev" + source: hosted + version: "6.6.0" + video_player_web: + dependency: transitive + description: + name: video_player_web + sha256: "9f3c00be2ef9b76a95d94ac5119fb843dca6f2c69e6c9968f6f2b6c9e7afbdeb" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + video_player_win: + dependency: "direct main" + description: + name: video_player_win + sha256: a4caca55ead1eb469d10060592e7ecdcbcd4493c6e2b63e4e666ff5446c790f1 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" + url: "https://pub.dev" + source: hosted + version: "15.0.2" + watcher: + dependency: transitive + description: + name: watcher + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + win32: + dependency: transitive + description: + name: win32 + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.dev" + source: hosted + version: "5.15.0" + window_manager: + dependency: "direct main" + description: + name: window_manager + sha256: "8699323b30da4cdbe2aa2e7c9de567a6abd8a97d9a5c850a3c86dcd0b34bbfbf" + url: "https://pub.dev" + source: hosted + version: "0.3.9" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + url: "https://pub.dev" + source: hosted + version: "6.6.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" +sdks: + dart: ">=3.10.7 <4.0.0" + flutter: ">=3.38.4" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..0fd3b94 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,68 @@ +name: trainhub_flutter +description: "TrainHub - Training Program Management" +publish_to: 'none' +version: 2.0.0+1 + +environment: + sdk: ^3.10.7 + +dependencies: + flutter: + sdk: flutter + + # Database + drift: ^2.14.0 + sqlite3_flutter_libs: ^0.5.18 + path_provider: ^2.1.2 + path: ^1.8.3 + + # State Management + flutter_riverpod: ^2.6.1 + riverpod_annotation: ^2.6.1 + + # Dependency Injection + get_it: ^8.0.2 + + # Routing + auto_route: ^9.2.2 + + # Code Generation + freezed_annotation: ^2.4.4 + json_annotation: ^4.9.0 + + # UI + shadcn_ui: ^0.9.3 + google_fonts: ^6.1.0 + fl_chart: ^0.66.0 + animate_do: ^3.3.4 + + # Video + video_player: ^2.8.2 + video_player_win: ^3.2.2 + + # Markdown + flutter_markdown: ^0.6.18+3 + + # Utilities + file_picker: ^6.1.1 + http: ^1.2.0 + window_manager: ^0.3.9 + uuid: ^4.5.1 + + # Keep temporarily during migration + provider: ^6.1.1 + go_router: ^12.1.0 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^3.0.0 + build_runner: ^2.4.8 + drift_dev: ^2.14.0 + freezed: ^2.5.7 + json_serializable: ^6.8.0 + riverpod_generator: ^2.6.2 + auto_route_generator: ^9.0.0 + +flutter: + uses-material-design: true diff --git a/test/widget_test.dart b/test/widget_test.dart new file mode 100644 index 0000000..c162739 --- /dev/null +++ b/test/widget_test.dart @@ -0,0 +1,11 @@ +import 'package:flutter_test/flutter_test.dart'; +// import 'package:trainhub_flutter/main.dart'; + +void main() { + testWidgets('Smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + // await tester.pumpWidget(const TrainHubApp()); + + expect(true, true); + }); +} diff --git a/windows/.gitignore b/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt new file mode 100644 index 0000000..948227d --- /dev/null +++ b/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(trainhub_flutter 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 "trainhub_flutter") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + 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() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# 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_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +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) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +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. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/windows/flutter/CMakeLists.txt b/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..903f489 --- /dev/null +++ b/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +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. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# 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/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app 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. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..99d0961 --- /dev/null +++ b/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,23 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include +#include +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + ScreenRetrieverPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("ScreenRetrieverPlugin")); + Sqlite3FlutterLibsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin")); + VideoPlayerWinPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("VideoPlayerWinPluginCApi")); + WindowManagerPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("WindowManagerPlugin")); +} diff --git a/windows/flutter/generated_plugin_registrant.h b/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..bfb35f9 --- /dev/null +++ b/windows/flutter/generated_plugins.cmake @@ -0,0 +1,27 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + screen_retriever + sqlite3_flutter_libs + video_player_win + window_manager +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + 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}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/windows/runner/CMakeLists.txt b/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +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} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# 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 build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc new file mode 100644 index 0000000..ee01702 --- /dev/null +++ b/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "trainhub_flutter" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "trainhub_flutter" "\0" + VALUE "LegalCopyright", "Copyright (C) 2026 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "trainhub_flutter.exe" "\0" + VALUE "ProductName", "trainhub_flutter" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..955ee30 --- /dev/null +++ b/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/windows/runner/flutter_window.h b/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/windows/runner/main.cpp b/windows/runner/main.cpp new file mode 100644 index 0000000..d174112 --- /dev/null +++ b/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"trainhub_flutter", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/windows/runner/resource.h b/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000..c04e20c Binary files /dev/null and b/windows/runner/resources/app_icon.ico differ diff --git a/windows/runner/runner.exe.manifest b/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..153653e --- /dev/null +++ b/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/windows/runner/utils.cpp b/windows/runner/utils.cpp new file mode 100644 index 0000000..3a0b465 --- /dev/null +++ b/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + unsigned int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/windows/runner/utils.h b/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/windows/runner/win32_window.cpp b/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/windows/runner/win32_window.h b/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_