refactor(activity-meter): redesign screen with honest per-range stats
This commit is contained in:
@@ -3,6 +3,7 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:intl/date_symbol_data_local.dart';
|
||||||
import 'package:design_system/design_system.dart';
|
import 'package:design_system/design_system.dart';
|
||||||
import 'package:sca_treezor/sca_treezor.dart';
|
import 'package:sca_treezor/sca_treezor.dart';
|
||||||
import 'package:sf_app_platform/config/env/environment_enum.dart';
|
import 'package:sf_app_platform/config/env/environment_enum.dart';
|
||||||
@@ -19,14 +20,12 @@ import 'package:sf_tracking/sf_tracking.dart';
|
|||||||
Future<void> initApp(EnvironmentEnum env) async {
|
Future<void> initApp(EnvironmentEnum env) async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
||||||
|
await initializeDateFormatting();
|
||||||
|
|
||||||
navigationModule();
|
navigationModule();
|
||||||
scaTreezorModule();
|
scaTreezorModule();
|
||||||
themePackages();
|
themePackages();
|
||||||
|
|
||||||
// Order matters: Firebase → sfTracking (FirebaseTrackingClient touches
|
|
||||||
// FirebaseAnalytics.instance) → router (SaveFamilyApp wires sfTracking
|
|
||||||
// into SfRouterListener at construction time).
|
|
||||||
await setupFirebase(env);
|
await setupFirebase(env);
|
||||||
await setupNotifications();
|
await setupNotifications();
|
||||||
initSfTracking();
|
initSfTracking();
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ dependencies:
|
|||||||
#dependencies go here
|
#dependencies go here
|
||||||
cupertino_icons: ^1.0.8
|
cupertino_icons: ^1.0.8
|
||||||
flutter_svg: ^2.2.2
|
flutter_svg: ^2.2.2
|
||||||
|
intl: ^0.20.2
|
||||||
go_router_builder: ^4.1.1
|
go_router_builder: ^4.1.1
|
||||||
build_runner: ^2.7.1
|
build_runner: ^2.7.1
|
||||||
|
|
||||||
|
|||||||
@@ -5,13 +5,18 @@ import 'package:legacy_shared/legacy_shared.dart';
|
|||||||
import 'package:sf_localizations/sf_localizations.dart';
|
import 'package:sf_localizations/sf_localizations.dart';
|
||||||
import 'package:utils/utils.dart';
|
import 'package:utils/utils.dart';
|
||||||
|
|
||||||
|
import '../../../core/presentation/format_date.dart';
|
||||||
|
import '../../../core/presentation/time_range.dart';
|
||||||
import '../../../core/presentation/widgets/time_range_selector.dart';
|
import '../../../core/presentation/widgets/time_range_selector.dart';
|
||||||
import 'state/activity_meter_view_model.dart';
|
import 'state/activity_meter_view_model.dart';
|
||||||
import 'state/activity_meter_view_state.dart';
|
import 'state/activity_meter_view_state.dart';
|
||||||
|
import 'widgets/activity_footers.dart';
|
||||||
|
import 'widgets/hourly_bar_chart.dart';
|
||||||
|
import 'widgets/pedometer_toggle.dart';
|
||||||
|
import 'widgets/section_header.dart';
|
||||||
import 'widgets/steps_bar_chart.dart';
|
import 'widgets/steps_bar_chart.dart';
|
||||||
import 'widgets/steps_history_section.dart';
|
import 'widgets/steps_history_section.dart';
|
||||||
import 'widgets/steps_progress_ring.dart';
|
import 'widgets/steps_progress_ring.dart';
|
||||||
import 'widgets/steps_stats_row.dart';
|
|
||||||
|
|
||||||
class ActivityMeterScreen extends ConsumerWidget {
|
class ActivityMeterScreen extends ConsumerWidget {
|
||||||
const ActivityMeterScreen({super.key});
|
const ActivityMeterScreen({super.key});
|
||||||
@@ -19,145 +24,66 @@ class ActivityMeterScreen extends ConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final theme = ref.watch(themePortProvider);
|
final theme = ref.watch(themePortProvider);
|
||||||
final state = ref.watch(activityMeterViewModelProvider);
|
final isLoading = ref.watch(
|
||||||
final vm = ref.read(activityMeterViewModelProvider.notifier);
|
activityMeterViewModelProvider.select((s) => s.isLoading),
|
||||||
final device = ref.watch(selectedDeviceProvider).value;
|
);
|
||||||
|
|
||||||
ref.listen(activityMeterViewModelProvider.select((s) => s.errorEvent), (
|
ref.listen(activityMeterViewModelProvider.select((s) => s.errorEvent), (
|
||||||
previous,
|
_,
|
||||||
next,
|
next,
|
||||||
) {
|
) {
|
||||||
if (next != null) {
|
if (next == null) return;
|
||||||
final message = switch (next) {
|
final message = switch (next) {
|
||||||
ActivityMeterErrorEvent.loadData => context.translate(
|
ActivityMeterErrorEvent.loadData ||
|
||||||
I18n.errorActivityData,
|
ActivityMeterErrorEvent.loadMore => context.translate(
|
||||||
),
|
I18n.errorActivityData,
|
||||||
ActivityMeterErrorEvent.loadMore => context.translate(
|
),
|
||||||
I18n.errorActivityData,
|
ActivityMeterErrorEvent.pedometer => context.translate(
|
||||||
),
|
I18n.errorPedometer,
|
||||||
ActivityMeterErrorEvent.pedometer => context.translate(
|
),
|
||||||
I18n.errorPedometer,
|
};
|
||||||
),
|
showTopSnackbar(context, message: message, type: MessageType.error);
|
||||||
};
|
|
||||||
showTopSnackbar(context, message: message, type: MessageType.error);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final hasData =
|
|
||||||
state.todayTotal > 0 ||
|
|
||||||
state.chartData.isNotEmpty ||
|
|
||||||
state.historyData.isNotEmpty;
|
|
||||||
|
|
||||||
return LegacyPageLayout(
|
return LegacyPageLayout(
|
||||||
theme: theme,
|
theme: theme,
|
||||||
title: context.translate(I18n.activityMeter),
|
title: context.translate(I18n.activityMeter),
|
||||||
body: state.isLoading
|
body: isLoading
|
||||||
? const Center(child: CircularProgressIndicator())
|
? const Center(child: CircularProgressIndicator())
|
||||||
: SingleChildScrollView(
|
: const _ActivityMeterBody(),
|
||||||
child: Column(
|
);
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
}
|
||||||
children: [
|
}
|
||||||
if (!hasData)
|
|
||||||
Padding(
|
class _ActivityMeterBody extends ConsumerWidget {
|
||||||
padding: EdgeInsets.symmetric(
|
const _ActivityMeterBody();
|
||||||
horizontal: SizeUtils.getByScreen(small: 16, big: 14),
|
|
||||||
vertical: SizeUtils.getByScreen(small: 12, big: 10),
|
@override
|
||||||
),
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
child: Row(
|
final vm = ref.read(activityMeterViewModelProvider.notifier);
|
||||||
children: [
|
final timeRange = ref.watch(
|
||||||
Icon(
|
activityMeterViewModelProvider.select((s) => s.timeRange),
|
||||||
Icons.info_outline,
|
);
|
||||||
color: theme.getColorFor(ThemeCode.legacyPrimary),
|
|
||||||
size: SizeUtils.getByScreen(small: 20, big: 22),
|
return SingleChildScrollView(
|
||||||
),
|
child: Column(
|
||||||
SizedBox(
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
width: SizeUtils.getByScreen(small: 8, big: 10),
|
children: [
|
||||||
),
|
const PedometerToggle(),
|
||||||
Expanded(
|
SectionHeader(title: context.translate(I18n.activityMeterSectionToday)),
|
||||||
child: Text(
|
const _TodaySection(),
|
||||||
context.translate(I18n.noActivityData),
|
const _ActivityRangeHeader(),
|
||||||
style: TextStyle(
|
TimeRangeSelector(
|
||||||
fontSize: SizeUtils.getByScreen(
|
selected: timeRange,
|
||||||
small: 14,
|
onSelected: vm.selectTimeRange,
|
||||||
big: 15,
|
onCustomTap: () => _pickCustomRange(context, vm),
|
||||||
),
|
theme: ref.watch(themePortProvider),
|
||||||
color: theme
|
),
|
||||||
.getColorFor(ThemeCode.textPrimary)
|
const _ActivitySection(),
|
||||||
.withAlpha(178),
|
if (timeRange != TimeRange.today) const _HistorySection(),
|
||||||
),
|
SizedBox(height: SizeUtils.getByScreen(small: 24, big: 20)),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.symmetric(
|
|
||||||
horizontal: SizeUtils.getByScreen(small: 16, big: 14),
|
|
||||||
vertical: SizeUtils.getByScreen(small: 4, big: 4),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
context.translate(I18n.activityMeterPedometer),
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: SizeUtils.getByScreen(small: 14, big: 15),
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Switch.adaptive(
|
|
||||||
value: device?.settings.pedometer ?? false,
|
|
||||||
activeTrackColor: theme.getColorFor(
|
|
||||||
ThemeCode.legacyPrimary,
|
|
||||||
),
|
|
||||||
onChanged: (value) async {
|
|
||||||
final success = await vm.togglePedometer(
|
|
||||||
enabled: value,
|
|
||||||
);
|
|
||||||
if (!context.mounted) return;
|
|
||||||
if (success) {
|
|
||||||
showTopSnackbar(
|
|
||||||
context,
|
|
||||||
message: context.translate(
|
|
||||||
value
|
|
||||||
? I18n.activityMeterPedometerEnabled
|
|
||||||
: I18n.activityMeterPedometerDisabled,
|
|
||||||
),
|
|
||||||
type: MessageType.success,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
StepsProgressRing(
|
|
||||||
steps: state.todayTotal,
|
|
||||||
goal: state.dailyGoal,
|
|
||||||
theme: theme,
|
|
||||||
),
|
|
||||||
TimeRangeSelector(
|
|
||||||
selected: state.timeRange,
|
|
||||||
onSelected: (range) => vm.selectTimeRange(range),
|
|
||||||
onCustomTap: () => _pickCustomRange(context, vm),
|
|
||||||
theme: theme,
|
|
||||||
),
|
|
||||||
StepsBarChart(data: state.chartData, theme: theme),
|
|
||||||
SizedBox(height: SizeUtils.getByScreen(small: 8, big: 6)),
|
|
||||||
StepsStatsRow(stats: state.stats, theme: theme),
|
|
||||||
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 14)),
|
|
||||||
StepsHistorySection(
|
|
||||||
items: state.historyData,
|
|
||||||
dailyGoal: state.dailyGoal,
|
|
||||||
hasMore: state.hasMoreHistory,
|
|
||||||
isLoadingMore: state.isLoadingMore,
|
|
||||||
onLoadMore: () => vm.loadMoreHistory(),
|
|
||||||
theme: theme,
|
|
||||||
),
|
|
||||||
SizedBox(height: SizeUtils.getByScreen(small: 24, big: 20)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,3 +102,164 @@ class ActivityMeterScreen extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _TodaySection extends ConsumerWidget {
|
||||||
|
const _TodaySection();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final theme = ref.watch(themePortProvider);
|
||||||
|
final (todayTotal, dailyGoal) = ref.watch(
|
||||||
|
activityMeterViewModelProvider.select(
|
||||||
|
(s) => (s.todayTotal, s.dailyGoal),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
StepsProgressRing(steps: todayTotal, goal: dailyGoal, theme: theme),
|
||||||
|
if (todayTotal == 0)
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: SizeUtils.getByScreen(small: 32, big: 28),
|
||||||
|
vertical: SizeUtils.getByScreen(small: 4, big: 2),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
context.translate(I18n.activityMeterNoStepsToday),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: SizeUtils.getByScreen(small: 13, big: 12),
|
||||||
|
color: theme
|
||||||
|
.getColorFor(ThemeCode.textPrimary)
|
||||||
|
.withValues(alpha: 0.5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ActivityRangeHeader extends ConsumerWidget {
|
||||||
|
const _ActivityRangeHeader();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final (start, end, timeRange) = ref.watch(
|
||||||
|
activityMeterViewModelProvider.select(
|
||||||
|
(s) => (s.rangeStart, s.rangeEnd, s.timeRange),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return SectionHeader(
|
||||||
|
title: context.translate(I18n.activityMeterSectionActivity),
|
||||||
|
subtitle: _subtitle(context, start, end, timeRange),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _subtitle(
|
||||||
|
BuildContext context,
|
||||||
|
DateTime? start,
|
||||||
|
DateTime? end,
|
||||||
|
TimeRange timeRange,
|
||||||
|
) {
|
||||||
|
if (start == null || end == null) return null;
|
||||||
|
if (timeRange == TimeRange.today) {
|
||||||
|
return context
|
||||||
|
.translate(I18n.activityMeterRangeLabelToday)
|
||||||
|
.replaceAll('{date}', formatDayHeader(context, start));
|
||||||
|
}
|
||||||
|
return '${formatDayHeader(context, start)} — ${formatDayHeader(context, end)}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ActivitySection extends ConsumerWidget {
|
||||||
|
const _ActivitySection();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final theme = ref.watch(themePortProvider);
|
||||||
|
final (timeRange, hourly, daily, stats, activeHours, hasData) = ref.watch(
|
||||||
|
activityMeterViewModelProvider.select(
|
||||||
|
(s) => (
|
||||||
|
s.timeRange,
|
||||||
|
s.hourlyData,
|
||||||
|
s.chartData,
|
||||||
|
s.stats,
|
||||||
|
s.activeHoursToday,
|
||||||
|
s.hasActivityData,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final isToday = timeRange == TimeRange.today;
|
||||||
|
|
||||||
|
if (!hasData) {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: SizeUtils.getByScreen(small: 16, big: 14),
|
||||||
|
vertical: SizeUtils.getByScreen(small: 24, big: 20),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
context.translate(I18n.activityMeterNoStepsPeriod),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: SizeUtils.getByScreen(small: 13, big: 12),
|
||||||
|
color: theme
|
||||||
|
.getColorFor(ThemeCode.textPrimary)
|
||||||
|
.withValues(alpha: 0.5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
if (isToday)
|
||||||
|
HourlyBarChart(data: hourly)
|
||||||
|
else
|
||||||
|
StepsBarChart(data: daily),
|
||||||
|
SizedBox(height: SizeUtils.getByScreen(small: 8, big: 6)),
|
||||||
|
if (isToday)
|
||||||
|
TodayActivityFooter(activeHours: activeHours)
|
||||||
|
else
|
||||||
|
PeriodActivityFooter(stats: stats),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HistorySection extends ConsumerWidget {
|
||||||
|
const _HistorySection();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final theme = ref.watch(themePortProvider);
|
||||||
|
final vm = ref.read(activityMeterViewModelProvider.notifier);
|
||||||
|
final (history, dailyGoal, hasMore, isLoadingMore) = ref.watch(
|
||||||
|
activityMeterViewModelProvider.select(
|
||||||
|
(s) => (s.historyData, s.dailyGoal, s.hasMoreHistory, s.isLoadingMore),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (history.isEmpty) return const SizedBox.shrink();
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
SectionHeader(
|
||||||
|
title: context.translate(I18n.activityMeterSectionHistory),
|
||||||
|
),
|
||||||
|
StepsHistorySection(
|
||||||
|
items: history.where((d) => d.totalSteps > 0).toList(),
|
||||||
|
dailyGoal: dailyGoal,
|
||||||
|
hasMore: hasMore,
|
||||||
|
isLoadingMore: isLoadingMore,
|
||||||
|
onLoadMore: vm.loadMoreHistory,
|
||||||
|
theme: theme,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import '../../../../core/domain/entities/steps_entity.dart';
|
||||||
|
import '../../../../core/presentation/time_range.dart';
|
||||||
|
import 'activity_meter_view_state.dart';
|
||||||
|
|
||||||
|
List<DailySteps> groupByDay(List<StepsEntity> steps) {
|
||||||
|
final Map<DateTime, int> groups = {};
|
||||||
|
|
||||||
|
for (final step in steps) {
|
||||||
|
final date = DateTime.fromMillisecondsSinceEpoch(step.occurredAt);
|
||||||
|
final dayKey = DateTime(date.year, date.month, date.day);
|
||||||
|
groups[dayKey] = (groups[dayKey] ?? 0) + step.steps;
|
||||||
|
}
|
||||||
|
|
||||||
|
final sorted = groups.entries.toList()
|
||||||
|
..sort((a, b) => a.key.compareTo(b.key));
|
||||||
|
|
||||||
|
return sorted
|
||||||
|
.map((e) => DailySteps(date: e.key, totalSteps: e.value))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<HourlySteps> groupByHour(List<StepsEntity> steps) {
|
||||||
|
final Map<int, int> groups = {};
|
||||||
|
|
||||||
|
for (final step in steps) {
|
||||||
|
final date = DateTime.fromMillisecondsSinceEpoch(step.occurredAt);
|
||||||
|
groups[date.hour] = (groups[date.hour] ?? 0) + step.steps;
|
||||||
|
}
|
||||||
|
|
||||||
|
return List<HourlySteps>.generate(
|
||||||
|
24,
|
||||||
|
(h) => HourlySteps(hour: h, totalSteps: groups[h] ?? 0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int countActiveHours(List<StepsEntity> steps) {
|
||||||
|
final Set<int> hours = {};
|
||||||
|
for (final s in steps) {
|
||||||
|
if (s.steps <= 0) continue;
|
||||||
|
final date = DateTime.fromMillisecondsSinceEpoch(s.occurredAt);
|
||||||
|
hours.add(date.hour);
|
||||||
|
}
|
||||||
|
return hours.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<DailySteps> mergeHistory(
|
||||||
|
List<DailySteps> existing,
|
||||||
|
List<DailySteps> newItems,
|
||||||
|
) {
|
||||||
|
final Map<DateTime, int> merged = {
|
||||||
|
for (final d in existing) d.date: d.totalSteps,
|
||||||
|
};
|
||||||
|
for (final d in newItems) {
|
||||||
|
merged[d.date] = (merged[d.date] ?? 0) + d.totalSteps;
|
||||||
|
}
|
||||||
|
|
||||||
|
final sorted = merged.entries.toList()
|
||||||
|
..sort((a, b) => b.key.compareTo(a.key));
|
||||||
|
|
||||||
|
return sorted
|
||||||
|
.map((e) => DailySteps(date: e.key, totalSteps: e.value))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
StepsStats computeStats(List<DailySteps> daily, int rangeDays) {
|
||||||
|
if (daily.isEmpty) {
|
||||||
|
return StepsStats(rangeDays: rangeDays);
|
||||||
|
}
|
||||||
|
|
||||||
|
final total = daily.fold(0, (sum, d) => sum + d.totalSteps);
|
||||||
|
final activeDays = daily.where((d) => d.totalSteps > 0).length;
|
||||||
|
final best = daily.reduce((a, b) => a.totalSteps >= b.totalSteps ? a : b);
|
||||||
|
|
||||||
|
final divisor = rangeDays > 0 ? rangeDays : daily.length;
|
||||||
|
|
||||||
|
return StepsStats(
|
||||||
|
total: total,
|
||||||
|
avgPerDay: (total / divisor).round(),
|
||||||
|
activeDays: activeDays,
|
||||||
|
rangeDays: rangeDays,
|
||||||
|
bestDay: total > 0 ? best : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int countDays(DateTime start, DateTime end) {
|
||||||
|
final s = DateTime.utc(start.year, start.month, start.day);
|
||||||
|
final e = DateTime.utc(end.year, end.month, end.day);
|
||||||
|
return e.difference(s).inDays + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
(DateTime, DateTime) resolveRange(
|
||||||
|
TimeRange timeRange, {
|
||||||
|
required DateTime now,
|
||||||
|
DateTime? customStart,
|
||||||
|
DateTime? customEnd,
|
||||||
|
}) {
|
||||||
|
final todayStart = DateTime(now.year, now.month, now.day);
|
||||||
|
|
||||||
|
switch (timeRange) {
|
||||||
|
case TimeRange.today:
|
||||||
|
return (todayStart, now);
|
||||||
|
case TimeRange.sevenDays:
|
||||||
|
return (DateTime(now.year, now.month, now.day - 6), now);
|
||||||
|
case TimeRange.thirtyDays:
|
||||||
|
return (DateTime(now.year, now.month, now.day - 29), now);
|
||||||
|
case TimeRange.custom:
|
||||||
|
final s = customStart ?? todayStart;
|
||||||
|
final e = customEnd ?? now;
|
||||||
|
return (DateTime(s.year, s.month, s.day), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,10 +5,11 @@ import 'package:legacy_shared/legacy_shared.dart';
|
|||||||
import 'package:sf_tracking/sf_tracking.dart';
|
import 'package:sf_tracking/sf_tracking.dart';
|
||||||
|
|
||||||
import '../../../../core/data/datasources/health_query_builder.dart';
|
import '../../../../core/data/datasources/health_query_builder.dart';
|
||||||
import '../../../../core/presentation/time_range.dart';
|
|
||||||
import '../../../../core/domain/repositories/steps_repository.dart';
|
|
||||||
import '../../../../core/providers/steps_repository_provider.dart';
|
|
||||||
import '../../../../core/domain/entities/steps_entity.dart';
|
import '../../../../core/domain/entities/steps_entity.dart';
|
||||||
|
import '../../../../core/domain/repositories/steps_repository.dart';
|
||||||
|
import '../../../../core/presentation/time_range.dart';
|
||||||
|
import '../../../../core/providers/steps_repository_provider.dart';
|
||||||
|
import 'activity_meter_aggregator.dart';
|
||||||
import 'activity_meter_view_state.dart';
|
import 'activity_meter_view_state.dart';
|
||||||
|
|
||||||
final activityMeterViewModelProvider =
|
final activityMeterViewModelProvider =
|
||||||
@@ -27,11 +28,12 @@ class ActivityMeterViewModel extends Notifier<ActivityMeterViewState> {
|
|||||||
ActivityMeterViewState build() {
|
ActivityMeterViewState build() {
|
||||||
_repository = ref.read(stepsRepositoryProvider);
|
_repository = ref.read(stepsRepositoryProvider);
|
||||||
_tracking = ref.read(sfTrackingProvider);
|
_tracking = ref.read(sfTrackingProvider);
|
||||||
_init();
|
Future.microtask(_loadFilteredData);
|
||||||
return const ActivityMeterViewState();
|
return const ActivityMeterViewState();
|
||||||
}
|
}
|
||||||
|
|
||||||
String? get _identificator => ref.read(selectedDeviceProvider).value?.identificator;
|
String? get _identificator =>
|
||||||
|
ref.read(selectedDeviceProvider).value?.identificator;
|
||||||
|
|
||||||
Future<void> selectTimeRange(TimeRange range) async {
|
Future<void> selectTimeRange(TimeRange range) async {
|
||||||
if (range == state.timeRange) return;
|
if (range == state.timeRange) return;
|
||||||
@@ -77,7 +79,11 @@ class ActivityMeterViewModel extends Notifier<ActivityMeterViewState> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final nextPage = state.currentHistoryPage + 1;
|
final nextPage = state.currentHistoryPage + 1;
|
||||||
final filters = _buildTimeFilters();
|
final (start, end) = _resolveRange();
|
||||||
|
final filters = HealthQueryBuilder.timeRangeFilters(
|
||||||
|
start: start,
|
||||||
|
end: end,
|
||||||
|
);
|
||||||
|
|
||||||
final steps = await _repository.getSteps(
|
final steps = await _repository.getSteps(
|
||||||
identificator: identificator,
|
identificator: identificator,
|
||||||
@@ -90,8 +96,8 @@ class ActivityMeterViewModel extends Notifier<ActivityMeterViewState> {
|
|||||||
);
|
);
|
||||||
if (!ref.mounted) return;
|
if (!ref.mounted) return;
|
||||||
|
|
||||||
final newDaily = _groupByDay(steps);
|
final newDaily = groupByDay(steps);
|
||||||
final merged = _mergeHistory(state.historyData, newDaily);
|
final merged = mergeHistory(state.historyData, newDaily);
|
||||||
|
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
historyData: merged,
|
historyData: merged,
|
||||||
@@ -99,7 +105,7 @@ class ActivityMeterViewModel extends Notifier<ActivityMeterViewState> {
|
|||||||
hasMoreHistory: steps.length >= _historyPageSize,
|
hasMoreHistory: steps.length >= _historyPageSize,
|
||||||
isLoadingMore: false,
|
isLoadingMore: false,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (_) {
|
||||||
if (!ref.mounted) return;
|
if (!ref.mounted) return;
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
isLoadingMore: false,
|
isLoadingMore: false,
|
||||||
@@ -108,42 +114,6 @@ class ActivityMeterViewModel extends Notifier<ActivityMeterViewState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _init() async {
|
|
||||||
final identificator = _identificator;
|
|
||||||
if (identificator == null) {
|
|
||||||
state = state.copyWith(isLoading: false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
final todayStart = DateTime.now();
|
|
||||||
final todayFilters = HealthQueryBuilder.timeRangeFilters(
|
|
||||||
start: DateTime(todayStart.year, todayStart.month, todayStart.day),
|
|
||||||
end: todayStart,
|
|
||||||
);
|
|
||||||
|
|
||||||
final todaySteps = await _repository.getSteps(
|
|
||||||
identificator: identificator,
|
|
||||||
queryParameters: HealthQueryBuilder.build(
|
|
||||||
orderDirection: OrderDirection.desc,
|
|
||||||
filters: todayFilters,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (!ref.mounted) return;
|
|
||||||
|
|
||||||
final todayTotal = todaySteps.fold(0, (sum, s) => sum + s.steps);
|
|
||||||
state = state.copyWith(todayTotal: todayTotal);
|
|
||||||
|
|
||||||
await _loadFilteredData();
|
|
||||||
} catch (e) {
|
|
||||||
if (!ref.mounted) return;
|
|
||||||
state = state.copyWith(
|
|
||||||
isLoading: false,
|
|
||||||
errorEvent: ActivityMeterErrorEvent.loadData,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _loadFilteredData() async {
|
Future<void> _loadFilteredData() async {
|
||||||
final identificator = _identificator;
|
final identificator = _identificator;
|
||||||
if (identificator == null) {
|
if (identificator == null) {
|
||||||
@@ -152,9 +122,13 @@ class ActivityMeterViewModel extends Notifier<ActivityMeterViewState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final filters = _buildTimeFilters();
|
final (start, end) = _resolveRange();
|
||||||
|
final filters = HealthQueryBuilder.timeRangeFilters(
|
||||||
|
start: start,
|
||||||
|
end: end,
|
||||||
|
);
|
||||||
|
|
||||||
final (chartSteps, histSteps) = await (
|
final (chartSteps, histSteps, todaySteps) = await (
|
||||||
_repository.getSteps(
|
_repository.getSteps(
|
||||||
identificator: identificator,
|
identificator: identificator,
|
||||||
queryParameters: HealthQueryBuilder.build(
|
queryParameters: HealthQueryBuilder.build(
|
||||||
@@ -171,22 +145,37 @@ class ActivityMeterViewModel extends Notifier<ActivityMeterViewState> {
|
|||||||
filters: filters,
|
filters: filters,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
_fetchTodaySteps(identificator),
|
||||||
).wait;
|
).wait;
|
||||||
if (!ref.mounted) return;
|
if (!ref.mounted) return;
|
||||||
|
|
||||||
final chartDaily = _groupByDay(chartSteps);
|
final todayTotal = todaySteps.fold(0, (sum, s) => sum + s.steps);
|
||||||
final historyDaily = _groupByDay(histSteps).reversed.toList();
|
final activeHoursToday = countActiveHours(todaySteps);
|
||||||
|
|
||||||
|
final chartDaily = groupByDay(chartSteps);
|
||||||
|
final hourly = state.timeRange == TimeRange.today
|
||||||
|
? groupByHour(todaySteps)
|
||||||
|
: const <HourlySteps>[];
|
||||||
|
final historyDaily = groupByDay(histSteps).reversed.toList();
|
||||||
|
|
||||||
|
final rangeDays = countDays(start, end);
|
||||||
|
final stats = computeStats(chartDaily, rangeDays);
|
||||||
|
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
|
todayTotal: todayTotal,
|
||||||
chartData: chartDaily,
|
chartData: chartDaily,
|
||||||
|
hourlyData: hourly,
|
||||||
|
activeHoursToday: activeHoursToday,
|
||||||
historyData: historyDaily,
|
historyData: historyDaily,
|
||||||
currentHistoryPage: 1,
|
currentHistoryPage: 1,
|
||||||
hasMoreHistory: histSteps.length >= _historyPageSize,
|
hasMoreHistory: histSteps.length >= _historyPageSize,
|
||||||
stats: _computeStats(chartDaily),
|
stats: stats,
|
||||||
|
rangeStart: start,
|
||||||
|
rangeEnd: end,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
errorEvent: null,
|
errorEvent: null,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (_) {
|
||||||
if (!ref.mounted) return;
|
if (!ref.mounted) return;
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
@@ -195,81 +184,27 @@ class ActivityMeterViewModel extends Notifier<ActivityMeterViewState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<DailySteps> _groupByDay(List<StepsEntity> steps) {
|
Future<List<StepsEntity>> _fetchTodaySteps(String identificator) {
|
||||||
final Map<DateTime, int> groups = {};
|
final now = DateTime.now();
|
||||||
|
final todayStart = DateTime(now.year, now.month, now.day);
|
||||||
for (final step in steps) {
|
return _repository.getSteps(
|
||||||
final date = DateTime.fromMillisecondsSinceEpoch(step.occurredAt);
|
identificator: identificator,
|
||||||
final dayKey = DateTime(date.year, date.month, date.day);
|
queryParameters: HealthQueryBuilder.build(
|
||||||
groups[dayKey] = (groups[dayKey] ?? 0) + step.steps;
|
orderDirection: OrderDirection.asc,
|
||||||
}
|
filters: HealthQueryBuilder.timeRangeFilters(
|
||||||
|
start: todayStart,
|
||||||
final sorted = groups.entries.toList()
|
end: now,
|
||||||
..sort((a, b) => a.key.compareTo(b.key));
|
),
|
||||||
|
),
|
||||||
return sorted
|
|
||||||
.map((e) => DailySteps(date: e.key, totalSteps: e.value))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<DailySteps> _mergeHistory(
|
|
||||||
List<DailySteps> existing,
|
|
||||||
List<DailySteps> newItems,
|
|
||||||
) {
|
|
||||||
final Map<DateTime, int> merged = {
|
|
||||||
for (final d in existing) d.date: d.totalSteps,
|
|
||||||
};
|
|
||||||
for (final d in newItems) {
|
|
||||||
merged[d.date] = (merged[d.date] ?? 0) + d.totalSteps;
|
|
||||||
}
|
|
||||||
|
|
||||||
final sorted = merged.entries.toList()
|
|
||||||
..sort((a, b) => b.key.compareTo(a.key));
|
|
||||||
|
|
||||||
return sorted
|
|
||||||
.map((e) => DailySteps(date: e.key, totalSteps: e.value))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
StepsStats _computeStats(List<DailySteps> daily) {
|
|
||||||
if (daily.isEmpty) return const StepsStats();
|
|
||||||
|
|
||||||
final total = daily.fold(0, (sum, d) => sum + d.totalSteps);
|
|
||||||
final best = daily.reduce((a, b) => a.totalSteps >= b.totalSteps ? a : b);
|
|
||||||
|
|
||||||
return StepsStats(
|
|
||||||
avgPerDay: (total / daily.length).round(),
|
|
||||||
total: total,
|
|
||||||
bestDaySteps: best.totalSteps,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<HealthFilter>? _buildTimeFilters() {
|
(DateTime, DateTime) _resolveRange() => resolveRange(
|
||||||
final range = _getTimeRange();
|
state.timeRange,
|
||||||
if (range == null) return null;
|
now: DateTime.now(),
|
||||||
|
customStart: state.customStart,
|
||||||
final (start, end) = range;
|
customEnd: state.customEnd,
|
||||||
return HealthQueryBuilder.timeRangeFilters(start: start, end: end);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
(DateTime, DateTime)? _getTimeRange() {
|
|
||||||
final now = DateTime.now();
|
|
||||||
final todayStart = DateTime(now.year, now.month, now.day);
|
|
||||||
|
|
||||||
switch (state.timeRange) {
|
|
||||||
case TimeRange.today:
|
|
||||||
return (todayStart, now);
|
|
||||||
case TimeRange.sevenDays:
|
|
||||||
return (todayStart.subtract(const Duration(days: 6)), now);
|
|
||||||
case TimeRange.thirtyDays:
|
|
||||||
return (todayStart.subtract(const Duration(days: 29)), now);
|
|
||||||
case TimeRange.custom:
|
|
||||||
if (state.customStart != null && state.customEnd != null) {
|
|
||||||
return (state.customStart!, state.customEnd!);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> togglePedometer({required bool enabled}) async {
|
Future<bool> togglePedometer({required bool enabled}) async {
|
||||||
final device = ref.read(selectedDeviceProvider).value;
|
final device = ref.read(selectedDeviceProvider).value;
|
||||||
@@ -290,7 +225,7 @@ class ActivityMeterViewModel extends Notifier<ActivityMeterViewState> {
|
|||||||
unawaited(_tracking.legacyDeviceActivityPedometerToggled(enabled));
|
unawaited(_tracking.legacyDeviceActivityPedometerToggled(enabled));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (_) {
|
||||||
if (!ref.mounted) return false;
|
if (!ref.mounted) return false;
|
||||||
state = state.copyWith(errorEvent: ActivityMeterErrorEvent.pedometer);
|
state = state.copyWith(errorEvent: ActivityMeterErrorEvent.pedometer);
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -12,12 +12,20 @@ abstract class DailySteps with _$DailySteps {
|
|||||||
_DailySteps;
|
_DailySteps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class HourlySteps with _$HourlySteps {
|
||||||
|
const factory HourlySteps({required int hour, required int totalSteps}) =
|
||||||
|
_HourlySteps;
|
||||||
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
abstract class StepsStats with _$StepsStats {
|
abstract class StepsStats with _$StepsStats {
|
||||||
const factory StepsStats({
|
const factory StepsStats({
|
||||||
@Default(0) int avgPerDay,
|
|
||||||
@Default(0) int total,
|
@Default(0) int total,
|
||||||
@Default(0) int bestDaySteps,
|
@Default(0) int avgPerDay,
|
||||||
|
@Default(0) int activeDays,
|
||||||
|
@Default(0) int rangeDays,
|
||||||
|
DailySteps? bestDay,
|
||||||
}) = _StepsStats;
|
}) = _StepsStats;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,6 +35,8 @@ abstract class ActivityMeterViewState with _$ActivityMeterViewState {
|
|||||||
@Default(0) int todayTotal,
|
@Default(0) int todayTotal,
|
||||||
@Default(8000) int dailyGoal,
|
@Default(8000) int dailyGoal,
|
||||||
@Default([]) List<DailySteps> chartData,
|
@Default([]) List<DailySteps> chartData,
|
||||||
|
@Default([]) List<HourlySteps> hourlyData,
|
||||||
|
@Default(0) int activeHoursToday,
|
||||||
@Default([]) List<DailySteps> historyData,
|
@Default([]) List<DailySteps> historyData,
|
||||||
@Default(1) int currentHistoryPage,
|
@Default(1) int currentHistoryPage,
|
||||||
@Default(false) bool hasMoreHistory,
|
@Default(false) bool hasMoreHistory,
|
||||||
@@ -34,8 +44,16 @@ abstract class ActivityMeterViewState with _$ActivityMeterViewState {
|
|||||||
@Default(TimeRange.today) TimeRange timeRange,
|
@Default(TimeRange.today) TimeRange timeRange,
|
||||||
DateTime? customStart,
|
DateTime? customStart,
|
||||||
DateTime? customEnd,
|
DateTime? customEnd,
|
||||||
|
DateTime? rangeStart,
|
||||||
|
DateTime? rangeEnd,
|
||||||
@Default(true) bool isLoading,
|
@Default(true) bool isLoading,
|
||||||
@Default(false) bool isLoadingMore,
|
@Default(false) bool isLoadingMore,
|
||||||
ActivityMeterErrorEvent? errorEvent,
|
ActivityMeterErrorEvent? errorEvent,
|
||||||
}) = _ActivityMeterViewState;
|
}) = _ActivityMeterViewState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension ActivityMeterViewStateX on ActivityMeterViewState {
|
||||||
|
bool get hasActivityData => timeRange == TimeRange.today
|
||||||
|
? hourlyData.any((h) => h.totalSteps > 0)
|
||||||
|
: stats.total > 0;
|
||||||
|
}
|
||||||
|
|||||||
@@ -269,12 +269,272 @@ as int,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$HourlySteps {
|
||||||
|
|
||||||
|
int get hour; int get totalSteps;
|
||||||
|
/// Create a copy of HourlySteps
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$HourlyStepsCopyWith<HourlySteps> get copyWith => _$HourlyStepsCopyWithImpl<HourlySteps>(this as HourlySteps, _$identity);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is HourlySteps&&(identical(other.hour, hour) || other.hour == hour)&&(identical(other.totalSteps, totalSteps) || other.totalSteps == totalSteps));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,hour,totalSteps);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'HourlySteps(hour: $hour, totalSteps: $totalSteps)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $HourlyStepsCopyWith<$Res> {
|
||||||
|
factory $HourlyStepsCopyWith(HourlySteps value, $Res Function(HourlySteps) _then) = _$HourlyStepsCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
int hour, int totalSteps
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$HourlyStepsCopyWithImpl<$Res>
|
||||||
|
implements $HourlyStepsCopyWith<$Res> {
|
||||||
|
_$HourlyStepsCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final HourlySteps _self;
|
||||||
|
final $Res Function(HourlySteps) _then;
|
||||||
|
|
||||||
|
/// Create a copy of HourlySteps
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? hour = null,Object? totalSteps = null,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
hour: null == hour ? _self.hour : hour // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,totalSteps: null == totalSteps ? _self.totalSteps : totalSteps // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Adds pattern-matching-related methods to [HourlySteps].
|
||||||
|
extension HourlyStepsPatterns on HourlySteps {
|
||||||
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _HourlySteps value)? $default,{required TResult orElse(),}){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _HourlySteps() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// Callbacks receives the raw object, upcasted.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case final Subclass2 value:
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _HourlySteps value) $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _HourlySteps():
|
||||||
|
return $default(_that);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _HourlySteps value)? $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _HourlySteps() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to an `orElse` callback.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int hour, int totalSteps)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _HourlySteps() when $default != null:
|
||||||
|
return $default(_that.hour,_that.totalSteps);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// As opposed to `map`, this offers destructuring.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case Subclass2(:final field2):
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int hour, int totalSteps) $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _HourlySteps():
|
||||||
|
return $default(_that.hour,_that.totalSteps);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to returning `null`
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int hour, int totalSteps)? $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _HourlySteps() when $default != null:
|
||||||
|
return $default(_that.hour,_that.totalSteps);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
|
||||||
|
class _HourlySteps implements HourlySteps {
|
||||||
|
const _HourlySteps({required this.hour, required this.totalSteps});
|
||||||
|
|
||||||
|
|
||||||
|
@override final int hour;
|
||||||
|
@override final int totalSteps;
|
||||||
|
|
||||||
|
/// Create a copy of HourlySteps
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$HourlyStepsCopyWith<_HourlySteps> get copyWith => __$HourlyStepsCopyWithImpl<_HourlySteps>(this, _$identity);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _HourlySteps&&(identical(other.hour, hour) || other.hour == hour)&&(identical(other.totalSteps, totalSteps) || other.totalSteps == totalSteps));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,hour,totalSteps);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'HourlySteps(hour: $hour, totalSteps: $totalSteps)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$HourlyStepsCopyWith<$Res> implements $HourlyStepsCopyWith<$Res> {
|
||||||
|
factory _$HourlyStepsCopyWith(_HourlySteps value, $Res Function(_HourlySteps) _then) = __$HourlyStepsCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
int hour, int totalSteps
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$HourlyStepsCopyWithImpl<$Res>
|
||||||
|
implements _$HourlyStepsCopyWith<$Res> {
|
||||||
|
__$HourlyStepsCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _HourlySteps _self;
|
||||||
|
final $Res Function(_HourlySteps) _then;
|
||||||
|
|
||||||
|
/// Create a copy of HourlySteps
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? hour = null,Object? totalSteps = null,}) {
|
||||||
|
return _then(_HourlySteps(
|
||||||
|
hour: null == hour ? _self.hour : hour // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,totalSteps: null == totalSteps ? _self.totalSteps : totalSteps // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$StepsStats {
|
mixin _$StepsStats {
|
||||||
|
|
||||||
int get avgPerDay; int get total; int get bestDaySteps;
|
int get total; int get avgPerDay; int get activeDays; int get rangeDays; DailySteps? get bestDay;
|
||||||
/// Create a copy of StepsStats
|
/// Create a copy of StepsStats
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -285,16 +545,16 @@ $StepsStatsCopyWith<StepsStats> get copyWith => _$StepsStatsCopyWithImpl<StepsSt
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is StepsStats&&(identical(other.avgPerDay, avgPerDay) || other.avgPerDay == avgPerDay)&&(identical(other.total, total) || other.total == total)&&(identical(other.bestDaySteps, bestDaySteps) || other.bestDaySteps == bestDaySteps));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is StepsStats&&(identical(other.total, total) || other.total == total)&&(identical(other.avgPerDay, avgPerDay) || other.avgPerDay == avgPerDay)&&(identical(other.activeDays, activeDays) || other.activeDays == activeDays)&&(identical(other.rangeDays, rangeDays) || other.rangeDays == rangeDays)&&(identical(other.bestDay, bestDay) || other.bestDay == bestDay));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,avgPerDay,total,bestDaySteps);
|
int get hashCode => Object.hash(runtimeType,total,avgPerDay,activeDays,rangeDays,bestDay);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'StepsStats(avgPerDay: $avgPerDay, total: $total, bestDaySteps: $bestDaySteps)';
|
return 'StepsStats(total: $total, avgPerDay: $avgPerDay, activeDays: $activeDays, rangeDays: $rangeDays, bestDay: $bestDay)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -305,11 +565,11 @@ abstract mixin class $StepsStatsCopyWith<$Res> {
|
|||||||
factory $StepsStatsCopyWith(StepsStats value, $Res Function(StepsStats) _then) = _$StepsStatsCopyWithImpl;
|
factory $StepsStatsCopyWith(StepsStats value, $Res Function(StepsStats) _then) = _$StepsStatsCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
int avgPerDay, int total, int bestDaySteps
|
int total, int avgPerDay, int activeDays, int rangeDays, DailySteps? bestDay
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
$DailyStepsCopyWith<$Res>? get bestDay;
|
||||||
|
|
||||||
}
|
}
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@@ -322,15 +582,29 @@ class _$StepsStatsCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of StepsStats
|
/// Create a copy of StepsStats
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? avgPerDay = null,Object? total = null,Object? bestDaySteps = null,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? total = null,Object? avgPerDay = null,Object? activeDays = null,Object? rangeDays = null,Object? bestDay = freezed,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
avgPerDay: null == avgPerDay ? _self.avgPerDay : avgPerDay // ignore: cast_nullable_to_non_nullable
|
total: null == total ? _self.total : total // ignore: cast_nullable_to_non_nullable
|
||||||
as int,total: null == total ? _self.total : total // ignore: cast_nullable_to_non_nullable
|
as int,avgPerDay: null == avgPerDay ? _self.avgPerDay : avgPerDay // ignore: cast_nullable_to_non_nullable
|
||||||
as int,bestDaySteps: null == bestDaySteps ? _self.bestDaySteps : bestDaySteps // ignore: cast_nullable_to_non_nullable
|
as int,activeDays: null == activeDays ? _self.activeDays : activeDays // ignore: cast_nullable_to_non_nullable
|
||||||
as int,
|
as int,rangeDays: null == rangeDays ? _self.rangeDays : rangeDays // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,bestDay: freezed == bestDay ? _self.bestDay : bestDay // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DailySteps?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
/// Create a copy of StepsStats
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$DailyStepsCopyWith<$Res>? get bestDay {
|
||||||
|
if (_self.bestDay == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $DailyStepsCopyWith<$Res>(_self.bestDay!, (value) {
|
||||||
|
return _then(_self.copyWith(bestDay: value));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -412,10 +686,10 @@ return $default(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int avgPerDay, int total, int bestDaySteps)? $default,{required TResult orElse(),}) {final _that = this;
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int total, int avgPerDay, int activeDays, int rangeDays, DailySteps? bestDay)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _StepsStats() when $default != null:
|
case _StepsStats() when $default != null:
|
||||||
return $default(_that.avgPerDay,_that.total,_that.bestDaySteps);case _:
|
return $default(_that.total,_that.avgPerDay,_that.activeDays,_that.rangeDays,_that.bestDay);case _:
|
||||||
return orElse();
|
return orElse();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -433,10 +707,10 @@ return $default(_that.avgPerDay,_that.total,_that.bestDaySteps);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int avgPerDay, int total, int bestDaySteps) $default,) {final _that = this;
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int total, int avgPerDay, int activeDays, int rangeDays, DailySteps? bestDay) $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _StepsStats():
|
case _StepsStats():
|
||||||
return $default(_that.avgPerDay,_that.total,_that.bestDaySteps);case _:
|
return $default(_that.total,_that.avgPerDay,_that.activeDays,_that.rangeDays,_that.bestDay);case _:
|
||||||
throw StateError('Unexpected subclass');
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -453,10 +727,10 @@ return $default(_that.avgPerDay,_that.total,_that.bestDaySteps);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int avgPerDay, int total, int bestDaySteps)? $default,) {final _that = this;
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int total, int avgPerDay, int activeDays, int rangeDays, DailySteps? bestDay)? $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _StepsStats() when $default != null:
|
case _StepsStats() when $default != null:
|
||||||
return $default(_that.avgPerDay,_that.total,_that.bestDaySteps);case _:
|
return $default(_that.total,_that.avgPerDay,_that.activeDays,_that.rangeDays,_that.bestDay);case _:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -468,12 +742,14 @@ return $default(_that.avgPerDay,_that.total,_that.bestDaySteps);case _:
|
|||||||
|
|
||||||
|
|
||||||
class _StepsStats implements StepsStats {
|
class _StepsStats implements StepsStats {
|
||||||
const _StepsStats({this.avgPerDay = 0, this.total = 0, this.bestDaySteps = 0});
|
const _StepsStats({this.total = 0, this.avgPerDay = 0, this.activeDays = 0, this.rangeDays = 0, this.bestDay});
|
||||||
|
|
||||||
|
|
||||||
@override@JsonKey() final int avgPerDay;
|
|
||||||
@override@JsonKey() final int total;
|
@override@JsonKey() final int total;
|
||||||
@override@JsonKey() final int bestDaySteps;
|
@override@JsonKey() final int avgPerDay;
|
||||||
|
@override@JsonKey() final int activeDays;
|
||||||
|
@override@JsonKey() final int rangeDays;
|
||||||
|
@override final DailySteps? bestDay;
|
||||||
|
|
||||||
/// Create a copy of StepsStats
|
/// Create a copy of StepsStats
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@@ -485,16 +761,16 @@ _$StepsStatsCopyWith<_StepsStats> get copyWith => __$StepsStatsCopyWithImpl<_Ste
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _StepsStats&&(identical(other.avgPerDay, avgPerDay) || other.avgPerDay == avgPerDay)&&(identical(other.total, total) || other.total == total)&&(identical(other.bestDaySteps, bestDaySteps) || other.bestDaySteps == bestDaySteps));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _StepsStats&&(identical(other.total, total) || other.total == total)&&(identical(other.avgPerDay, avgPerDay) || other.avgPerDay == avgPerDay)&&(identical(other.activeDays, activeDays) || other.activeDays == activeDays)&&(identical(other.rangeDays, rangeDays) || other.rangeDays == rangeDays)&&(identical(other.bestDay, bestDay) || other.bestDay == bestDay));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,avgPerDay,total,bestDaySteps);
|
int get hashCode => Object.hash(runtimeType,total,avgPerDay,activeDays,rangeDays,bestDay);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'StepsStats(avgPerDay: $avgPerDay, total: $total, bestDaySteps: $bestDaySteps)';
|
return 'StepsStats(total: $total, avgPerDay: $avgPerDay, activeDays: $activeDays, rangeDays: $rangeDays, bestDay: $bestDay)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -505,11 +781,11 @@ abstract mixin class _$StepsStatsCopyWith<$Res> implements $StepsStatsCopyWith<$
|
|||||||
factory _$StepsStatsCopyWith(_StepsStats value, $Res Function(_StepsStats) _then) = __$StepsStatsCopyWithImpl;
|
factory _$StepsStatsCopyWith(_StepsStats value, $Res Function(_StepsStats) _then) = __$StepsStatsCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
int avgPerDay, int total, int bestDaySteps
|
int total, int avgPerDay, int activeDays, int rangeDays, DailySteps? bestDay
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@override $DailyStepsCopyWith<$Res>? get bestDay;
|
||||||
|
|
||||||
}
|
}
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@@ -522,22 +798,36 @@ class __$StepsStatsCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of StepsStats
|
/// Create a copy of StepsStats
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? avgPerDay = null,Object? total = null,Object? bestDaySteps = null,}) {
|
@override @pragma('vm:prefer-inline') $Res call({Object? total = null,Object? avgPerDay = null,Object? activeDays = null,Object? rangeDays = null,Object? bestDay = freezed,}) {
|
||||||
return _then(_StepsStats(
|
return _then(_StepsStats(
|
||||||
avgPerDay: null == avgPerDay ? _self.avgPerDay : avgPerDay // ignore: cast_nullable_to_non_nullable
|
total: null == total ? _self.total : total // ignore: cast_nullable_to_non_nullable
|
||||||
as int,total: null == total ? _self.total : total // ignore: cast_nullable_to_non_nullable
|
as int,avgPerDay: null == avgPerDay ? _self.avgPerDay : avgPerDay // ignore: cast_nullable_to_non_nullable
|
||||||
as int,bestDaySteps: null == bestDaySteps ? _self.bestDaySteps : bestDaySteps // ignore: cast_nullable_to_non_nullable
|
as int,activeDays: null == activeDays ? _self.activeDays : activeDays // ignore: cast_nullable_to_non_nullable
|
||||||
as int,
|
as int,rangeDays: null == rangeDays ? _self.rangeDays : rangeDays // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,bestDay: freezed == bestDay ? _self.bestDay : bestDay // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DailySteps?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a copy of StepsStats
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$DailyStepsCopyWith<$Res>? get bestDay {
|
||||||
|
if (_self.bestDay == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $DailyStepsCopyWith<$Res>(_self.bestDay!, (value) {
|
||||||
|
return _then(_self.copyWith(bestDay: value));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$ActivityMeterViewState {
|
mixin _$ActivityMeterViewState {
|
||||||
|
|
||||||
int get todayTotal; int get dailyGoal; List<DailySteps> get chartData; List<DailySteps> get historyData; int get currentHistoryPage; bool get hasMoreHistory; StepsStats get stats; TimeRange get timeRange; DateTime? get customStart; DateTime? get customEnd; bool get isLoading; bool get isLoadingMore; ActivityMeterErrorEvent? get errorEvent;
|
int get todayTotal; int get dailyGoal; List<DailySteps> get chartData; List<HourlySteps> get hourlyData; int get activeHoursToday; List<DailySteps> get historyData; int get currentHistoryPage; bool get hasMoreHistory; StepsStats get stats; TimeRange get timeRange; DateTime? get customStart; DateTime? get customEnd; DateTime? get rangeStart; DateTime? get rangeEnd; bool get isLoading; bool get isLoadingMore; ActivityMeterErrorEvent? get errorEvent;
|
||||||
/// Create a copy of ActivityMeterViewState
|
/// Create a copy of ActivityMeterViewState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -548,16 +838,16 @@ $ActivityMeterViewStateCopyWith<ActivityMeterViewState> get copyWith => _$Activi
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is ActivityMeterViewState&&(identical(other.todayTotal, todayTotal) || other.todayTotal == todayTotal)&&(identical(other.dailyGoal, dailyGoal) || other.dailyGoal == dailyGoal)&&const DeepCollectionEquality().equals(other.chartData, chartData)&&const DeepCollectionEquality().equals(other.historyData, historyData)&&(identical(other.currentHistoryPage, currentHistoryPage) || other.currentHistoryPage == currentHistoryPage)&&(identical(other.hasMoreHistory, hasMoreHistory) || other.hasMoreHistory == hasMoreHistory)&&(identical(other.stats, stats) || other.stats == stats)&&(identical(other.timeRange, timeRange) || other.timeRange == timeRange)&&(identical(other.customStart, customStart) || other.customStart == customStart)&&(identical(other.customEnd, customEnd) || other.customEnd == customEnd)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isLoadingMore, isLoadingMore) || other.isLoadingMore == isLoadingMore)&&(identical(other.errorEvent, errorEvent) || other.errorEvent == errorEvent));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is ActivityMeterViewState&&(identical(other.todayTotal, todayTotal) || other.todayTotal == todayTotal)&&(identical(other.dailyGoal, dailyGoal) || other.dailyGoal == dailyGoal)&&const DeepCollectionEquality().equals(other.chartData, chartData)&&const DeepCollectionEquality().equals(other.hourlyData, hourlyData)&&(identical(other.activeHoursToday, activeHoursToday) || other.activeHoursToday == activeHoursToday)&&const DeepCollectionEquality().equals(other.historyData, historyData)&&(identical(other.currentHistoryPage, currentHistoryPage) || other.currentHistoryPage == currentHistoryPage)&&(identical(other.hasMoreHistory, hasMoreHistory) || other.hasMoreHistory == hasMoreHistory)&&(identical(other.stats, stats) || other.stats == stats)&&(identical(other.timeRange, timeRange) || other.timeRange == timeRange)&&(identical(other.customStart, customStart) || other.customStart == customStart)&&(identical(other.customEnd, customEnd) || other.customEnd == customEnd)&&(identical(other.rangeStart, rangeStart) || other.rangeStart == rangeStart)&&(identical(other.rangeEnd, rangeEnd) || other.rangeEnd == rangeEnd)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isLoadingMore, isLoadingMore) || other.isLoadingMore == isLoadingMore)&&(identical(other.errorEvent, errorEvent) || other.errorEvent == errorEvent));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,todayTotal,dailyGoal,const DeepCollectionEquality().hash(chartData),const DeepCollectionEquality().hash(historyData),currentHistoryPage,hasMoreHistory,stats,timeRange,customStart,customEnd,isLoading,isLoadingMore,errorEvent);
|
int get hashCode => Object.hash(runtimeType,todayTotal,dailyGoal,const DeepCollectionEquality().hash(chartData),const DeepCollectionEquality().hash(hourlyData),activeHoursToday,const DeepCollectionEquality().hash(historyData),currentHistoryPage,hasMoreHistory,stats,timeRange,customStart,customEnd,rangeStart,rangeEnd,isLoading,isLoadingMore,errorEvent);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'ActivityMeterViewState(todayTotal: $todayTotal, dailyGoal: $dailyGoal, chartData: $chartData, historyData: $historyData, currentHistoryPage: $currentHistoryPage, hasMoreHistory: $hasMoreHistory, stats: $stats, timeRange: $timeRange, customStart: $customStart, customEnd: $customEnd, isLoading: $isLoading, isLoadingMore: $isLoadingMore, errorEvent: $errorEvent)';
|
return 'ActivityMeterViewState(todayTotal: $todayTotal, dailyGoal: $dailyGoal, chartData: $chartData, hourlyData: $hourlyData, activeHoursToday: $activeHoursToday, historyData: $historyData, currentHistoryPage: $currentHistoryPage, hasMoreHistory: $hasMoreHistory, stats: $stats, timeRange: $timeRange, customStart: $customStart, customEnd: $customEnd, rangeStart: $rangeStart, rangeEnd: $rangeEnd, isLoading: $isLoading, isLoadingMore: $isLoadingMore, errorEvent: $errorEvent)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -568,7 +858,7 @@ abstract mixin class $ActivityMeterViewStateCopyWith<$Res> {
|
|||||||
factory $ActivityMeterViewStateCopyWith(ActivityMeterViewState value, $Res Function(ActivityMeterViewState) _then) = _$ActivityMeterViewStateCopyWithImpl;
|
factory $ActivityMeterViewStateCopyWith(ActivityMeterViewState value, $Res Function(ActivityMeterViewState) _then) = _$ActivityMeterViewStateCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
int todayTotal, int dailyGoal, List<DailySteps> chartData, List<DailySteps> historyData, int currentHistoryPage, bool hasMoreHistory, StepsStats stats, TimeRange timeRange, DateTime? customStart, DateTime? customEnd, bool isLoading, bool isLoadingMore, ActivityMeterErrorEvent? errorEvent
|
int todayTotal, int dailyGoal, List<DailySteps> chartData, List<HourlySteps> hourlyData, int activeHoursToday, List<DailySteps> historyData, int currentHistoryPage, bool hasMoreHistory, StepsStats stats, TimeRange timeRange, DateTime? customStart, DateTime? customEnd, DateTime? rangeStart, DateTime? rangeEnd, bool isLoading, bool isLoadingMore, ActivityMeterErrorEvent? errorEvent
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -585,18 +875,22 @@ class _$ActivityMeterViewStateCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of ActivityMeterViewState
|
/// Create a copy of ActivityMeterViewState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? todayTotal = null,Object? dailyGoal = null,Object? chartData = null,Object? historyData = null,Object? currentHistoryPage = null,Object? hasMoreHistory = null,Object? stats = null,Object? timeRange = null,Object? customStart = freezed,Object? customEnd = freezed,Object? isLoading = null,Object? isLoadingMore = null,Object? errorEvent = freezed,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? todayTotal = null,Object? dailyGoal = null,Object? chartData = null,Object? hourlyData = null,Object? activeHoursToday = null,Object? historyData = null,Object? currentHistoryPage = null,Object? hasMoreHistory = null,Object? stats = null,Object? timeRange = null,Object? customStart = freezed,Object? customEnd = freezed,Object? rangeStart = freezed,Object? rangeEnd = freezed,Object? isLoading = null,Object? isLoadingMore = null,Object? errorEvent = freezed,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
todayTotal: null == todayTotal ? _self.todayTotal : todayTotal // ignore: cast_nullable_to_non_nullable
|
todayTotal: null == todayTotal ? _self.todayTotal : todayTotal // ignore: cast_nullable_to_non_nullable
|
||||||
as int,dailyGoal: null == dailyGoal ? _self.dailyGoal : dailyGoal // ignore: cast_nullable_to_non_nullable
|
as int,dailyGoal: null == dailyGoal ? _self.dailyGoal : dailyGoal // ignore: cast_nullable_to_non_nullable
|
||||||
as int,chartData: null == chartData ? _self.chartData : chartData // ignore: cast_nullable_to_non_nullable
|
as int,chartData: null == chartData ? _self.chartData : chartData // ignore: cast_nullable_to_non_nullable
|
||||||
as List<DailySteps>,historyData: null == historyData ? _self.historyData : historyData // ignore: cast_nullable_to_non_nullable
|
as List<DailySteps>,hourlyData: null == hourlyData ? _self.hourlyData : hourlyData // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<HourlySteps>,activeHoursToday: null == activeHoursToday ? _self.activeHoursToday : activeHoursToday // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,historyData: null == historyData ? _self.historyData : historyData // ignore: cast_nullable_to_non_nullable
|
||||||
as List<DailySteps>,currentHistoryPage: null == currentHistoryPage ? _self.currentHistoryPage : currentHistoryPage // ignore: cast_nullable_to_non_nullable
|
as List<DailySteps>,currentHistoryPage: null == currentHistoryPage ? _self.currentHistoryPage : currentHistoryPage // ignore: cast_nullable_to_non_nullable
|
||||||
as int,hasMoreHistory: null == hasMoreHistory ? _self.hasMoreHistory : hasMoreHistory // ignore: cast_nullable_to_non_nullable
|
as int,hasMoreHistory: null == hasMoreHistory ? _self.hasMoreHistory : hasMoreHistory // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,stats: null == stats ? _self.stats : stats // ignore: cast_nullable_to_non_nullable
|
as bool,stats: null == stats ? _self.stats : stats // ignore: cast_nullable_to_non_nullable
|
||||||
as StepsStats,timeRange: null == timeRange ? _self.timeRange : timeRange // ignore: cast_nullable_to_non_nullable
|
as StepsStats,timeRange: null == timeRange ? _self.timeRange : timeRange // ignore: cast_nullable_to_non_nullable
|
||||||
as TimeRange,customStart: freezed == customStart ? _self.customStart : customStart // ignore: cast_nullable_to_non_nullable
|
as TimeRange,customStart: freezed == customStart ? _self.customStart : customStart // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime?,customEnd: freezed == customEnd ? _self.customEnd : customEnd // ignore: cast_nullable_to_non_nullable
|
as DateTime?,customEnd: freezed == customEnd ? _self.customEnd : customEnd // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,rangeStart: freezed == rangeStart ? _self.rangeStart : rangeStart // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,rangeEnd: freezed == rangeEnd ? _self.rangeEnd : rangeEnd // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime?,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
|
as DateTime?,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,isLoadingMore: null == isLoadingMore ? _self.isLoadingMore : isLoadingMore // ignore: cast_nullable_to_non_nullable
|
as bool,isLoadingMore: null == isLoadingMore ? _self.isLoadingMore : isLoadingMore // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,errorEvent: freezed == errorEvent ? _self.errorEvent : errorEvent // ignore: cast_nullable_to_non_nullable
|
as bool,errorEvent: freezed == errorEvent ? _self.errorEvent : errorEvent // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -694,10 +988,10 @@ return $default(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int todayTotal, int dailyGoal, List<DailySteps> chartData, List<DailySteps> historyData, int currentHistoryPage, bool hasMoreHistory, StepsStats stats, TimeRange timeRange, DateTime? customStart, DateTime? customEnd, bool isLoading, bool isLoadingMore, ActivityMeterErrorEvent? errorEvent)? $default,{required TResult orElse(),}) {final _that = this;
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int todayTotal, int dailyGoal, List<DailySteps> chartData, List<HourlySteps> hourlyData, int activeHoursToday, List<DailySteps> historyData, int currentHistoryPage, bool hasMoreHistory, StepsStats stats, TimeRange timeRange, DateTime? customStart, DateTime? customEnd, DateTime? rangeStart, DateTime? rangeEnd, bool isLoading, bool isLoadingMore, ActivityMeterErrorEvent? errorEvent)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _ActivityMeterViewState() when $default != null:
|
case _ActivityMeterViewState() when $default != null:
|
||||||
return $default(_that.todayTotal,_that.dailyGoal,_that.chartData,_that.historyData,_that.currentHistoryPage,_that.hasMoreHistory,_that.stats,_that.timeRange,_that.customStart,_that.customEnd,_that.isLoading,_that.isLoadingMore,_that.errorEvent);case _:
|
return $default(_that.todayTotal,_that.dailyGoal,_that.chartData,_that.hourlyData,_that.activeHoursToday,_that.historyData,_that.currentHistoryPage,_that.hasMoreHistory,_that.stats,_that.timeRange,_that.customStart,_that.customEnd,_that.rangeStart,_that.rangeEnd,_that.isLoading,_that.isLoadingMore,_that.errorEvent);case _:
|
||||||
return orElse();
|
return orElse();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -715,10 +1009,10 @@ return $default(_that.todayTotal,_that.dailyGoal,_that.chartData,_that.historyDa
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int todayTotal, int dailyGoal, List<DailySteps> chartData, List<DailySteps> historyData, int currentHistoryPage, bool hasMoreHistory, StepsStats stats, TimeRange timeRange, DateTime? customStart, DateTime? customEnd, bool isLoading, bool isLoadingMore, ActivityMeterErrorEvent? errorEvent) $default,) {final _that = this;
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int todayTotal, int dailyGoal, List<DailySteps> chartData, List<HourlySteps> hourlyData, int activeHoursToday, List<DailySteps> historyData, int currentHistoryPage, bool hasMoreHistory, StepsStats stats, TimeRange timeRange, DateTime? customStart, DateTime? customEnd, DateTime? rangeStart, DateTime? rangeEnd, bool isLoading, bool isLoadingMore, ActivityMeterErrorEvent? errorEvent) $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _ActivityMeterViewState():
|
case _ActivityMeterViewState():
|
||||||
return $default(_that.todayTotal,_that.dailyGoal,_that.chartData,_that.historyData,_that.currentHistoryPage,_that.hasMoreHistory,_that.stats,_that.timeRange,_that.customStart,_that.customEnd,_that.isLoading,_that.isLoadingMore,_that.errorEvent);case _:
|
return $default(_that.todayTotal,_that.dailyGoal,_that.chartData,_that.hourlyData,_that.activeHoursToday,_that.historyData,_that.currentHistoryPage,_that.hasMoreHistory,_that.stats,_that.timeRange,_that.customStart,_that.customEnd,_that.rangeStart,_that.rangeEnd,_that.isLoading,_that.isLoadingMore,_that.errorEvent);case _:
|
||||||
throw StateError('Unexpected subclass');
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -735,10 +1029,10 @@ return $default(_that.todayTotal,_that.dailyGoal,_that.chartData,_that.historyDa
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int todayTotal, int dailyGoal, List<DailySteps> chartData, List<DailySteps> historyData, int currentHistoryPage, bool hasMoreHistory, StepsStats stats, TimeRange timeRange, DateTime? customStart, DateTime? customEnd, bool isLoading, bool isLoadingMore, ActivityMeterErrorEvent? errorEvent)? $default,) {final _that = this;
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int todayTotal, int dailyGoal, List<DailySteps> chartData, List<HourlySteps> hourlyData, int activeHoursToday, List<DailySteps> historyData, int currentHistoryPage, bool hasMoreHistory, StepsStats stats, TimeRange timeRange, DateTime? customStart, DateTime? customEnd, DateTime? rangeStart, DateTime? rangeEnd, bool isLoading, bool isLoadingMore, ActivityMeterErrorEvent? errorEvent)? $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _ActivityMeterViewState() when $default != null:
|
case _ActivityMeterViewState() when $default != null:
|
||||||
return $default(_that.todayTotal,_that.dailyGoal,_that.chartData,_that.historyData,_that.currentHistoryPage,_that.hasMoreHistory,_that.stats,_that.timeRange,_that.customStart,_that.customEnd,_that.isLoading,_that.isLoadingMore,_that.errorEvent);case _:
|
return $default(_that.todayTotal,_that.dailyGoal,_that.chartData,_that.hourlyData,_that.activeHoursToday,_that.historyData,_that.currentHistoryPage,_that.hasMoreHistory,_that.stats,_that.timeRange,_that.customStart,_that.customEnd,_that.rangeStart,_that.rangeEnd,_that.isLoading,_that.isLoadingMore,_that.errorEvent);case _:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -750,7 +1044,7 @@ return $default(_that.todayTotal,_that.dailyGoal,_that.chartData,_that.historyDa
|
|||||||
|
|
||||||
|
|
||||||
class _ActivityMeterViewState implements ActivityMeterViewState {
|
class _ActivityMeterViewState implements ActivityMeterViewState {
|
||||||
const _ActivityMeterViewState({this.todayTotal = 0, this.dailyGoal = 8000, final List<DailySteps> chartData = const [], final List<DailySteps> historyData = const [], this.currentHistoryPage = 1, this.hasMoreHistory = false, this.stats = const StepsStats(), this.timeRange = TimeRange.today, this.customStart, this.customEnd, this.isLoading = true, this.isLoadingMore = false, this.errorEvent}): _chartData = chartData,_historyData = historyData;
|
const _ActivityMeterViewState({this.todayTotal = 0, this.dailyGoal = 8000, final List<DailySteps> chartData = const [], final List<HourlySteps> hourlyData = const [], this.activeHoursToday = 0, final List<DailySteps> historyData = const [], this.currentHistoryPage = 1, this.hasMoreHistory = false, this.stats = const StepsStats(), this.timeRange = TimeRange.today, this.customStart, this.customEnd, this.rangeStart, this.rangeEnd, this.isLoading = true, this.isLoadingMore = false, this.errorEvent}): _chartData = chartData,_hourlyData = hourlyData,_historyData = historyData;
|
||||||
|
|
||||||
|
|
||||||
@override@JsonKey() final int todayTotal;
|
@override@JsonKey() final int todayTotal;
|
||||||
@@ -762,6 +1056,14 @@ class _ActivityMeterViewState implements ActivityMeterViewState {
|
|||||||
return EqualUnmodifiableListView(_chartData);
|
return EqualUnmodifiableListView(_chartData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final List<HourlySteps> _hourlyData;
|
||||||
|
@override@JsonKey() List<HourlySteps> get hourlyData {
|
||||||
|
if (_hourlyData is EqualUnmodifiableListView) return _hourlyData;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(_hourlyData);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override@JsonKey() final int activeHoursToday;
|
||||||
final List<DailySteps> _historyData;
|
final List<DailySteps> _historyData;
|
||||||
@override@JsonKey() List<DailySteps> get historyData {
|
@override@JsonKey() List<DailySteps> get historyData {
|
||||||
if (_historyData is EqualUnmodifiableListView) return _historyData;
|
if (_historyData is EqualUnmodifiableListView) return _historyData;
|
||||||
@@ -775,6 +1077,8 @@ class _ActivityMeterViewState implements ActivityMeterViewState {
|
|||||||
@override@JsonKey() final TimeRange timeRange;
|
@override@JsonKey() final TimeRange timeRange;
|
||||||
@override final DateTime? customStart;
|
@override final DateTime? customStart;
|
||||||
@override final DateTime? customEnd;
|
@override final DateTime? customEnd;
|
||||||
|
@override final DateTime? rangeStart;
|
||||||
|
@override final DateTime? rangeEnd;
|
||||||
@override@JsonKey() final bool isLoading;
|
@override@JsonKey() final bool isLoading;
|
||||||
@override@JsonKey() final bool isLoadingMore;
|
@override@JsonKey() final bool isLoadingMore;
|
||||||
@override final ActivityMeterErrorEvent? errorEvent;
|
@override final ActivityMeterErrorEvent? errorEvent;
|
||||||
@@ -789,16 +1093,16 @@ _$ActivityMeterViewStateCopyWith<_ActivityMeterViewState> get copyWith => __$Act
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ActivityMeterViewState&&(identical(other.todayTotal, todayTotal) || other.todayTotal == todayTotal)&&(identical(other.dailyGoal, dailyGoal) || other.dailyGoal == dailyGoal)&&const DeepCollectionEquality().equals(other._chartData, _chartData)&&const DeepCollectionEquality().equals(other._historyData, _historyData)&&(identical(other.currentHistoryPage, currentHistoryPage) || other.currentHistoryPage == currentHistoryPage)&&(identical(other.hasMoreHistory, hasMoreHistory) || other.hasMoreHistory == hasMoreHistory)&&(identical(other.stats, stats) || other.stats == stats)&&(identical(other.timeRange, timeRange) || other.timeRange == timeRange)&&(identical(other.customStart, customStart) || other.customStart == customStart)&&(identical(other.customEnd, customEnd) || other.customEnd == customEnd)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isLoadingMore, isLoadingMore) || other.isLoadingMore == isLoadingMore)&&(identical(other.errorEvent, errorEvent) || other.errorEvent == errorEvent));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ActivityMeterViewState&&(identical(other.todayTotal, todayTotal) || other.todayTotal == todayTotal)&&(identical(other.dailyGoal, dailyGoal) || other.dailyGoal == dailyGoal)&&const DeepCollectionEquality().equals(other._chartData, _chartData)&&const DeepCollectionEquality().equals(other._hourlyData, _hourlyData)&&(identical(other.activeHoursToday, activeHoursToday) || other.activeHoursToday == activeHoursToday)&&const DeepCollectionEquality().equals(other._historyData, _historyData)&&(identical(other.currentHistoryPage, currentHistoryPage) || other.currentHistoryPage == currentHistoryPage)&&(identical(other.hasMoreHistory, hasMoreHistory) || other.hasMoreHistory == hasMoreHistory)&&(identical(other.stats, stats) || other.stats == stats)&&(identical(other.timeRange, timeRange) || other.timeRange == timeRange)&&(identical(other.customStart, customStart) || other.customStart == customStart)&&(identical(other.customEnd, customEnd) || other.customEnd == customEnd)&&(identical(other.rangeStart, rangeStart) || other.rangeStart == rangeStart)&&(identical(other.rangeEnd, rangeEnd) || other.rangeEnd == rangeEnd)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isLoadingMore, isLoadingMore) || other.isLoadingMore == isLoadingMore)&&(identical(other.errorEvent, errorEvent) || other.errorEvent == errorEvent));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,todayTotal,dailyGoal,const DeepCollectionEquality().hash(_chartData),const DeepCollectionEquality().hash(_historyData),currentHistoryPage,hasMoreHistory,stats,timeRange,customStart,customEnd,isLoading,isLoadingMore,errorEvent);
|
int get hashCode => Object.hash(runtimeType,todayTotal,dailyGoal,const DeepCollectionEquality().hash(_chartData),const DeepCollectionEquality().hash(_hourlyData),activeHoursToday,const DeepCollectionEquality().hash(_historyData),currentHistoryPage,hasMoreHistory,stats,timeRange,customStart,customEnd,rangeStart,rangeEnd,isLoading,isLoadingMore,errorEvent);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'ActivityMeterViewState(todayTotal: $todayTotal, dailyGoal: $dailyGoal, chartData: $chartData, historyData: $historyData, currentHistoryPage: $currentHistoryPage, hasMoreHistory: $hasMoreHistory, stats: $stats, timeRange: $timeRange, customStart: $customStart, customEnd: $customEnd, isLoading: $isLoading, isLoadingMore: $isLoadingMore, errorEvent: $errorEvent)';
|
return 'ActivityMeterViewState(todayTotal: $todayTotal, dailyGoal: $dailyGoal, chartData: $chartData, hourlyData: $hourlyData, activeHoursToday: $activeHoursToday, historyData: $historyData, currentHistoryPage: $currentHistoryPage, hasMoreHistory: $hasMoreHistory, stats: $stats, timeRange: $timeRange, customStart: $customStart, customEnd: $customEnd, rangeStart: $rangeStart, rangeEnd: $rangeEnd, isLoading: $isLoading, isLoadingMore: $isLoadingMore, errorEvent: $errorEvent)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -809,7 +1113,7 @@ abstract mixin class _$ActivityMeterViewStateCopyWith<$Res> implements $Activity
|
|||||||
factory _$ActivityMeterViewStateCopyWith(_ActivityMeterViewState value, $Res Function(_ActivityMeterViewState) _then) = __$ActivityMeterViewStateCopyWithImpl;
|
factory _$ActivityMeterViewStateCopyWith(_ActivityMeterViewState value, $Res Function(_ActivityMeterViewState) _then) = __$ActivityMeterViewStateCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
int todayTotal, int dailyGoal, List<DailySteps> chartData, List<DailySteps> historyData, int currentHistoryPage, bool hasMoreHistory, StepsStats stats, TimeRange timeRange, DateTime? customStart, DateTime? customEnd, bool isLoading, bool isLoadingMore, ActivityMeterErrorEvent? errorEvent
|
int todayTotal, int dailyGoal, List<DailySteps> chartData, List<HourlySteps> hourlyData, int activeHoursToday, List<DailySteps> historyData, int currentHistoryPage, bool hasMoreHistory, StepsStats stats, TimeRange timeRange, DateTime? customStart, DateTime? customEnd, DateTime? rangeStart, DateTime? rangeEnd, bool isLoading, bool isLoadingMore, ActivityMeterErrorEvent? errorEvent
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -826,18 +1130,22 @@ class __$ActivityMeterViewStateCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of ActivityMeterViewState
|
/// Create a copy of ActivityMeterViewState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? todayTotal = null,Object? dailyGoal = null,Object? chartData = null,Object? historyData = null,Object? currentHistoryPage = null,Object? hasMoreHistory = null,Object? stats = null,Object? timeRange = null,Object? customStart = freezed,Object? customEnd = freezed,Object? isLoading = null,Object? isLoadingMore = null,Object? errorEvent = freezed,}) {
|
@override @pragma('vm:prefer-inline') $Res call({Object? todayTotal = null,Object? dailyGoal = null,Object? chartData = null,Object? hourlyData = null,Object? activeHoursToday = null,Object? historyData = null,Object? currentHistoryPage = null,Object? hasMoreHistory = null,Object? stats = null,Object? timeRange = null,Object? customStart = freezed,Object? customEnd = freezed,Object? rangeStart = freezed,Object? rangeEnd = freezed,Object? isLoading = null,Object? isLoadingMore = null,Object? errorEvent = freezed,}) {
|
||||||
return _then(_ActivityMeterViewState(
|
return _then(_ActivityMeterViewState(
|
||||||
todayTotal: null == todayTotal ? _self.todayTotal : todayTotal // ignore: cast_nullable_to_non_nullable
|
todayTotal: null == todayTotal ? _self.todayTotal : todayTotal // ignore: cast_nullable_to_non_nullable
|
||||||
as int,dailyGoal: null == dailyGoal ? _self.dailyGoal : dailyGoal // ignore: cast_nullable_to_non_nullable
|
as int,dailyGoal: null == dailyGoal ? _self.dailyGoal : dailyGoal // ignore: cast_nullable_to_non_nullable
|
||||||
as int,chartData: null == chartData ? _self._chartData : chartData // ignore: cast_nullable_to_non_nullable
|
as int,chartData: null == chartData ? _self._chartData : chartData // ignore: cast_nullable_to_non_nullable
|
||||||
as List<DailySteps>,historyData: null == historyData ? _self._historyData : historyData // ignore: cast_nullable_to_non_nullable
|
as List<DailySteps>,hourlyData: null == hourlyData ? _self._hourlyData : hourlyData // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<HourlySteps>,activeHoursToday: null == activeHoursToday ? _self.activeHoursToday : activeHoursToday // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,historyData: null == historyData ? _self._historyData : historyData // ignore: cast_nullable_to_non_nullable
|
||||||
as List<DailySteps>,currentHistoryPage: null == currentHistoryPage ? _self.currentHistoryPage : currentHistoryPage // ignore: cast_nullable_to_non_nullable
|
as List<DailySteps>,currentHistoryPage: null == currentHistoryPage ? _self.currentHistoryPage : currentHistoryPage // ignore: cast_nullable_to_non_nullable
|
||||||
as int,hasMoreHistory: null == hasMoreHistory ? _self.hasMoreHistory : hasMoreHistory // ignore: cast_nullable_to_non_nullable
|
as int,hasMoreHistory: null == hasMoreHistory ? _self.hasMoreHistory : hasMoreHistory // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,stats: null == stats ? _self.stats : stats // ignore: cast_nullable_to_non_nullable
|
as bool,stats: null == stats ? _self.stats : stats // ignore: cast_nullable_to_non_nullable
|
||||||
as StepsStats,timeRange: null == timeRange ? _self.timeRange : timeRange // ignore: cast_nullable_to_non_nullable
|
as StepsStats,timeRange: null == timeRange ? _self.timeRange : timeRange // ignore: cast_nullable_to_non_nullable
|
||||||
as TimeRange,customStart: freezed == customStart ? _self.customStart : customStart // ignore: cast_nullable_to_non_nullable
|
as TimeRange,customStart: freezed == customStart ? _self.customStart : customStart // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime?,customEnd: freezed == customEnd ? _self.customEnd : customEnd // ignore: cast_nullable_to_non_nullable
|
as DateTime?,customEnd: freezed == customEnd ? _self.customEnd : customEnd // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,rangeStart: freezed == rangeStart ? _self.rangeStart : rangeStart // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,rangeEnd: freezed == rangeEnd ? _self.rangeEnd : rangeEnd // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime?,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
|
as DateTime?,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,isLoadingMore: null == isLoadingMore ? _self.isLoadingMore : isLoadingMore // ignore: cast_nullable_to_non_nullable
|
as bool,isLoadingMore: null == isLoadingMore ? _self.isLoadingMore : isLoadingMore // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,errorEvent: freezed == errorEvent ? _self.errorEvent : errorEvent // ignore: cast_nullable_to_non_nullable
|
as bool,errorEvent: freezed == errorEvent ? _self.errorEvent : errorEvent // ignore: cast_nullable_to_non_nullable
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:utils/utils.dart';
|
||||||
|
|
||||||
|
class ActivityBarChartBase extends ConsumerWidget {
|
||||||
|
final List<BarChartGroupData> barGroups;
|
||||||
|
final AxisTitles bottomTitles;
|
||||||
|
final BarTouchTooltipData tooltip;
|
||||||
|
final double maxY;
|
||||||
|
final bool isEmpty;
|
||||||
|
|
||||||
|
const ActivityBarChartBase({
|
||||||
|
super.key,
|
||||||
|
required this.barGroups,
|
||||||
|
required this.bottomTitles,
|
||||||
|
required this.tooltip,
|
||||||
|
required this.maxY,
|
||||||
|
required this.isEmpty,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final theme = ref.watch(themePortProvider);
|
||||||
|
final height = SizeUtils.getByScreen<double>(small: 180, big: 160);
|
||||||
|
|
||||||
|
if (isEmpty) {
|
||||||
|
return SizedBox(
|
||||||
|
height: height,
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
'—',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: SizeUtils.getByScreen(small: 16, big: 14),
|
||||||
|
color: theme
|
||||||
|
.getColorFor(ThemeCode.textPrimary)
|
||||||
|
.withValues(alpha: 0.3),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final labelColor = theme
|
||||||
|
.getColorFor(ThemeCode.textPrimary)
|
||||||
|
.withValues(alpha: 0.4);
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: SizeUtils.getByScreen(small: 16, big: 14),
|
||||||
|
),
|
||||||
|
child: SizedBox(
|
||||||
|
height: height,
|
||||||
|
child: BarChart(
|
||||||
|
BarChartData(
|
||||||
|
maxY: maxY * 1.15,
|
||||||
|
barGroups: barGroups,
|
||||||
|
gridData: FlGridData(
|
||||||
|
show: true,
|
||||||
|
drawVerticalLine: false,
|
||||||
|
horizontalInterval: _computeInterval(maxY),
|
||||||
|
getDrawingHorizontalLine: (_) => FlLine(
|
||||||
|
color: theme
|
||||||
|
.getColorFor(ThemeCode.textPrimary)
|
||||||
|
.withValues(alpha: 0.08),
|
||||||
|
strokeWidth: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
titlesData: FlTitlesData(
|
||||||
|
leftTitles: AxisTitles(
|
||||||
|
sideTitles: SideTitles(
|
||||||
|
showTitles: true,
|
||||||
|
reservedSize: SizeUtils.getByScreen(small: 36, big: 32),
|
||||||
|
getTitlesWidget: (value, _) => Text(
|
||||||
|
_formatAxis(value),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: SizeUtils.getByScreen(small: 10, big: 9),
|
||||||
|
color: labelColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
bottomTitles: bottomTitles,
|
||||||
|
topTitles: const AxisTitles(
|
||||||
|
sideTitles: SideTitles(showTitles: false),
|
||||||
|
),
|
||||||
|
rightTitles: const AxisTitles(
|
||||||
|
sideTitles: SideTitles(showTitles: false),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
borderData: FlBorderData(show: false),
|
||||||
|
barTouchData: BarTouchData(touchTooltipData: tooltip),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static double _computeInterval(double maxY) {
|
||||||
|
if (maxY <= 0) return 1;
|
||||||
|
return (maxY / 4).ceilToDouble();
|
||||||
|
}
|
||||||
|
|
||||||
|
static String _formatAxis(double value) {
|
||||||
|
if (value >= 1000) return '${(value / 1000).toStringAsFixed(0)}k';
|
||||||
|
return value.toInt().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
static BarChartRodData buildRod({
|
||||||
|
required double toY,
|
||||||
|
required Color color,
|
||||||
|
required double width,
|
||||||
|
}) => BarChartRodData(
|
||||||
|
toY: toY,
|
||||||
|
color: color,
|
||||||
|
width: width,
|
||||||
|
borderRadius: BorderRadius.vertical(
|
||||||
|
top: Radius.circular(SizeUtils.getByScreen(small: 4, big: 3)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:sf_localizations/sf_localizations.dart';
|
||||||
|
import 'package:utils/utils.dart';
|
||||||
|
|
||||||
|
import '../../../../core/presentation/format_date.dart';
|
||||||
|
import '../format_steps.dart';
|
||||||
|
import '../state/activity_meter_view_state.dart';
|
||||||
|
import 'period_stats_cards.dart';
|
||||||
|
|
||||||
|
class TodayActivityFooter extends ConsumerWidget {
|
||||||
|
final int activeHours;
|
||||||
|
|
||||||
|
const TodayActivityFooter({super.key, required this.activeHours});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final theme = ref.watch(themePortProvider);
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: SizeUtils.getByScreen(small: 16, big: 14),
|
||||||
|
vertical: SizeUtils.getByScreen(small: 4, big: 3),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
context
|
||||||
|
.translate(I18n.activityMeterActiveHoursToday)
|
||||||
|
.replaceAll('{hours}', activeHours.toString()),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: SizeUtils.getByScreen(small: 13, big: 12),
|
||||||
|
color: theme
|
||||||
|
.getColorFor(ThemeCode.textPrimary)
|
||||||
|
.withValues(alpha: 0.6),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PeriodActivityFooter extends ConsumerWidget {
|
||||||
|
final StepsStats stats;
|
||||||
|
|
||||||
|
const PeriodActivityFooter({super.key, required this.stats});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final theme = ref.watch(themePortProvider);
|
||||||
|
final best = stats.bestDay;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
PeriodStatsCards(stats: stats),
|
||||||
|
if (best != null)
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: SizeUtils.getByScreen(small: 16, big: 14),
|
||||||
|
vertical: SizeUtils.getByScreen(small: 4, big: 3),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
context
|
||||||
|
.translate(I18n.activityMeterBestDay)
|
||||||
|
.replaceAll('{date}', formatDayHeader(context, best.date))
|
||||||
|
.replaceAll('{steps}', formatStepsNumber(best.totalSteps)),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: SizeUtils.getByScreen(small: 13, big: 12),
|
||||||
|
color: theme
|
||||||
|
.getColorFor(ThemeCode.textPrimary)
|
||||||
|
.withValues(alpha: 0.6),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:utils/utils.dart';
|
||||||
|
|
||||||
|
import '../state/activity_meter_view_state.dart';
|
||||||
|
import 'activity_bar_chart_base.dart';
|
||||||
|
|
||||||
|
class HourlyBarChart extends ConsumerWidget {
|
||||||
|
final List<HourlySteps> data;
|
||||||
|
|
||||||
|
const HourlyBarChart({super.key, required this.data});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final theme = ref.watch(themePortProvider);
|
||||||
|
final isEmpty = data.isEmpty || data.every((h) => h.totalSteps == 0);
|
||||||
|
final maxY = isEmpty
|
||||||
|
? 0.0
|
||||||
|
: data.map((h) => h.totalSteps.toDouble()).reduce((a, b) => a > b ? a : b);
|
||||||
|
|
||||||
|
final labelColor = theme
|
||||||
|
.getColorFor(ThemeCode.textPrimary)
|
||||||
|
.withValues(alpha: 0.4);
|
||||||
|
|
||||||
|
return ActivityBarChartBase(
|
||||||
|
isEmpty: isEmpty,
|
||||||
|
maxY: maxY,
|
||||||
|
barGroups: data
|
||||||
|
.map(
|
||||||
|
(bucket) => BarChartGroupData(
|
||||||
|
x: bucket.hour,
|
||||||
|
barRods: [
|
||||||
|
ActivityBarChartBase.buildRod(
|
||||||
|
toY: bucket.totalSteps.toDouble(),
|
||||||
|
color: theme.getColorFor(ThemeCode.legacyPrimary),
|
||||||
|
width: SizeUtils.getByScreen(small: 6, big: 5),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
bottomTitles: AxisTitles(
|
||||||
|
sideTitles: SideTitles(
|
||||||
|
showTitles: true,
|
||||||
|
interval: 3,
|
||||||
|
getTitlesWidget: (value, _) {
|
||||||
|
final hour = value.toInt();
|
||||||
|
if (hour < 0 || hour > 23 || hour % 6 != 0) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
return Text(
|
||||||
|
'${hour.toString().padLeft(2, '0')}h',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: SizeUtils.getByScreen(small: 9, big: 8),
|
||||||
|
color: labelColor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
tooltip: BarTouchTooltipData(
|
||||||
|
getTooltipColor: (_) =>
|
||||||
|
theme.getColorFor(ThemeCode.backgroundSecondary),
|
||||||
|
getTooltipItem: (group, _, rod, __) => BarTooltipItem(
|
||||||
|
'${group.x.toString().padLeft(2, '0')}h\n${rod.toY.toInt()}',
|
||||||
|
TextStyle(
|
||||||
|
fontSize: SizeUtils.getByScreen(small: 12, big: 11),
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: theme.getColorFor(ThemeCode.textPrimary),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:legacy_shared/legacy_shared.dart';
|
||||||
|
import 'package:sf_localizations/sf_localizations.dart';
|
||||||
|
import 'package:utils/utils.dart';
|
||||||
|
|
||||||
|
import '../state/activity_meter_view_model.dart';
|
||||||
|
|
||||||
|
class PedometerToggle extends ConsumerWidget {
|
||||||
|
const PedometerToggle({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final theme = ref.watch(themePortProvider);
|
||||||
|
final enabled = ref.watch(
|
||||||
|
selectedDeviceProvider.select(
|
||||||
|
(d) => d.value?.settings.pedometer ?? false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: SizeUtils.getByScreen(small: 16, big: 14),
|
||||||
|
vertical: SizeUtils.getByScreen(small: 8, big: 6),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
context.translate(I18n.activityMeterPedometer),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: SizeUtils.getByScreen(small: 14, big: 15),
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Switch.adaptive(
|
||||||
|
value: enabled,
|
||||||
|
activeTrackColor: theme.getColorFor(ThemeCode.legacyPrimary),
|
||||||
|
onChanged: (value) async {
|
||||||
|
final success = await ref
|
||||||
|
.read(activityMeterViewModelProvider.notifier)
|
||||||
|
.togglePedometer(enabled: value);
|
||||||
|
if (!context.mounted) return;
|
||||||
|
if (success) {
|
||||||
|
showTopSnackbar(
|
||||||
|
context,
|
||||||
|
message: context.translate(
|
||||||
|
value
|
||||||
|
? I18n.activityMeterPedometerEnabled
|
||||||
|
: I18n.activityMeterPedometerDisabled,
|
||||||
|
),
|
||||||
|
type: MessageType.success,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:sf_localizations/sf_localizations.dart';
|
||||||
|
import 'package:utils/utils.dart';
|
||||||
|
|
||||||
|
import '../format_steps.dart';
|
||||||
|
import '../state/activity_meter_view_state.dart';
|
||||||
|
|
||||||
|
class PeriodStatsCards extends StatelessWidget {
|
||||||
|
final StepsStats stats;
|
||||||
|
|
||||||
|
const PeriodStatsCards({super.key, required this.stats});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final hasData = stats.total > 0;
|
||||||
|
final gap = SizeUtils.getByScreen<double>(small: 8, big: 6);
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: SizeUtils.getByScreen(small: 16, big: 14),
|
||||||
|
vertical: SizeUtils.getByScreen(small: 8, big: 6),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: _StatCard(
|
||||||
|
label: context.translate(I18n.activityMeterTotalSteps),
|
||||||
|
value: hasData ? formatStepsNumber(stats.total) : '—',
|
||||||
|
unit: context.translate(I18n.unitSteps),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: gap),
|
||||||
|
Expanded(
|
||||||
|
child: _StatCard(
|
||||||
|
label: context.translate(I18n.activityMeterAverageDaily),
|
||||||
|
value: hasData ? formatStepsNumber(stats.avgPerDay) : '—',
|
||||||
|
unit: context.translate(I18n.unitStepsPerDay),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: gap),
|
||||||
|
Expanded(
|
||||||
|
child: _StatCard(
|
||||||
|
label: context.translate(I18n.activityMeterActiveDays),
|
||||||
|
value: stats.rangeDays > 0
|
||||||
|
? '${stats.activeDays}/${stats.rangeDays}'
|
||||||
|
: '—',
|
||||||
|
unit: context.translate(I18n.unitDays),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _StatCard extends ConsumerWidget {
|
||||||
|
final String label;
|
||||||
|
final String value;
|
||||||
|
final String unit;
|
||||||
|
|
||||||
|
const _StatCard({
|
||||||
|
required this.label,
|
||||||
|
required this.value,
|
||||||
|
required this.unit,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final theme = ref.watch(themePortProvider);
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: SizeUtils.getByScreen(small: 10, big: 8),
|
||||||
|
vertical: SizeUtils.getByScreen(small: 12, big: 10),
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.getColorFor(ThemeCode.backgroundSecondary),
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(SizeUtils.getByScreen(small: 12, big: 10)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
label.toUpperCase(),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: SizeUtils.getByScreen(small: 10, big: 9),
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
color: theme
|
||||||
|
.getColorFor(ThemeCode.textPrimary)
|
||||||
|
.withValues(alpha: 0.5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: SizeUtils.getByScreen(small: 6, big: 4)),
|
||||||
|
Text(
|
||||||
|
value,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: SizeUtils.getByScreen(small: 18, big: 16),
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: theme.getColorFor(ThemeCode.textPrimary),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: SizeUtils.getByScreen(small: 2, big: 1)),
|
||||||
|
Text(
|
||||||
|
unit,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: SizeUtils.getByScreen(small: 10, big: 9),
|
||||||
|
color: theme
|
||||||
|
.getColorFor(ThemeCode.textPrimary)
|
||||||
|
.withValues(alpha: 0.4),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:utils/utils.dart';
|
||||||
|
|
||||||
|
class SectionHeader extends ConsumerWidget {
|
||||||
|
final String title;
|
||||||
|
final String? subtitle;
|
||||||
|
|
||||||
|
const SectionHeader({super.key, required this.title, this.subtitle});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final theme = ref.watch(themePortProvider);
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
left: SizeUtils.getByScreen(small: 16, big: 14),
|
||||||
|
right: SizeUtils.getByScreen(small: 16, big: 14),
|
||||||
|
top: SizeUtils.getByScreen(small: 20, big: 18),
|
||||||
|
bottom: SizeUtils.getByScreen(small: 8, big: 6),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
title.toUpperCase(),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: SizeUtils.getByScreen(small: 12, big: 11),
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
letterSpacing: 1.2,
|
||||||
|
color: theme.getColorFor(ThemeCode.legacyPrimary),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (subtitle != null) ...[
|
||||||
|
SizedBox(height: SizeUtils.getByScreen(small: 4, big: 3)),
|
||||||
|
Text(
|
||||||
|
subtitle!,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: SizeUtils.getByScreen(small: 13, big: 12),
|
||||||
|
color: theme
|
||||||
|
.getColorFor(ThemeCode.textPrimary)
|
||||||
|
.withValues(alpha: 0.5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,156 +1,82 @@
|
|||||||
import 'package:design_system/design_system.dart';
|
import 'package:design_system/design_system.dart';
|
||||||
import 'package:fl_chart/fl_chart.dart';
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
import 'package:utils/utils.dart';
|
import 'package:utils/utils.dart';
|
||||||
|
|
||||||
import '../state/activity_meter_view_state.dart';
|
import '../state/activity_meter_view_state.dart';
|
||||||
|
import 'activity_bar_chart_base.dart';
|
||||||
|
|
||||||
class StepsBarChart extends StatelessWidget {
|
class StepsBarChart extends ConsumerWidget {
|
||||||
final List<DailySteps> data;
|
final List<DailySteps> data;
|
||||||
final ThemePort theme;
|
|
||||||
|
|
||||||
const StepsBarChart({super.key, required this.data, required this.theme});
|
const StepsBarChart({super.key, required this.data});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
if (data.isEmpty) {
|
final theme = ref.watch(themePortProvider);
|
||||||
return SizedBox(
|
final isEmpty = data.isEmpty;
|
||||||
height: SizeUtils.getByScreen(small: 180, big: 160),
|
final maxY = isEmpty
|
||||||
child: Center(
|
? 0.0
|
||||||
child: Text(
|
: data.map((d) => d.totalSteps.toDouble()).reduce((a, b) => a > b ? a : b);
|
||||||
'--',
|
|
||||||
style: TextStyle(
|
final locale = Localizations.localeOf(context).toString();
|
||||||
fontSize: SizeUtils.getByScreen(small: 16, big: 14),
|
final labelColor = theme
|
||||||
color: theme
|
.getColorFor(ThemeCode.textPrimary)
|
||||||
.getColorFor(ThemeCode.textPrimary)
|
.withValues(alpha: 0.4);
|
||||||
.withValues(alpha: 0.3),
|
|
||||||
|
return ActivityBarChartBase(
|
||||||
|
isEmpty: isEmpty,
|
||||||
|
maxY: maxY,
|
||||||
|
barGroups: data.asMap().entries.map((entry) {
|
||||||
|
return BarChartGroupData(
|
||||||
|
x: entry.key,
|
||||||
|
barRods: [
|
||||||
|
ActivityBarChartBase.buildRod(
|
||||||
|
toY: entry.value.totalSteps.toDouble(),
|
||||||
|
color: theme.getColorFor(ThemeCode.legacyPrimary),
|
||||||
|
width: _barWidth(data.length),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
bottomTitles: AxisTitles(
|
||||||
|
sideTitles: SideTitles(
|
||||||
|
showTitles: data.length <= 14,
|
||||||
|
getTitlesWidget: (value, _) {
|
||||||
|
final index = value.toInt();
|
||||||
|
if (index < 0 || index >= data.length) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
return Text(
|
||||||
|
DateFormat('d MMM', locale).format(data[index].date),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: SizeUtils.getByScreen(small: 9, big: 8),
|
||||||
|
color: labelColor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final maxY = data
|
|
||||||
.map((d) => d.totalSteps.toDouble())
|
|
||||||
.reduce((a, b) => a > b ? a : b);
|
|
||||||
|
|
||||||
return Padding(
|
|
||||||
padding: EdgeInsets.symmetric(
|
|
||||||
horizontal: SizeUtils.getByScreen(small: 16, big: 14),
|
|
||||||
),
|
),
|
||||||
child: SizedBox(
|
tooltip: BarTouchTooltipData(
|
||||||
height: SizeUtils.getByScreen(small: 180, big: 160),
|
getTooltipColor: (_) =>
|
||||||
child: BarChart(
|
theme.getColorFor(ThemeCode.backgroundSecondary),
|
||||||
BarChartData(
|
getTooltipItem: (group, _, rod, __) => BarTooltipItem(
|
||||||
maxY: maxY * 1.15,
|
'${DateFormat('d MMM y', locale).format(data[group.x].date)}\n${rod.toY.toInt()}',
|
||||||
barGroups: data.asMap().entries.map((entry) {
|
TextStyle(
|
||||||
return BarChartGroupData(
|
fontSize: SizeUtils.getByScreen(small: 12, big: 11),
|
||||||
x: entry.key,
|
fontWeight: FontWeight.w600,
|
||||||
barRods: [
|
color: theme.getColorFor(ThemeCode.textPrimary),
|
||||||
BarChartRodData(
|
|
||||||
toY: entry.value.totalSteps.toDouble(),
|
|
||||||
color: theme.getColorFor(ThemeCode.legacyPrimary),
|
|
||||||
width: _barWidth,
|
|
||||||
borderRadius: BorderRadius.vertical(
|
|
||||||
top: Radius.circular(
|
|
||||||
SizeUtils.getByScreen(small: 4, big: 3),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
gridData: FlGridData(
|
|
||||||
show: true,
|
|
||||||
drawVerticalLine: false,
|
|
||||||
horizontalInterval: _computeInterval(maxY),
|
|
||||||
getDrawingHorizontalLine: (value) => FlLine(
|
|
||||||
color: theme
|
|
||||||
.getColorFor(ThemeCode.textPrimary)
|
|
||||||
.withValues(alpha: 0.08),
|
|
||||||
strokeWidth: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
titlesData: FlTitlesData(
|
|
||||||
leftTitles: AxisTitles(
|
|
||||||
sideTitles: SideTitles(
|
|
||||||
showTitles: true,
|
|
||||||
reservedSize: SizeUtils.getByScreen(small: 36, big: 32),
|
|
||||||
getTitlesWidget: (value, meta) => Text(
|
|
||||||
_formatAxis(value),
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: SizeUtils.getByScreen(small: 10, big: 9),
|
|
||||||
color: theme
|
|
||||||
.getColorFor(ThemeCode.textPrimary)
|
|
||||||
.withValues(alpha: 0.4),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
bottomTitles: AxisTitles(
|
|
||||||
sideTitles: SideTitles(
|
|
||||||
showTitles: data.length <= 14,
|
|
||||||
getTitlesWidget: (value, meta) {
|
|
||||||
final index = value.toInt();
|
|
||||||
if (index < 0 || index >= data.length) {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
final date = data[index].date;
|
|
||||||
return Text(
|
|
||||||
'${date.day}/${date.month}',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: SizeUtils.getByScreen(small: 9, big: 8),
|
|
||||||
color: theme
|
|
||||||
.getColorFor(ThemeCode.textPrimary)
|
|
||||||
.withValues(alpha: 0.4),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
topTitles: const AxisTitles(
|
|
||||||
sideTitles: SideTitles(showTitles: false),
|
|
||||||
),
|
|
||||||
rightTitles: const AxisTitles(
|
|
||||||
sideTitles: SideTitles(showTitles: false),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
borderData: FlBorderData(show: false),
|
|
||||||
barTouchData: BarTouchData(
|
|
||||||
touchTooltipData: BarTouchTooltipData(
|
|
||||||
getTooltipColor: (_) =>
|
|
||||||
theme.getColorFor(ThemeCode.backgroundSecondary),
|
|
||||||
getTooltipItem: (group, groupIndex, rod, rodIndex) {
|
|
||||||
return BarTooltipItem(
|
|
||||||
rod.toY.toInt().toString(),
|
|
||||||
TextStyle(
|
|
||||||
fontSize: SizeUtils.getByScreen(small: 12, big: 11),
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: theme.getColorFor(ThemeCode.textPrimary),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
double get _barWidth {
|
double _barWidth(int length) {
|
||||||
if (data.length <= 7) return SizeUtils.getByScreen(small: 24, big: 20);
|
if (length <= 7) return SizeUtils.getByScreen(small: 24, big: 20);
|
||||||
if (data.length <= 14) return SizeUtils.getByScreen(small: 14, big: 12);
|
if (length <= 14) return SizeUtils.getByScreen(small: 14, big: 12);
|
||||||
return SizeUtils.getByScreen(small: 8, big: 6);
|
return SizeUtils.getByScreen(small: 8, big: 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
double _computeInterval(double maxY) {
|
|
||||||
if (maxY <= 0) return 1;
|
|
||||||
return (maxY / 4).ceilToDouble();
|
|
||||||
}
|
|
||||||
|
|
||||||
String _formatAxis(double value) {
|
|
||||||
if (value >= 1000) return '${(value / 1000).toStringAsFixed(0)}k';
|
|
||||||
return value.toInt().toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
import 'package:design_system/design_system.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:sf_localizations/sf_localizations.dart';
|
|
||||||
import 'package:utils/utils.dart';
|
|
||||||
|
|
||||||
import '../format_steps.dart';
|
|
||||||
import '../state/activity_meter_view_state.dart';
|
|
||||||
|
|
||||||
class StepsStatsRow extends StatelessWidget {
|
|
||||||
final StepsStats stats;
|
|
||||||
final ThemePort theme;
|
|
||||||
|
|
||||||
const StepsStatsRow({super.key, required this.stats, required this.theme});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final hasData = stats.total > 0;
|
|
||||||
|
|
||||||
return Padding(
|
|
||||||
padding: EdgeInsets.symmetric(
|
|
||||||
horizontal: SizeUtils.getByScreen(small: 16, big: 14),
|
|
||||||
vertical: SizeUtils.getByScreen(small: 8, big: 6),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: _StatItem(
|
|
||||||
label: context.translate(I18n.average),
|
|
||||||
value: hasData ? formatStepsNumber(stats.avgPerDay) : '--',
|
|
||||||
theme: theme,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: _StatItem(
|
|
||||||
label: context.translate(I18n.totalSteps),
|
|
||||||
value: hasData ? formatStepsNumber(stats.total) : '--',
|
|
||||||
theme: theme,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: _StatItem(
|
|
||||||
label: context.translate(I18n.bestDay),
|
|
||||||
value: hasData ? formatStepsNumber(stats.bestDaySteps) : '--',
|
|
||||||
theme: theme,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _StatItem extends StatelessWidget {
|
|
||||||
final String label;
|
|
||||||
final String value;
|
|
||||||
final ThemePort theme;
|
|
||||||
|
|
||||||
const _StatItem({
|
|
||||||
required this.label,
|
|
||||||
required this.value,
|
|
||||||
required this.theme,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
label,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: SizeUtils.getByScreen(small: 11, big: 10),
|
|
||||||
color: theme
|
|
||||||
.getColorFor(ThemeCode.textPrimary)
|
|
||||||
.withValues(alpha: 0.5),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: SizeUtils.getByScreen(small: 4, big: 2)),
|
|
||||||
Text(
|
|
||||||
value,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: SizeUtils.getByScreen(small: 20, big: 18),
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
color: theme.getColorFor(ThemeCode.textPrimary),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -60,6 +60,7 @@ dependencies:
|
|||||||
uuid: ^4.5.3
|
uuid: ^4.5.3
|
||||||
flutter_contacts: ^1.1.9+2
|
flutter_contacts: ^1.1.9+2
|
||||||
fl_chart: ^1.1.1
|
fl_chart: ^1.1.1
|
||||||
|
intl: ^0.20.2
|
||||||
lottie: ^3.3.1
|
lottie: ^3.3.1
|
||||||
image_picker: ^1.2.1
|
image_picker: ^1.2.1
|
||||||
|
|
||||||
|
|||||||
@@ -868,5 +868,19 @@
|
|||||||
"openSettings": "Einstellungen öffnen",
|
"openSettings": "Einstellungen öffnen",
|
||||||
"errorMessageCodeIsEmpty": "Der Code darf nicht leer sein",
|
"errorMessageCodeIsEmpty": "Der Code darf nicht leer sein",
|
||||||
"callWatchSubtitle": "Gib die Telefonnummer des Geräts ein, das du anrufen möchtest.",
|
"callWatchSubtitle": "Gib die Telefonnummer des Geräts ein, das du anrufen möchtest.",
|
||||||
"spyCallSubtitle": "Das Gerät ruft diese Nummer an. Gib deine Telefonnummer ein, um den Anruf von der Uhr zu erhalten."
|
"spyCallSubtitle": "Das Gerät ruft diese Nummer an. Gib deine Telefonnummer ein, um den Anruf von der Uhr zu erhalten.",
|
||||||
|
"activityMeterSectionToday": "Heute",
|
||||||
|
"activityMeterSectionActivity": "Aktivität",
|
||||||
|
"activityMeterSectionHistory": "Verlauf",
|
||||||
|
"activityMeterDailyGoal": "Tagesziel: {goal} ({percent}%)",
|
||||||
|
"activityMeterAverageDaily": "Tagesdurchschnitt",
|
||||||
|
"activityMeterTotalSteps": "Gesamt",
|
||||||
|
"activityMeterActiveDays": "Aktive Tage",
|
||||||
|
"activityMeterActiveHoursToday": "Aktive Stunden heute: {hours} von 24",
|
||||||
|
"activityMeterBestDay": "Bester Tag: {date} ({steps} Schritte)",
|
||||||
|
"activityMeterRangeLabelToday": "Heute, {date}",
|
||||||
|
"activityMeterNoStepsToday": "Heute noch keine Schritte erfasst",
|
||||||
|
"activityMeterNoStepsPeriod": "Keine Aktivität in diesem Zeitraum",
|
||||||
|
"unitStepsPerDay": "Schritte/Tag",
|
||||||
|
"unitDays": "Tage"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -868,5 +868,19 @@
|
|||||||
"openSettings": "Open settings",
|
"openSettings": "Open settings",
|
||||||
"errorMessageCodeIsEmpty": "The code cannot be empty",
|
"errorMessageCodeIsEmpty": "The code cannot be empty",
|
||||||
"callWatchSubtitle": "Enter the phone number of the device you want to call.",
|
"callWatchSubtitle": "Enter the phone number of the device you want to call.",
|
||||||
"spyCallSubtitle": "The device will call this number. Enter your phone to receive the call from the watch."
|
"spyCallSubtitle": "The device will call this number. Enter your phone to receive the call from the watch.",
|
||||||
|
"activityMeterSectionToday": "Today",
|
||||||
|
"activityMeterSectionActivity": "Activity",
|
||||||
|
"activityMeterSectionHistory": "History",
|
||||||
|
"activityMeterDailyGoal": "Daily goal: {goal} ({percent}%)",
|
||||||
|
"activityMeterAverageDaily": "Daily average",
|
||||||
|
"activityMeterTotalSteps": "Total",
|
||||||
|
"activityMeterActiveDays": "Active days",
|
||||||
|
"activityMeterActiveHoursToday": "Active hours today: {hours} of 24",
|
||||||
|
"activityMeterBestDay": "Best day: {date} ({steps} steps)",
|
||||||
|
"activityMeterRangeLabelToday": "Today, {date}",
|
||||||
|
"activityMeterNoStepsToday": "No steps recorded yet today",
|
||||||
|
"activityMeterNoStepsPeriod": "No activity recorded in this period",
|
||||||
|
"unitStepsPerDay": "steps/day",
|
||||||
|
"unitDays": "days"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -868,5 +868,19 @@
|
|||||||
"openSettings": "Abrir ajustes",
|
"openSettings": "Abrir ajustes",
|
||||||
"errorMessageCodeIsEmpty": "El código no puede estar vacío",
|
"errorMessageCodeIsEmpty": "El código no puede estar vacío",
|
||||||
"callWatchSubtitle": "Introduce el número de teléfono del dispositivo al que quieres llamar.",
|
"callWatchSubtitle": "Introduce el número de teléfono del dispositivo al que quieres llamar.",
|
||||||
"spyCallSubtitle": "El dispositivo llamará a este número. Introduce tu teléfono para recibir la llamada desde el reloj."
|
"spyCallSubtitle": "El dispositivo llamará a este número. Introduce tu teléfono para recibir la llamada desde el reloj.",
|
||||||
|
"activityMeterSectionToday": "Hoy",
|
||||||
|
"activityMeterSectionActivity": "Actividad",
|
||||||
|
"activityMeterSectionHistory": "Historial",
|
||||||
|
"activityMeterDailyGoal": "Meta diaria: {goal} ({percent}%)",
|
||||||
|
"activityMeterAverageDaily": "Promedio diario",
|
||||||
|
"activityMeterTotalSteps": "Total",
|
||||||
|
"activityMeterActiveDays": "Días activos",
|
||||||
|
"activityMeterActiveHoursToday": "Horas activas hoy: {hours} de 24",
|
||||||
|
"activityMeterBestDay": "Mejor día: {date} ({steps} pasos)",
|
||||||
|
"activityMeterRangeLabelToday": "Hoy, {date}",
|
||||||
|
"activityMeterNoStepsToday": "Aún no se registraron pasos hoy",
|
||||||
|
"activityMeterNoStepsPeriod": "No hay actividad registrada en este período",
|
||||||
|
"unitStepsPerDay": "pasos/día",
|
||||||
|
"unitDays": "días"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -868,5 +868,19 @@
|
|||||||
"openSettings": "Ouvrir les paramètres",
|
"openSettings": "Ouvrir les paramètres",
|
||||||
"errorMessageCodeIsEmpty": "Le code ne peut pas être vide",
|
"errorMessageCodeIsEmpty": "Le code ne peut pas être vide",
|
||||||
"callWatchSubtitle": "Saisis le numéro de téléphone de l'appareil que tu veux appeler.",
|
"callWatchSubtitle": "Saisis le numéro de téléphone de l'appareil que tu veux appeler.",
|
||||||
"spyCallSubtitle": "L'appareil appellera ce numéro. Saisis ton téléphone pour recevoir l'appel de la montre."
|
"spyCallSubtitle": "L'appareil appellera ce numéro. Saisis ton téléphone pour recevoir l'appel de la montre.",
|
||||||
|
"activityMeterSectionToday": "Aujourd'hui",
|
||||||
|
"activityMeterSectionActivity": "Activité",
|
||||||
|
"activityMeterSectionHistory": "Historique",
|
||||||
|
"activityMeterDailyGoal": "Objectif quotidien : {goal} ({percent}%)",
|
||||||
|
"activityMeterAverageDaily": "Moyenne quotidienne",
|
||||||
|
"activityMeterTotalSteps": "Total",
|
||||||
|
"activityMeterActiveDays": "Jours actifs",
|
||||||
|
"activityMeterActiveHoursToday": "Heures actives aujourd'hui : {hours} sur 24",
|
||||||
|
"activityMeterBestDay": "Meilleur jour : {date} ({steps} pas)",
|
||||||
|
"activityMeterRangeLabelToday": "Aujourd'hui, {date}",
|
||||||
|
"activityMeterNoStepsToday": "Aucun pas enregistré aujourd'hui",
|
||||||
|
"activityMeterNoStepsPeriod": "Aucune activité enregistrée sur cette période",
|
||||||
|
"unitStepsPerDay": "pas/jour",
|
||||||
|
"unitDays": "jours"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -868,5 +868,19 @@
|
|||||||
"openSettings": "Apri impostazioni",
|
"openSettings": "Apri impostazioni",
|
||||||
"errorMessageCodeIsEmpty": "Il codice non può essere vuoto",
|
"errorMessageCodeIsEmpty": "Il codice non può essere vuoto",
|
||||||
"callWatchSubtitle": "Inserisci il numero di telefono del dispositivo che vuoi chiamare.",
|
"callWatchSubtitle": "Inserisci il numero di telefono del dispositivo che vuoi chiamare.",
|
||||||
"spyCallSubtitle": "Il dispositivo chiamerà questo numero. Inserisci il tuo telefono per ricevere la chiamata dall'orologio."
|
"spyCallSubtitle": "Il dispositivo chiamerà questo numero. Inserisci il tuo telefono per ricevere la chiamata dall'orologio.",
|
||||||
|
"activityMeterSectionToday": "Oggi",
|
||||||
|
"activityMeterSectionActivity": "Attività",
|
||||||
|
"activityMeterSectionHistory": "Cronologia",
|
||||||
|
"activityMeterDailyGoal": "Obiettivo giornaliero: {goal} ({percent}%)",
|
||||||
|
"activityMeterAverageDaily": "Media giornaliera",
|
||||||
|
"activityMeterTotalSteps": "Totale",
|
||||||
|
"activityMeterActiveDays": "Giorni attivi",
|
||||||
|
"activityMeterActiveHoursToday": "Ore attive oggi: {hours} di 24",
|
||||||
|
"activityMeterBestDay": "Giorno migliore: {date} ({steps} passi)",
|
||||||
|
"activityMeterRangeLabelToday": "Oggi, {date}",
|
||||||
|
"activityMeterNoStepsToday": "Nessun passo registrato oggi",
|
||||||
|
"activityMeterNoStepsPeriod": "Nessuna attività registrata in questo periodo",
|
||||||
|
"unitStepsPerDay": "passi/giorno",
|
||||||
|
"unitDays": "giorni"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -868,5 +868,19 @@
|
|||||||
"openSettings": "Abrir definições",
|
"openSettings": "Abrir definições",
|
||||||
"errorMessageCodeIsEmpty": "O código não pode estar vazio",
|
"errorMessageCodeIsEmpty": "O código não pode estar vazio",
|
||||||
"callWatchSubtitle": "Digita o número de telefone do dispositivo que queres chamar.",
|
"callWatchSubtitle": "Digita o número de telefone do dispositivo que queres chamar.",
|
||||||
"spyCallSubtitle": "O dispositivo ligará para este número. Digita o teu telefone para receber a chamada do relógio."
|
"spyCallSubtitle": "O dispositivo ligará para este número. Digita o teu telefone para receber a chamada do relógio.",
|
||||||
|
"activityMeterSectionToday": "Hoje",
|
||||||
|
"activityMeterSectionActivity": "Atividade",
|
||||||
|
"activityMeterSectionHistory": "Histórico",
|
||||||
|
"activityMeterDailyGoal": "Meta diária: {goal} ({percent}%)",
|
||||||
|
"activityMeterAverageDaily": "Média diária",
|
||||||
|
"activityMeterTotalSteps": "Total",
|
||||||
|
"activityMeterActiveDays": "Dias ativos",
|
||||||
|
"activityMeterActiveHoursToday": "Horas ativas hoje: {hours} de 24",
|
||||||
|
"activityMeterBestDay": "Melhor dia: {date} ({steps} passos)",
|
||||||
|
"activityMeterRangeLabelToday": "Hoje, {date}",
|
||||||
|
"activityMeterNoStepsToday": "Ainda não há passos registados hoje",
|
||||||
|
"activityMeterNoStepsPeriod": "Sem atividade registada neste período",
|
||||||
|
"unitStepsPerDay": "passos/dia",
|
||||||
|
"unitDays": "dias"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,6 +121,20 @@ class I18n {
|
|||||||
static const String callOutgoing = 'callOutgoing';
|
static const String callOutgoing = 'callOutgoing';
|
||||||
static const String callWatch = 'callWatch';
|
static const String callWatch = 'callWatch';
|
||||||
static const String callWatchSubtitle = 'callWatchSubtitle';
|
static const String callWatchSubtitle = 'callWatchSubtitle';
|
||||||
|
static const String activityMeterSectionToday = 'activityMeterSectionToday';
|
||||||
|
static const String activityMeterSectionActivity = 'activityMeterSectionActivity';
|
||||||
|
static const String activityMeterSectionHistory = 'activityMeterSectionHistory';
|
||||||
|
static const String activityMeterDailyGoal = 'activityMeterDailyGoal';
|
||||||
|
static const String activityMeterAverageDaily = 'activityMeterAverageDaily';
|
||||||
|
static const String activityMeterTotalSteps = 'activityMeterTotalSteps';
|
||||||
|
static const String activityMeterActiveDays = 'activityMeterActiveDays';
|
||||||
|
static const String activityMeterActiveHoursToday = 'activityMeterActiveHoursToday';
|
||||||
|
static const String activityMeterBestDay = 'activityMeterBestDay';
|
||||||
|
static const String activityMeterRangeLabelToday = 'activityMeterRangeLabelToday';
|
||||||
|
static const String activityMeterNoStepsToday = 'activityMeterNoStepsToday';
|
||||||
|
static const String activityMeterNoStepsPeriod = 'activityMeterNoStepsPeriod';
|
||||||
|
static const String unitStepsPerDay = 'unitStepsPerDay';
|
||||||
|
static const String unitDays = 'unitDays';
|
||||||
static const String spyCallSubtitle = 'spyCallSubtitle';
|
static const String spyCallSubtitle = 'spyCallSubtitle';
|
||||||
static const String cancel = 'cancel';
|
static const String cancel = 'cancel';
|
||||||
static const String cardPinChange = 'cardPinChange';
|
static const String cardPinChange = 'cardPinChange';
|
||||||
|
|||||||
Reference in New Issue
Block a user