fix: resolve widget API mismatches and build errors after merge
- EnergyBar: add energyLevel int? param, make fraction optional - EnergyBadge: add energyLevel int? param, make level optional - StreakDots: add streakDays int? param, make days/streakCount optional - TaskCard: add task TaskEntity? and onComplete VoidCallback? params - PetBubble: replace local PetType enum with domain PetType import - TaskEntity: add isCompleted getter (status == TaskStatus.done) - TasksNotifier: add uncompleteTask() method - task_providers: add todayTasksProvider alias for tasksProvider - PetStateNotifier: fix constructor (String ids, updatedAt), add onTaskCompleted() - TasksScreen: watch userProvider + petMoodProvider, fix PetBubble/EmptyState calls Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,8 @@ class TaskEntity {
|
||||
required this.updatedAt,
|
||||
});
|
||||
|
||||
bool get isCompleted => status == TaskStatus.done;
|
||||
|
||||
TaskEntity copyWith({
|
||||
int? id,
|
||||
int? userId,
|
||||
|
||||
@@ -40,18 +40,21 @@ final userProvider =
|
||||
|
||||
class PetStateNotifier extends Notifier<PetStateEntity> {
|
||||
@override
|
||||
PetStateEntity build() => const PetStateEntity(
|
||||
id: 1,
|
||||
userId: 1,
|
||||
PetStateEntity build() => PetStateEntity(
|
||||
id: '1',
|
||||
userId: '1',
|
||||
energyToday: 2,
|
||||
streakDays: 3,
|
||||
totalTasksDone: 12,
|
||||
unlockedItems: 'masle,pelisek',
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
void incrementTasksDone() {
|
||||
state = state.copyWith(totalTasksDone: state.totalTasksDone + 1);
|
||||
}
|
||||
|
||||
void onTaskCompleted() => incrementTasksDone();
|
||||
}
|
||||
|
||||
final petStateProvider =
|
||||
|
||||
@@ -21,6 +21,19 @@ class TasksNotifier extends Notifier<List<TaskEntity>> {
|
||||
];
|
||||
}
|
||||
|
||||
void uncompleteTask(int id) {
|
||||
state = [
|
||||
for (final task in state)
|
||||
if (task.id == id)
|
||||
task.copyWith(
|
||||
status: TaskStatus.pending,
|
||||
updatedAt: DateTime.now(),
|
||||
)
|
||||
else
|
||||
task,
|
||||
];
|
||||
}
|
||||
|
||||
void skipTask(int id) {
|
||||
final skipped = state.firstWhere((t) => t.id == id);
|
||||
state = [
|
||||
@@ -34,6 +47,9 @@ class TasksNotifier extends Notifier<List<TaskEntity>> {
|
||||
final tasksProvider =
|
||||
NotifierProvider<TasksNotifier, List<TaskEntity>>(TasksNotifier.new);
|
||||
|
||||
/// Alias — today's tasks (same provider, filtered by status in screens).
|
||||
final todayTasksProvider = tasksProvider;
|
||||
|
||||
/// Pending tasks only, ordered by sort order.
|
||||
final pendingTasksProvider = Provider<List<TaskEntity>>((ref) {
|
||||
final tasks = ref.watch(tasksProvider);
|
||||
|
||||
@@ -23,6 +23,8 @@ class TasksScreen extends ConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final tasks = ref.watch(todayTasksProvider);
|
||||
final petState = ref.watch(petStateProvider);
|
||||
final userState = ref.watch(userProvider);
|
||||
final petMood = ref.watch(petMoodProvider);
|
||||
final userName = ref.watch(userNameProvider);
|
||||
|
||||
final pendingTasks = tasks.where((t) => !t.isCompleted).toList();
|
||||
@@ -32,7 +34,7 @@ class TasksScreen extends ConsumerWidget {
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: tasks.isEmpty
|
||||
? _EmptyState(petType: petState.petType)
|
||||
? _EmptyState(petType: userState.petType.name)
|
||||
: SingleChildScrollView(
|
||||
padding: const EdgeInsets.fromLTRB(20, 16, 20, 100),
|
||||
child: Column(
|
||||
@@ -67,9 +69,10 @@ class TasksScreen extends ConsumerWidget {
|
||||
|
||||
// 4. Pet area
|
||||
PetBubble(
|
||||
petType: petState.petType,
|
||||
mood: petState.mood,
|
||||
message: _petMessage(petState.mood, petState.petName),
|
||||
petType: userState.petType,
|
||||
petName: userState.petName,
|
||||
mood: petMood.name,
|
||||
message: _petMessage(petMood.name, userState.petName),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
|
||||
@@ -27,14 +27,20 @@ enum EnergyLevel {
|
||||
/// - medium: orange bg, orange text, sun icon — "Stredni"
|
||||
/// - high: red bg, red text, fire icon — "Narocne"
|
||||
class EnergyBadge extends StatelessWidget {
|
||||
const EnergyBadge({super.key, required this.level});
|
||||
const EnergyBadge({super.key, this.level, this.energyLevel});
|
||||
|
||||
final EnergyLevel level;
|
||||
final EnergyLevel? level;
|
||||
|
||||
/// Energy level as raw int (1–3); used when [level] is not provided.
|
||||
final int? energyLevel;
|
||||
|
||||
/// Convenience constructor from raw int (1/2/3).
|
||||
factory EnergyBadge.fromInt(int level) =>
|
||||
EnergyBadge(level: EnergyLevel.fromInt(level));
|
||||
|
||||
EnergyLevel get _effectiveLevel =>
|
||||
level ?? EnergyLevel.fromInt(energyLevel ?? 1);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final (bgColor, textColor, icon, label) = _props;
|
||||
@@ -61,7 +67,7 @@ class EnergyBadge extends StatelessWidget {
|
||||
}
|
||||
|
||||
(Color bg, Color text, IconData icon, String label) get _props =>
|
||||
switch (level) {
|
||||
switch (_effectiveLevel) {
|
||||
EnergyLevel.low => (
|
||||
KittieColors.greenLight,
|
||||
KittieColors.greenDark,
|
||||
|
||||
@@ -8,19 +8,29 @@ import '../../core/theme/kittie_typography.dart';
|
||||
///
|
||||
/// Shows a track with creamDark background and amber fill
|
||||
/// that animates to the current [fraction] (0.0–1.0).
|
||||
/// Alternatively, pass [energyLevel] (1–3) to compute the fraction.
|
||||
class EnergyBar extends StatelessWidget {
|
||||
const EnergyBar({
|
||||
super.key,
|
||||
required this.fraction,
|
||||
this.fraction,
|
||||
this.energyLevel,
|
||||
this.label = 'ENERGIE',
|
||||
}) : assert(fraction >= 0.0 && fraction <= 1.0);
|
||||
});
|
||||
|
||||
/// Fill amount from 0.0 (empty) to 1.0 (full).
|
||||
final double fraction;
|
||||
final double? fraction;
|
||||
|
||||
/// Energy level 1–3; used when [fraction] is not provided.
|
||||
final int? energyLevel;
|
||||
|
||||
/// Label displayed above the bar.
|
||||
final String label;
|
||||
|
||||
double get _effectiveFraction {
|
||||
if (energyLevel != null) return (energyLevel! / 3.0).clamp(0.0, 1.0);
|
||||
return (fraction ?? 0.0).clamp(0.0, 1.0);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
@@ -52,7 +62,7 @@ class EnergyBar extends StatelessWidget {
|
||||
AnimatedContainer(
|
||||
duration: AppConstants.animationNormal,
|
||||
curve: Curves.easeInOut,
|
||||
width: constraints.maxWidth * fraction,
|
||||
width: constraints.maxWidth * _effectiveFraction,
|
||||
color: KittieColors.amber,
|
||||
),
|
||||
],
|
||||
|
||||
@@ -3,16 +3,7 @@ import 'package:flutter/material.dart';
|
||||
import '../../core/constants/app_constants.dart';
|
||||
import '../../core/theme/kittie_colors.dart';
|
||||
import '../../core/theme/kittie_typography.dart';
|
||||
|
||||
/// Pet type determines the displayed emoji.
|
||||
enum PetType {
|
||||
dog('\u{1F436}'),
|
||||
cat('\u{1F431}'),
|
||||
capybara('\u{1F9AB}');
|
||||
|
||||
const PetType(this.emoji);
|
||||
final String emoji;
|
||||
}
|
||||
import '../../domain/entities/pet_type.dart';
|
||||
|
||||
/// Pet area card with a speech bubble.
|
||||
///
|
||||
|
||||
@@ -17,18 +17,39 @@ enum DayStatus { done, today, future }
|
||||
class StreakDots extends StatelessWidget {
|
||||
const StreakDots({
|
||||
super.key,
|
||||
required this.days,
|
||||
required this.streakCount,
|
||||
}) : assert(days.length == 7, 'days must have exactly 7 entries');
|
||||
this.days,
|
||||
this.streakCount = 0,
|
||||
this.streakDays,
|
||||
});
|
||||
|
||||
/// Status for each day Mon–Sun.
|
||||
final List<DayStatus> days;
|
||||
/// Status for each day Mon–Sun. Computed from [streakDays] if omitted.
|
||||
final List<DayStatus>? days;
|
||||
|
||||
/// Current consecutive-day streak count.
|
||||
final int streakCount;
|
||||
|
||||
/// Streak as an integer; used to compute [days] and count when provided.
|
||||
final int? streakDays;
|
||||
|
||||
static const _dayLabels = ['Po', 'Ut', 'St', 'Ct', 'Pa', 'So', 'Ne'];
|
||||
|
||||
static List<DayStatus> _generateDays(int streak) {
|
||||
final todayIndex = DateTime.now().weekday - 1; // 0=Mon, 6=Sun
|
||||
return List.generate(7, (i) {
|
||||
if (i == todayIndex) return DayStatus.today;
|
||||
if (i < todayIndex) {
|
||||
final daysAgo = todayIndex - i;
|
||||
return daysAgo <= streak ? DayStatus.done : DayStatus.future;
|
||||
}
|
||||
return DayStatus.future;
|
||||
});
|
||||
}
|
||||
|
||||
List<DayStatus> get _effectiveDays =>
|
||||
streakDays != null ? _generateDays(streakDays!) : (days ?? List.filled(7, DayStatus.future));
|
||||
|
||||
int get _effectiveCount => streakDays ?? streakCount;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
@@ -41,7 +62,7 @@ class StreakDots extends StatelessWidget {
|
||||
const Text('\u{1F525}', style: TextStyle(fontSize: 16)),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'$streakCount',
|
||||
'$_effectiveCount',
|
||||
style: KittieTypography.labelLarge.copyWith(
|
||||
color: KittieColors.brown,
|
||||
),
|
||||
@@ -55,7 +76,7 @@ class StreakDots extends StatelessWidget {
|
||||
children: List.generate(7, (i) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(left: i > 0 ? 8 : 0),
|
||||
child: _DayDot(label: _dayLabels[i], status: days[i]),
|
||||
child: _DayDot(label: _dayLabels[i], status: _effectiveDays[i]),
|
||||
);
|
||||
}),
|
||||
),
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:flutter/services.dart';
|
||||
import '../../core/constants/app_constants.dart';
|
||||
import '../../core/theme/kittie_colors.dart';
|
||||
import '../../core/theme/kittie_typography.dart';
|
||||
import '../../domain/entities/task_entity.dart';
|
||||
import 'energy_badge.dart';
|
||||
|
||||
/// A task list item with circular checkbox, title, energy badge, and time.
|
||||
@@ -18,25 +19,36 @@ import 'energy_badge.dart';
|
||||
class TaskCard extends StatelessWidget {
|
||||
const TaskCard({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.energyLevel,
|
||||
this.task,
|
||||
this.title,
|
||||
this.energyLevel,
|
||||
this.isDone = false,
|
||||
this.isSelfCare = false,
|
||||
this.isOverdue = false,
|
||||
this.timeText,
|
||||
this.onComplete,
|
||||
this.onToggle,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final int energyLevel;
|
||||
/// Optional task entity; its fields take precedence over individual params.
|
||||
final TaskEntity? task;
|
||||
final String? title;
|
||||
final int? energyLevel;
|
||||
final bool isDone;
|
||||
final bool isSelfCare;
|
||||
final bool isOverdue;
|
||||
final String? timeText;
|
||||
final VoidCallback? onComplete;
|
||||
final VoidCallback? onToggle;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
String get _title => task?.title ?? title ?? '';
|
||||
int get _energyLevel => task?.energyLevel ?? energyLevel ?? 2;
|
||||
bool get _isDone => task?.isCompleted ?? isDone;
|
||||
bool get _isSelfCare => task?.isSelfCare ?? isSelfCare;
|
||||
VoidCallback? get _toggleCallback => onComplete ?? onToggle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
@@ -52,13 +64,13 @@ class TaskCard extends StatelessWidget {
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
_Checkbox(isDone: isDone, onToggle: onToggle),
|
||||
_Checkbox(isDone: _isDone, onToggle: _toggleCallback),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(child: _Content(
|
||||
title: title,
|
||||
energyLevel: energyLevel,
|
||||
isDone: isDone,
|
||||
isSelfCare: isSelfCare,
|
||||
title: _title,
|
||||
energyLevel: _energyLevel,
|
||||
isDone: _isDone,
|
||||
isSelfCare: _isSelfCare,
|
||||
timeText: timeText,
|
||||
)),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user