Merge NDM-App-231-check-all-task-and-verify-if-is-everything-in-app into dev
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
# App Audit — Fixes & Test Coverage
|
||||
|
||||
**Branch**: `NDM-App-231-check-all-task-and-verify-if-is-everything-in-app`
|
||||
**Commits**: `ed4d988`, `c425e99`
|
||||
|
||||
## What Was Done
|
||||
|
||||
Full audit of the KittieMobile Flutter app. Small issues fixed in-place; larger missing features queued as AITM tasks.
|
||||
|
||||
---
|
||||
|
||||
## Direct Fixes Applied
|
||||
|
||||
### 1. Router deduplication
|
||||
**File**: `lib/core/router/routes.dart`, `lib/core/router/app_router.dart`
|
||||
|
||||
- Merged duplicate `Routes` class — it existed in both files. Canonical location is now `routes.dart` only.
|
||||
- Removed unused `Routes.home = '/'` constant (`initialLocation` already uses `Routes.tasks`).
|
||||
- Deleted orphaned generated files: `app_router.g.dart`, `pet_providers.g.dart`, `task_providers.g.dart` — they were generated by a mismatched Riverpod version and caused compile errors.
|
||||
|
||||
### 2. PetType canonical import
|
||||
**Files**: `lib/features/onboarding/providers/onboarding_providers.dart`, `lib/features/onboarding/screens/onboarding_screen.dart`
|
||||
|
||||
- Removed local re-declaration of `PetType` enum in onboarding providers.
|
||||
- Added import from canonical location: `lib/domain/entities/pet_type.dart`.
|
||||
|
||||
### 3. FAB navigation fix
|
||||
**File**: `lib/features/tasks/screens/tasks_screen.dart`
|
||||
|
||||
- FAB was calling `context.push(Routes.addTask)` — the route does not exist as a named route.
|
||||
- Changed to call `showAddTaskSheet(context)` directly (the bottom sheet function).
|
||||
|
||||
### 4. Pet mood display fix
|
||||
**File**: `lib/features/tasks/screens/tasks_screen.dart`
|
||||
|
||||
- `PetBubble` `mood` parameter was receiving `petMood.name` (raw enum string, e.g. `"happy"`).
|
||||
- Changed to `petMood.emoji` — now shows the actual emoji character.
|
||||
- `_petMessage` function signature updated to accept `PetMood` enum directly (exhaustive switch, no silent fall-through on future renames).
|
||||
|
||||
### 5. Czech diacritics
|
||||
**Files**: `tasks_screen`, `energy_badge`, `task_card`, `app_shell`, `pet_screen`, `profile_screen`, `zen_screen`, `lib/domain/entities/pet_type.dart`, `lib/shared/utils/czech_date.dart`
|
||||
|
||||
- Multiple UI strings had broken diacritics (e.g. `"Přidat úkol"` was corrupted).
|
||||
- All fixed in-place. `flutter analyze` confirmed no issues after fix.
|
||||
|
||||
### 6. Entity alignment with DB schema
|
||||
**Files**: `lib/domain/entities/task_entity.dart`, `lib/domain/entities/user_entity.dart`
|
||||
|
||||
- `TaskEntity.id` and `UserEntity.id` changed from `int` to `String` to match Drift UUID schema.
|
||||
- `TaskEntity` gained: `photoPath`, `snoozeUntil`, `syncedAt`, `localId`.
|
||||
- `UserEntity` gained: `deviceId`, `createdAt`, `updatedAt`.
|
||||
- `TasksNotifier` methods updated to accept `String` ids.
|
||||
|
||||
---
|
||||
|
||||
## Test Coverage Added
|
||||
|
||||
7 new test files, 78 new tests — all passing (162 total with existing).
|
||||
|
||||
| File | Tests | Coverage |
|
||||
|------|-------|----------|
|
||||
| `test/domain/entities/task_entity_test.dart` | 11 | defaults, `isCompleted`, `copyWith` |
|
||||
| `test/domain/entities/user_entity_test.dart` | 7 | defaults, `copyWith` |
|
||||
| `test/domain/entities/pet_state_entity_test.dart` | 12 | defaults, `copyWith` with sentinel pattern |
|
||||
| `test/domain/entities/task_status_test.dart` | 7 | `fromString` all 5 values + throws on unknown |
|
||||
| `test/domain/entities/pet_type_test.dart` | 11 | `fromString` all 3 values + unknown→cat fallback, `.emoji`, `.label` |
|
||||
| `test/shared/utils/czech_date_test.dart` | 8 | `czechDateHeader` known dates, all weekdays, all months |
|
||||
| `test/shared/utils/kittie_date_utils_test.dart` | 22 | `isToday`, `isTomorrow`, `isOverdue`, `formatRelativeDate`, `getWeekdayLabel`, `getStreakDays` |
|
||||
|
||||
---
|
||||
|
||||
## Audit Checklist
|
||||
|
||||
| Feature | Status |
|
||||
|---------|--------|
|
||||
| Onboarding (3-step wizard) | Done |
|
||||
| Task list + pet bubble + energy bar | Done |
|
||||
| Add task bottom sheet (4-step) | Done |
|
||||
| Pet screen | Done |
|
||||
| Profile screen | Done |
|
||||
| Zen mode | Done |
|
||||
| Czech diacritics | Done (fixed in this task) |
|
||||
| Router / Routes class | Done (fixed in this task) |
|
||||
| Task persistence → Drift DB | **Missing** → AITM `mmwesrifqrzt` |
|
||||
| User/onboarding → DB | **Missing** → AITM `mmwet4su7xai` |
|
||||
| Daily reset energy check-in | **Missing** → AITM `mmwetkuvry2f` |
|
||||
| Task detail/edit + swipe delete | **Missing** → AITM `mmwetzyqw7lm` |
|
||||
| L10n ARB CZ+EN | **Missing** → AITM `mmweufu125yi` |
|
||||
|
||||
---
|
||||
|
||||
## AITM Tasks Created
|
||||
|
||||
| AITM ID | Title | Priority |
|
||||
|---------|-------|----------|
|
||||
| `mmwesrifqrzt` | Connect AddTask to Drift DB — remove sample tasks | high |
|
||||
| `mmwet4su7xai` | User/onboarding persistence — save to DB, load on startup | high |
|
||||
| `mmwetkuvry2f` | Daily reset screen — morning energy check-in | medium |
|
||||
| `mmwetzyqw7lm` | Task detail/edit screen + swipe-to-delete | medium |
|
||||
| `mmweufu125yi` | L10n ARB-based CZ/EN locale switching | low |
|
||||
@@ -6,14 +6,7 @@ import '../../features/tasks/screens/pet_screen.dart';
|
||||
import '../../features/zen_mode/screens/zen_screen.dart';
|
||||
import '../../shared/widgets/profile_screen.dart';
|
||||
import '../../shared/widgets/app_shell.dart';
|
||||
|
||||
/// Route name constants.
|
||||
abstract final class Routes {
|
||||
static const String tasks = '/tasks';
|
||||
static const String pet = '/pet';
|
||||
static const String zen = '/zen';
|
||||
static const String profile = '/profile';
|
||||
}
|
||||
import 'routes.dart';
|
||||
|
||||
final appRouterProvider = Provider<GoRouter>((ref) {
|
||||
return GoRouter(
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'app_router.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(appRouter)
|
||||
final appRouterProvider = AppRouterProvider._();
|
||||
|
||||
final class AppRouterProvider
|
||||
extends $FunctionalProvider<GoRouter, GoRouter, GoRouter>
|
||||
with $Provider<GoRouter> {
|
||||
AppRouterProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'appRouterProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$appRouterHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$ProviderElement<GoRouter> $createElement($ProviderPointer pointer) =>
|
||||
$ProviderElement(pointer);
|
||||
|
||||
@override
|
||||
GoRouter create(Ref ref) {
|
||||
return appRouter(ref);
|
||||
}
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(GoRouter value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<GoRouter>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$appRouterHash() => r'992c1f48b4899faf26807c2d6009807a91a03f5c';
|
||||
@@ -1,5 +1,7 @@
|
||||
/// Route name constants for go_router navigation.
|
||||
abstract final class Routes {
|
||||
static const home = '/';
|
||||
static const addTask = '/add-task';
|
||||
static const tasks = '/tasks';
|
||||
static const pet = '/pet';
|
||||
static const zen = '/zen';
|
||||
static const profile = '/profile';
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ enum PetType {
|
||||
/// Czech label for each pet type.
|
||||
String get label => switch (this) {
|
||||
PetType.dog => 'Pejsek',
|
||||
PetType.cat => 'Kocicka',
|
||||
PetType.cat => 'Kočička',
|
||||
PetType.capybara => 'Kapybara',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,17 +2,21 @@ import 'task_status.dart';
|
||||
|
||||
/// Pure domain entity for a task.
|
||||
class TaskEntity {
|
||||
final int id;
|
||||
final int userId;
|
||||
final String id;
|
||||
final String userId;
|
||||
final String title;
|
||||
final String? description;
|
||||
final int energyLevel;
|
||||
final TaskStatus status;
|
||||
final bool isSelfCare;
|
||||
final DateTime? deadline;
|
||||
final String? photoPath;
|
||||
final DateTime? snoozeUntil;
|
||||
final int sortOrder;
|
||||
final DateTime createdAt;
|
||||
final DateTime updatedAt;
|
||||
final DateTime? syncedAt;
|
||||
final String? localId;
|
||||
|
||||
const TaskEntity({
|
||||
required this.id,
|
||||
@@ -23,25 +27,33 @@ class TaskEntity {
|
||||
this.status = TaskStatus.pending,
|
||||
this.isSelfCare = false,
|
||||
this.deadline,
|
||||
this.photoPath,
|
||||
this.snoozeUntil,
|
||||
this.sortOrder = 0,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
this.syncedAt,
|
||||
this.localId,
|
||||
});
|
||||
|
||||
bool get isCompleted => status == TaskStatus.done;
|
||||
|
||||
TaskEntity copyWith({
|
||||
int? id,
|
||||
int? userId,
|
||||
String? id,
|
||||
String? userId,
|
||||
String? title,
|
||||
String? description,
|
||||
int? energyLevel,
|
||||
TaskStatus? status,
|
||||
bool? isSelfCare,
|
||||
DateTime? deadline,
|
||||
String? photoPath,
|
||||
DateTime? snoozeUntil,
|
||||
int? sortOrder,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
DateTime? syncedAt,
|
||||
String? localId,
|
||||
}) {
|
||||
return TaskEntity(
|
||||
id: id ?? this.id,
|
||||
@@ -52,9 +64,13 @@ class TaskEntity {
|
||||
status: status ?? this.status,
|
||||
isSelfCare: isSelfCare ?? this.isSelfCare,
|
||||
deadline: deadline ?? this.deadline,
|
||||
photoPath: photoPath ?? this.photoPath,
|
||||
snoozeUntil: snoozeUntil ?? this.snoozeUntil,
|
||||
sortOrder: sortOrder ?? this.sortOrder,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
syncedAt: syncedAt ?? this.syncedAt,
|
||||
localId: localId ?? this.localId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,29 +2,41 @@ import 'pet_type.dart';
|
||||
|
||||
/// Pure domain entity for a user.
|
||||
class UserEntity {
|
||||
final int id;
|
||||
final String id;
|
||||
final String deviceId;
|
||||
final String name;
|
||||
final PetType petType;
|
||||
final String petName;
|
||||
final DateTime createdAt;
|
||||
final DateTime updatedAt;
|
||||
|
||||
const UserEntity({
|
||||
required this.id,
|
||||
this.deviceId = '',
|
||||
this.name = 'Friend',
|
||||
this.petType = PetType.cat,
|
||||
this.petName = 'Yoshi',
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
});
|
||||
|
||||
UserEntity copyWith({
|
||||
int? id,
|
||||
String? id,
|
||||
String? deviceId,
|
||||
String? name,
|
||||
PetType? petType,
|
||||
String? petName,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
}) {
|
||||
return UserEntity(
|
||||
id: id ?? this.id,
|
||||
deviceId: deviceId ?? this.deviceId,
|
||||
name: name ?? this.name,
|
||||
petType: petType ?? this.petType,
|
||||
petName: petName ?? this.petName,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'onboarding_providers.g.dart';
|
||||
import '../../../domain/entities/pet_type.dart';
|
||||
|
||||
/// Pet types available during onboarding.
|
||||
enum PetType { dog, cat, capybara }
|
||||
part 'onboarding_providers.g.dart';
|
||||
|
||||
/// Current onboarding step (0-based).
|
||||
@riverpod
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../../../core/theme/kittie_colors.dart';
|
||||
import '../../../core/theme/kittie_typography.dart';
|
||||
import '../../../domain/entities/pet_type.dart';
|
||||
import '../providers/onboarding_providers.dart';
|
||||
|
||||
class OnboardingScreen extends ConsumerStatefulWidget {
|
||||
|
||||
@@ -21,11 +21,13 @@ enum PetMood {
|
||||
|
||||
class UserNotifier extends Notifier<UserEntity> {
|
||||
@override
|
||||
UserEntity build() => const UserEntity(
|
||||
id: 1,
|
||||
UserEntity build() => UserEntity(
|
||||
id: '1',
|
||||
name: 'Friend',
|
||||
petType: PetType.cat,
|
||||
petName: 'Yoshi',
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
void updateName(String name) => state = state.copyWith(name: name);
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'pet_providers.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
/// Manages the pet state (energy, streak, total tasks done).
|
||||
|
||||
@ProviderFor(PetState)
|
||||
final petStateProvider = PetStateProvider._();
|
||||
|
||||
/// Manages the pet state (energy, streak, total tasks done).
|
||||
final class PetStateProvider
|
||||
extends $AsyncNotifierProvider<PetState, Map<String, dynamic>> {
|
||||
/// Manages the pet state (energy, streak, total tasks done).
|
||||
PetStateProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'petStateProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$petStateHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
PetState create() => PetState();
|
||||
}
|
||||
|
||||
String _$petStateHash() => r'4ae2b5760d1c5f54099481b5b1729145dd0775bc';
|
||||
|
||||
/// Manages the pet state (energy, streak, total tasks done).
|
||||
|
||||
abstract class _$PetState extends $AsyncNotifier<Map<String, dynamic>> {
|
||||
FutureOr<Map<String, dynamic>> build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final ref = this.ref
|
||||
as $Ref<AsyncValue<Map<String, dynamic>>, Map<String, dynamic>>;
|
||||
final element = ref.element as $ClassProviderElement<
|
||||
AnyNotifier<AsyncValue<Map<String, dynamic>>, Map<String, dynamic>>,
|
||||
AsyncValue<Map<String, dynamic>>,
|
||||
Object?,
|
||||
Object?>;
|
||||
element.handleCreate(ref, build);
|
||||
}
|
||||
}
|
||||
|
||||
/// Derived provider for the pet's current mood.
|
||||
|
||||
@ProviderFor(petMood)
|
||||
final petMoodProvider = PetMoodProvider._();
|
||||
|
||||
/// Derived provider for the pet's current mood.
|
||||
|
||||
final class PetMoodProvider
|
||||
extends $FunctionalProvider<AsyncValue<PetMood>, PetMood, FutureOr<PetMood>>
|
||||
with $FutureModifier<PetMood>, $FutureProvider<PetMood> {
|
||||
/// Derived provider for the pet's current mood.
|
||||
PetMoodProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'petMoodProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$petMoodHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$FutureProviderElement<PetMood> $createElement($ProviderPointer pointer) =>
|
||||
$FutureProviderElement(pointer);
|
||||
|
||||
@override
|
||||
FutureOr<PetMood> create(Ref ref) {
|
||||
return petMood(ref);
|
||||
}
|
||||
}
|
||||
|
||||
String _$petMoodHash() => r'f608d5d30377353de259575fc987bae85cbd5ecf';
|
||||
@@ -8,7 +8,7 @@ class TasksNotifier extends Notifier<List<TaskEntity>> {
|
||||
@override
|
||||
List<TaskEntity> build() => _sampleTasks;
|
||||
|
||||
void completeTask(int id) {
|
||||
void completeTask(String id) {
|
||||
state = [
|
||||
for (final task in state)
|
||||
if (task.id == id)
|
||||
@@ -21,7 +21,7 @@ class TasksNotifier extends Notifier<List<TaskEntity>> {
|
||||
];
|
||||
}
|
||||
|
||||
void uncompleteTask(int id) {
|
||||
void uncompleteTask(String id) {
|
||||
state = [
|
||||
for (final task in state)
|
||||
if (task.id == id)
|
||||
@@ -34,7 +34,7 @@ class TasksNotifier extends Notifier<List<TaskEntity>> {
|
||||
];
|
||||
}
|
||||
|
||||
void skipTask(int id) {
|
||||
void skipTask(String id) {
|
||||
final skipped = state.firstWhere((t) => t.id == id);
|
||||
state = [
|
||||
...state.where((t) => t.id != id),
|
||||
@@ -83,8 +83,8 @@ final _now = DateTime.now();
|
||||
|
||||
final _sampleTasks = <TaskEntity>[
|
||||
TaskEntity(
|
||||
id: 1,
|
||||
userId: 1,
|
||||
id: '1',
|
||||
userId: '1',
|
||||
title: 'Nakoupit potraviny',
|
||||
energyLevel: 1,
|
||||
sortOrder: 0,
|
||||
@@ -92,18 +92,18 @@ final _sampleTasks = <TaskEntity>[
|
||||
updatedAt: _now,
|
||||
),
|
||||
TaskEntity(
|
||||
id: 2,
|
||||
userId: 1,
|
||||
title: 'Udelat prezentaci',
|
||||
id: '2',
|
||||
userId: '1',
|
||||
title: 'Udělat prezentaci',
|
||||
energyLevel: 3,
|
||||
sortOrder: 1,
|
||||
createdAt: _now,
|
||||
updatedAt: _now,
|
||||
),
|
||||
TaskEntity(
|
||||
id: 3,
|
||||
userId: 1,
|
||||
title: 'Protahnout se',
|
||||
id: '3',
|
||||
userId: '1',
|
||||
title: 'Protáhnout se',
|
||||
energyLevel: 1,
|
||||
isSelfCare: true,
|
||||
sortOrder: 2,
|
||||
@@ -111,8 +111,8 @@ final _sampleTasks = <TaskEntity>[
|
||||
updatedAt: _now,
|
||||
),
|
||||
TaskEntity(
|
||||
id: 4,
|
||||
userId: 1,
|
||||
id: '4',
|
||||
userId: '1',
|
||||
title: 'Napsat email klientovi',
|
||||
energyLevel: 2,
|
||||
sortOrder: 3,
|
||||
@@ -120,8 +120,8 @@ final _sampleTasks = <TaskEntity>[
|
||||
updatedAt: _now,
|
||||
),
|
||||
TaskEntity(
|
||||
id: 5,
|
||||
userId: 1,
|
||||
id: '5',
|
||||
userId: '1',
|
||||
title: 'Meditace',
|
||||
energyLevel: 1,
|
||||
isSelfCare: true,
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'task_providers.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
/// Provides all tasks as a list.
|
||||
|
||||
@ProviderFor(Tasks)
|
||||
final tasksProvider = TasksProvider._();
|
||||
|
||||
/// Provides all tasks as a list.
|
||||
final class TasksProvider extends $AsyncNotifierProvider<Tasks, List<String>> {
|
||||
/// Provides all tasks as a list.
|
||||
TasksProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'tasksProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$tasksHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
Tasks create() => Tasks();
|
||||
}
|
||||
|
||||
String _$tasksHash() => r'0bc19ff9f5dc2f434086c53b504a68b003b8f4c1';
|
||||
|
||||
/// Provides all tasks as a list.
|
||||
|
||||
abstract class _$Tasks extends $AsyncNotifier<List<String>> {
|
||||
FutureOr<List<String>> build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final ref = this.ref as $Ref<AsyncValue<List<String>>, List<String>>;
|
||||
final element = ref.element as $ClassProviderElement<
|
||||
AnyNotifier<AsyncValue<List<String>>, List<String>>,
|
||||
AsyncValue<List<String>>,
|
||||
Object?,
|
||||
Object?>;
|
||||
element.handleCreate(ref, build);
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides today's tasks filtered by date.
|
||||
|
||||
@ProviderFor(todayTasks)
|
||||
final todayTasksProvider = TodayTasksProvider._();
|
||||
|
||||
/// Provides today's tasks filtered by date.
|
||||
|
||||
final class TodayTasksProvider extends $FunctionalProvider<
|
||||
AsyncValue<List<String>>, List<String>, FutureOr<List<String>>>
|
||||
with $FutureModifier<List<String>>, $FutureProvider<List<String>> {
|
||||
/// Provides today's tasks filtered by date.
|
||||
TodayTasksProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'todayTasksProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$todayTasksHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$FutureProviderElement<List<String>> $createElement(
|
||||
$ProviderPointer pointer) =>
|
||||
$FutureProviderElement(pointer);
|
||||
|
||||
@override
|
||||
FutureOr<List<String>> create(Ref ref) {
|
||||
return todayTasks(ref);
|
||||
}
|
||||
}
|
||||
|
||||
String _$todayTasksHash() => r'2f1951cfe4e0862205bc07dd583797db1db79027';
|
||||
|
||||
/// Provides a single task by ID.
|
||||
|
||||
@ProviderFor(taskById)
|
||||
final taskByIdProvider = TaskByIdFamily._();
|
||||
|
||||
/// Provides a single task by ID.
|
||||
|
||||
final class TaskByIdProvider
|
||||
extends $FunctionalProvider<AsyncValue<String?>, String?, FutureOr<String?>>
|
||||
with $FutureModifier<String?>, $FutureProvider<String?> {
|
||||
/// Provides a single task by ID.
|
||||
TaskByIdProvider._(
|
||||
{required TaskByIdFamily super.from, required String super.argument})
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'taskByIdProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$taskByIdHash();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return r'taskByIdProvider'
|
||||
''
|
||||
'($argument)';
|
||||
}
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$FutureProviderElement<String?> $createElement($ProviderPointer pointer) =>
|
||||
$FutureProviderElement(pointer);
|
||||
|
||||
@override
|
||||
FutureOr<String?> create(Ref ref) {
|
||||
final argument = this.argument as String;
|
||||
return taskById(
|
||||
ref,
|
||||
argument,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is TaskByIdProvider && other.argument == argument;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return argument.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
String _$taskByIdHash() => r'43c2bcb96afd2d7a22711c21d182b8e3a17a0b5c';
|
||||
|
||||
/// Provides a single task by ID.
|
||||
|
||||
final class TaskByIdFamily extends $Family
|
||||
with $FunctionalFamilyOverride<FutureOr<String?>, String> {
|
||||
TaskByIdFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'taskByIdProvider',
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
isAutoDispose: true,
|
||||
);
|
||||
|
||||
/// Provides a single task by ID.
|
||||
|
||||
TaskByIdProvider call(
|
||||
String id,
|
||||
) =>
|
||||
TaskByIdProvider._(argument: id, from: this);
|
||||
|
||||
@override
|
||||
String toString() => r'taskByIdProvider';
|
||||
}
|
||||
@@ -35,7 +35,7 @@ class PetScreen extends ConsumerWidget {
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Tvuj spolecnik — ${user.petType.label}',
|
||||
'Tvůj společník — ${user.petType.label}',
|
||||
style: KittieTypography.bodyMedium.copyWith(
|
||||
color: KittieColors.brownLight,
|
||||
),
|
||||
@@ -107,19 +107,19 @@ class PetScreen extends ConsumerWidget {
|
||||
),
|
||||
_StatTile(
|
||||
icon: Icons.check_circle_rounded,
|
||||
label: 'Hotove ukoly',
|
||||
label: 'Hotové úkoly',
|
||||
value: '${petState.totalTasksDone}',
|
||||
color: KittieColors.sageGreen,
|
||||
),
|
||||
_StatTile(
|
||||
icon: Icons.favorite_rounded,
|
||||
label: 'Pece o sebe',
|
||||
label: 'Péče o sebe',
|
||||
value: '$selfCareCount',
|
||||
color: KittieColors.purple,
|
||||
),
|
||||
_StatTile(
|
||||
icon: Icons.star_rounded,
|
||||
label: 'Uroven',
|
||||
label: 'Úroveň',
|
||||
value: '$level',
|
||||
color: KittieColors.amber,
|
||||
),
|
||||
@@ -130,7 +130,7 @@ class PetScreen extends ConsumerWidget {
|
||||
// Unlocked items header
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text('Odemcene predmety',
|
||||
child: Text('Odemčené předměty',
|
||||
style: KittieTypography.h3),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
@@ -142,12 +142,12 @@ class PetScreen extends ConsumerWidget {
|
||||
scrollDirection: Axis.horizontal,
|
||||
children: [
|
||||
_ItemCard(
|
||||
name: 'Masle',
|
||||
name: 'Mašle',
|
||||
icon: Icons.catching_pokemon_rounded,
|
||||
unlocked: unlocked.contains('masle'),
|
||||
),
|
||||
_ItemCard(
|
||||
name: 'Pelisek',
|
||||
name: 'Pelíšek',
|
||||
icon: Icons.bed_rounded,
|
||||
unlocked: unlocked.contains('pelisek'),
|
||||
),
|
||||
@@ -157,7 +157,7 @@ class PetScreen extends ConsumerWidget {
|
||||
unlocked: unlocked.contains('koruna'),
|
||||
),
|
||||
_ItemCard(
|
||||
name: 'Kridla',
|
||||
name: 'Křídla',
|
||||
icon: Icons.flight_rounded,
|
||||
unlocked: unlocked.contains('kridla'),
|
||||
),
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
import '../../../core/router/routes.dart';
|
||||
import '../../../core/theme/kittie_colors.dart';
|
||||
import '../screens/add_task_screen.dart';
|
||||
import '../../../domain/entities/pet_type.dart';
|
||||
import '../../../shared/utils/czech_date.dart';
|
||||
import '../../../shared/widgets/energy_bar.dart';
|
||||
@@ -72,8 +71,8 @@ class TasksScreen extends ConsumerWidget {
|
||||
PetBubble(
|
||||
petType: userState.petType,
|
||||
petName: userState.petName,
|
||||
mood: petMood.name,
|
||||
message: _petMessage(petMood.name, userState.petName),
|
||||
mood: petMood.emoji,
|
||||
message: _petMessage(petMood, userState.petName),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
@@ -85,7 +84,7 @@ class TasksScreen extends ConsumerWidget {
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'DNESNI UKOLY',
|
||||
'DNEŠNÍ ÚKOLY',
|
||||
style: GoogleFonts.lexend(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -149,18 +148,17 @@ class TasksScreen extends ConsumerWidget {
|
||||
|
||||
// 8. FAB
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () => context.push(Routes.addTask),
|
||||
onPressed: () => showAddTaskSheet(context),
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static String _petMessage(String mood, String petName) {
|
||||
static String _petMessage(PetMood mood, String petName) {
|
||||
return switch (mood) {
|
||||
'happy' => '$petName je stastna! Jde ti to skvele \u{1F44F}',
|
||||
'neutral' => '$petName te ceka! Co dnes zvladneme? \u{2728}',
|
||||
'sad' => '$petName te podporuje. Zkus jeden ukol \u{1F495}',
|
||||
_ => '$petName je tady pro tebe \u{1F60A}',
|
||||
PetMood.happy => '$petName je šťastná! Jde ti to skvěle \u{1F44F}',
|
||||
PetMood.neutral => '$petName tě čeká! Co dnes zvládneme? \u{2728}',
|
||||
PetMood.sad => '$petName tě podporuje. Zkus jeden úkol \u{1F495}',
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -181,7 +179,7 @@ class _EmptyState extends StatelessWidget {
|
||||
Text(emoji, style: const TextStyle(fontSize: 64)),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Zadne ukoly na dnes!',
|
||||
'Žádné úkoly na dnes!',
|
||||
style: GoogleFonts.lexend(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -190,7 +188,7 @@ class _EmptyState extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Uzij si volny den \u{2615}',
|
||||
'Užij si volný den \u{2615}',
|
||||
style: GoogleFonts.lexend(
|
||||
fontSize: 14,
|
||||
color: KittieColors.brownLight,
|
||||
|
||||
@@ -88,7 +88,7 @@ class ZenScreen extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Text('Preskocit',
|
||||
child: Text('Přeskočit',
|
||||
style: KittieTypography.labelLarge
|
||||
.copyWith(color: KittieColors.brownLight)),
|
||||
),
|
||||
@@ -109,7 +109,7 @@ class ZenScreen extends ConsumerWidget {
|
||||
const Text('\u{2728}', style: TextStyle(fontSize: 48)),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Vse hotovo.\nOdpocivej.',
|
||||
'Vše hotovo.\nOdpočívej.',
|
||||
textAlign: TextAlign.center,
|
||||
style: KittieTypography.h2.copyWith(color: KittieColors.brown),
|
||||
),
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
const _czechDays = [
|
||||
'PONDELI',
|
||||
'UTERY',
|
||||
'STREDA',
|
||||
'CTVRTEK',
|
||||
'PATEK',
|
||||
'PONDĚLÍ',
|
||||
'ÚTERÝ',
|
||||
'STŘEDA',
|
||||
'ČTVRTEK',
|
||||
'PÁTEK',
|
||||
'SOBOTA',
|
||||
'NEDELE',
|
||||
'NEDĚLE',
|
||||
];
|
||||
|
||||
const _czechMonthsGenitive = [
|
||||
'LEDNA',
|
||||
'UNORA',
|
||||
'BREZNA',
|
||||
'ÚNORA',
|
||||
'BŘEZNA',
|
||||
'DUBNA',
|
||||
'KVETNA',
|
||||
'CERVNA',
|
||||
'CERVENCE',
|
||||
'KVĚTNA',
|
||||
'ČERVNA',
|
||||
'ČERVENCE',
|
||||
'SRPNA',
|
||||
'ZARI',
|
||||
'RIJNA',
|
||||
'ZÁŘÍ',
|
||||
'ŘÍJNA',
|
||||
'LISTOPADU',
|
||||
'PROSINCE',
|
||||
];
|
||||
|
||||
/// Returns Czech date header like "STREDA, 17. BREZNA".
|
||||
/// Returns Czech date header like "STŘEDA, 17. BŘEZNA".
|
||||
String czechDateHeader(DateTime date) {
|
||||
final day = _czechDays[date.weekday - 1];
|
||||
final month = _czechMonthsGenitive[date.month - 1];
|
||||
@@ -33,8 +33,8 @@ String czechDateHeader(DateTime date) {
|
||||
/// Returns time-based Czech greeting.
|
||||
String czechGreeting() {
|
||||
final hour = DateTime.now().hour;
|
||||
if (hour >= 5 && hour < 12) return 'Dobre rano';
|
||||
if (hour >= 12 && hour < 18) return 'Dobry den';
|
||||
if (hour >= 18 && hour < 22) return 'Dobry vecer';
|
||||
if (hour >= 5 && hour < 12) return 'Dobré ráno';
|
||||
if (hour >= 12 && hour < 18) return 'Dobrý den';
|
||||
if (hour >= 18 && hour < 22) return 'Dobrý večer';
|
||||
return 'Dobrou noc';
|
||||
}
|
||||
|
||||
@@ -28,13 +28,13 @@ class AppShell extends StatelessWidget {
|
||||
children: [
|
||||
_NavItem(
|
||||
icon: Icons.grid_view_rounded,
|
||||
label: 'ukoly',
|
||||
label: 'úkoly',
|
||||
isActive: navigationShell.currentIndex == 0,
|
||||
onTap: () => navigationShell.goBranch(0),
|
||||
),
|
||||
_NavItem(
|
||||
icon: Icons.pets_rounded,
|
||||
label: 'mazlicek',
|
||||
label: 'mazlíček',
|
||||
isActive: navigationShell.currentIndex == 1,
|
||||
onTap: () => navigationShell.goBranch(1),
|
||||
),
|
||||
|
||||
@@ -74,19 +74,19 @@ class EnergyBadge extends StatelessWidget {
|
||||
KittieColors.greenLight,
|
||||
KittieColors.greenDark,
|
||||
Icons.eco_outlined,
|
||||
'Lehke',
|
||||
'Lehké',
|
||||
),
|
||||
EnergyLevel.medium => (
|
||||
KittieColors.orangeLight,
|
||||
KittieColors.orange,
|
||||
Icons.wb_sunny_outlined,
|
||||
'Stredni',
|
||||
'Střední',
|
||||
),
|
||||
EnergyLevel.high => (
|
||||
KittieColors.redLight,
|
||||
KittieColors.red,
|
||||
Icons.local_fire_department_outlined,
|
||||
'Narocne',
|
||||
'Náročné',
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -28,11 +28,11 @@ class ProfileScreen extends ConsumerWidget {
|
||||
|
||||
// User name
|
||||
_EditableRow(
|
||||
label: 'Jmeno',
|
||||
label: 'Jméno',
|
||||
value: user.name,
|
||||
onEdit: () => _showEditDialog(
|
||||
context,
|
||||
title: 'Upravit jmeno',
|
||||
title: 'Upravit jméno',
|
||||
initialValue: user.name,
|
||||
onSave: (v) => ref.read(userProvider.notifier).updateName(v),
|
||||
),
|
||||
@@ -41,11 +41,11 @@ class ProfileScreen extends ConsumerWidget {
|
||||
|
||||
// Pet name
|
||||
_EditableRow(
|
||||
label: 'Jmeno mazlicka',
|
||||
label: 'Jméno mazlíčka',
|
||||
value: user.petName,
|
||||
onEdit: () => _showEditDialog(
|
||||
context,
|
||||
title: 'Upravit jmeno mazlicka',
|
||||
title: 'Upravit jméno mazlíčka',
|
||||
initialValue: user.petName,
|
||||
onSave: (v) =>
|
||||
ref.read(userProvider.notifier).updatePetName(v),
|
||||
@@ -54,7 +54,7 @@ class ProfileScreen extends ConsumerWidget {
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Pet type
|
||||
_InfoRow(label: 'Typ mazlicka', value: user.petType.label),
|
||||
_InfoRow(label: 'Typ mazlíčka', value: user.petType.label),
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// Stats summary
|
||||
@@ -79,14 +79,14 @@ class ProfileScreen extends ConsumerWidget {
|
||||
const Divider(color: KittieColors.creamBorder, height: 24),
|
||||
_StatRow(
|
||||
icon: Icons.check_circle_rounded,
|
||||
label: 'Hotove ukoly',
|
||||
label: 'Hotové úkoly',
|
||||
value: '${petState.totalTasksDone}',
|
||||
color: KittieColors.sageGreen,
|
||||
),
|
||||
const Divider(color: KittieColors.creamBorder, height: 24),
|
||||
_StatRow(
|
||||
icon: Icons.star_rounded,
|
||||
label: 'Uroven mazlicka',
|
||||
label: 'Úroveň mazlíčka',
|
||||
value: '$level',
|
||||
color: KittieColors.amber,
|
||||
),
|
||||
@@ -156,7 +156,7 @@ class ProfileScreen extends ConsumerWidget {
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(ctx),
|
||||
child: const Text('Zrusit'),
|
||||
child: const Text('Zrušit'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
@@ -164,7 +164,7 @@ class ProfileScreen extends ConsumerWidget {
|
||||
if (text.isNotEmpty) onSave(text);
|
||||
Navigator.pop(ctx);
|
||||
},
|
||||
child: const Text('Ulozit'),
|
||||
child: const Text('Uložit'),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -184,7 +184,7 @@ class _SelfCareTag extends StatelessWidget {
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'Pece o sebe',
|
||||
'Péče o sebe',
|
||||
style: KittieTypography.labelSmall.copyWith(
|
||||
color: KittieColors.purple,
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user