fix: assets
Container images / publish (., deploy/pocketbase/Dockerfile, evoliohealth-pocketbase) (push) Canceled after 0s
Container images / publish (., server/Dockerfile, evoliohealth-server) (push) Canceled after 0s
Checks / go (push) Canceled after 0s
Checks / flutter (push) Canceled after 0s
Checks / compose (push) Canceled after 0s
Android development APK / apk (push) Canceled after 0s
|
After Width: | Height: | Size: 528 KiB |
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 344 KiB |
|
After Width: | Height: | Size: 251 KiB |
|
After Width: | Height: | Size: 251 KiB |
@@ -0,0 +1,788 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'evolio_theme.dart';
|
||||
import 'strings.dart';
|
||||
|
||||
class BiometricsScreen extends StatefulWidget {
|
||||
const BiometricsScreen({super.key});
|
||||
|
||||
@override
|
||||
State<BiometricsScreen> createState() => _BiometricsScreenState();
|
||||
}
|
||||
|
||||
class _BiometricsScreenState extends State<BiometricsScreen> {
|
||||
int _selectedDestination = 2;
|
||||
int _selectedPeriod = 1;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final strings = AppStrings.of(context);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: EvolioColors.background.withValues(alpha: 0.96),
|
||||
surfaceTintColor: Colors.transparent,
|
||||
titleSpacing: 20,
|
||||
title: const _Brand(),
|
||||
actions: [
|
||||
IconButton(
|
||||
tooltip: strings.settings,
|
||||
onPressed: () => _showNotice(strings.scaleUnavailable),
|
||||
icon: const Icon(Icons.settings_outlined),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
top: false,
|
||||
child: CustomScrollView(
|
||||
key: const Key('biometrics-scroll-view'),
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 20, 20, 112),
|
||||
sliver: SliverList.list(
|
||||
children: [
|
||||
_WeightCard(strings: strings),
|
||||
const SizedBox(height: 16),
|
||||
_GradientAction(
|
||||
icon: Icons.bluetooth_rounded,
|
||||
label: strings.connectScale,
|
||||
onPressed: () => _showNotice(strings.scaleUnavailable),
|
||||
),
|
||||
const SizedBox(height: 28),
|
||||
_SectionHeader(title: strings.silhouetteTitle),
|
||||
const SizedBox(height: 12),
|
||||
_SilhouetteCard(strings: strings),
|
||||
const SizedBox(height: 28),
|
||||
_EvolutionHeader(
|
||||
strings: strings,
|
||||
selectedPeriod: _selectedPeriod,
|
||||
onSelected: (value) =>
|
||||
setState(() => _selectedPeriod = value),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_ChartCard(strings: strings, selectedPeriod: _selectedPeriod),
|
||||
const SizedBox(height: 28),
|
||||
_SectionHeader(title: strings.bodyMeasurements),
|
||||
const SizedBox(height: 12),
|
||||
_MetricsGrid(strings: strings),
|
||||
const SizedBox(height: 16),
|
||||
_GuidanceCard(strings: strings),
|
||||
const SizedBox(height: 20),
|
||||
_GradientAction(
|
||||
icon: Icons.save_outlined,
|
||||
label: strings.saveSession,
|
||||
onPressed: () => _showNotice(strings.saveUnavailable),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: NavigationBar(
|
||||
selectedIndex: _selectedDestination,
|
||||
onDestinationSelected: (index) {
|
||||
setState(() => _selectedDestination = index);
|
||||
},
|
||||
destinations: [
|
||||
NavigationDestination(
|
||||
icon: const Icon(Icons.dashboard_outlined),
|
||||
selectedIcon: const Icon(Icons.dashboard),
|
||||
label: strings.overview,
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: const Icon(Icons.directions_run_outlined),
|
||||
selectedIcon: const Icon(Icons.directions_run),
|
||||
label: strings.activity,
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: const Icon(Icons.straighten_outlined),
|
||||
selectedIcon: const Icon(Icons.straighten),
|
||||
label: strings.measurements,
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: const Icon(Icons.photo_library_outlined),
|
||||
selectedIcon: const Icon(Icons.photo_library),
|
||||
label: strings.photos,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showNotice(String message) {
|
||||
ScaffoldMessenger.of(context)
|
||||
..hideCurrentSnackBar()
|
||||
..showSnackBar(SnackBar(content: Text(message)));
|
||||
}
|
||||
}
|
||||
|
||||
class _Brand extends StatelessWidget {
|
||||
const _Brand();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Semantics(
|
||||
label: 'EvolioHealth',
|
||||
header: true,
|
||||
excludeSemantics: true,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: Image.asset(
|
||||
'assets/images/evoliohealth-logo.png',
|
||||
width: 42,
|
||||
height: 42,
|
||||
fit: BoxFit.cover,
|
||||
semanticLabel: 'EvolioHealth logo',
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 11),
|
||||
RichText(
|
||||
text: const TextSpan(
|
||||
style: TextStyle(
|
||||
color: EvolioColors.text,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w800,
|
||||
letterSpacing: -0.6,
|
||||
),
|
||||
children: [
|
||||
TextSpan(text: 'EVOLIO'),
|
||||
TextSpan(
|
||||
text: 'HEALTH',
|
||||
style: TextStyle(color: EvolioColors.cyan),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Panel extends StatelessWidget {
|
||||
const _Panel({
|
||||
required this.child,
|
||||
this.padding = const EdgeInsets.all(18),
|
||||
this.accent,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
final EdgeInsets padding;
|
||||
final Color? accent;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: EvolioColors.surface,
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
border: Border.all(color: Colors.white.withValues(alpha: 0.06)),
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Stack(
|
||||
children: [
|
||||
Padding(padding: padding, child: child),
|
||||
if (accent != null)
|
||||
Positioned(
|
||||
left: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: ColoredBox(
|
||||
color: accent!,
|
||||
child: const SizedBox(width: 3),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _WeightCard extends StatelessWidget {
|
||||
const _WeightCard({required this.strings});
|
||||
|
||||
final AppStrings strings;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _Panel(
|
||||
accent: EvolioColors.cyan,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_CapsLabel(strings.currentWeight),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
'82.4',
|
||||
style: Theme.of(context).textTheme.displayLarge,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 5),
|
||||
child: Text(
|
||||
strings.kilograms,
|
||||
style: const TextStyle(color: EvolioColors.muted),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 7,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: EvolioColors.magenta.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(9),
|
||||
border: Border.all(
|
||||
color: EvolioColors.magenta.withValues(alpha: 0.35),
|
||||
),
|
||||
),
|
||||
child: const Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.south_east_rounded,
|
||||
size: 16,
|
||||
color: EvolioColors.magenta,
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
Text(
|
||||
'-1.2 kg',
|
||||
style: TextStyle(
|
||||
color: EvolioColors.magenta,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
strings.dateLabel,
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _GradientAction extends StatelessWidget {
|
||||
const _GradientAction({
|
||||
required this.icon,
|
||||
required this.label,
|
||||
required this.onPressed,
|
||||
});
|
||||
|
||||
final IconData icon;
|
||||
final String label;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Semantics(
|
||||
button: true,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [EvolioColors.cyan, EvolioColors.lime],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: EvolioColors.cyan.withValues(alpha: 0.16),
|
||||
blurRadius: 18,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: FilledButton.icon(
|
||||
onPressed: onPressed,
|
||||
style: FilledButton.styleFrom(
|
||||
foregroundColor: const Color(0xFF09201E),
|
||||
backgroundColor: Colors.transparent,
|
||||
shadowColor: Colors.transparent,
|
||||
minimumSize: const Size.fromHeight(54),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
icon: Icon(icon, size: 20),
|
||||
label: Text(
|
||||
label.toUpperCase(),
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w800,
|
||||
letterSpacing: 1.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SectionHeader extends StatelessWidget {
|
||||
const _SectionHeader({required this.title});
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Text(
|
||||
title.toUpperCase(),
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SilhouetteCard extends StatelessWidget {
|
||||
const _SilhouetteCard({required this.strings});
|
||||
|
||||
final AppStrings strings;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _Panel(
|
||||
padding: EdgeInsets.zero,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(18, 18, 18, 6),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_CapsLabel(strings.silhouetteHint),
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
strings.silhouetteDisclaimer,
|
||||
style: const TextStyle(
|
||||
color: EvolioColors.muted,
|
||||
fontSize: 12,
|
||||
height: 1.35,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 260,
|
||||
width: double.infinity,
|
||||
child: Semantics(
|
||||
image: true,
|
||||
label: strings.silhouetteTitle,
|
||||
child: const CustomPaint(painter: _SilhouettePainter()),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SilhouettePainter extends CustomPainter {
|
||||
const _SilhouettePainter();
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final center = Offset(size.width / 2, size.height / 2);
|
||||
final glow = Paint()
|
||||
..shader =
|
||||
RadialGradient(
|
||||
colors: [
|
||||
EvolioColors.cyan.withValues(alpha: 0.18),
|
||||
EvolioColors.surface.withValues(alpha: 0),
|
||||
],
|
||||
).createShader(
|
||||
Rect.fromCircle(center: center, radius: size.shortestSide * 0.48),
|
||||
);
|
||||
canvas.drawCircle(center, size.shortestSide * 0.48, glow);
|
||||
|
||||
final grid = Paint()
|
||||
..color = Colors.white.withValues(alpha: 0.035)
|
||||
..strokeWidth = 1;
|
||||
for (double x = 18; x < size.width; x += 24) {
|
||||
canvas.drawLine(Offset(x, 0), Offset(x, size.height), grid);
|
||||
}
|
||||
for (double y = 18; y < size.height; y += 24) {
|
||||
canvas.drawLine(Offset(0, y), Offset(size.width, y), grid);
|
||||
}
|
||||
|
||||
final outline = Paint()
|
||||
..shader = const LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [EvolioColors.cyan, EvolioColors.lime],
|
||||
).createShader(Rect.fromLTWH(0, 20, size.width, size.height - 30))
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2.2;
|
||||
final fill = Paint()
|
||||
..color = EvolioColors.cyan.withValues(alpha: 0.07)
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
final x = size.width / 2;
|
||||
final body = Path()
|
||||
..addOval(Rect.fromCircle(center: Offset(x, 46), radius: 22))
|
||||
..moveTo(x - 18, 70)
|
||||
..cubicTo(x - 48, 88, x - 47, 122, x - 38, 151)
|
||||
..cubicTo(x - 34, 171, x - 42, 205, x - 34, 242)
|
||||
..lineTo(x - 13, 242)
|
||||
..cubicTo(x - 8, 211, x - 7, 184, x, 169)
|
||||
..cubicTo(x + 7, 184, x + 8, 211, x + 13, 242)
|
||||
..lineTo(x + 34, 242)
|
||||
..cubicTo(x + 42, 205, x + 34, 171, x + 38, 151)
|
||||
..cubicTo(x + 47, 122, x + 48, 88, x + 18, 70)
|
||||
..close();
|
||||
canvas.drawPath(body, fill);
|
||||
canvas.drawPath(body, outline);
|
||||
|
||||
final marker = Paint()..color = EvolioColors.magenta;
|
||||
for (final y in [105.0, 139.0, 168.0]) {
|
||||
canvas.drawCircle(Offset(x + 39, y), 3, marker);
|
||||
canvas.drawLine(
|
||||
Offset(x + 39, y),
|
||||
Offset(x + 70, y),
|
||||
Paint()
|
||||
..color = EvolioColors.magenta.withValues(alpha: 0.5)
|
||||
..strokeWidth = 1,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||
}
|
||||
|
||||
class _EvolutionHeader extends StatelessWidget {
|
||||
const _EvolutionHeader({
|
||||
required this.strings,
|
||||
required this.selectedPeriod,
|
||||
required this.onSelected,
|
||||
});
|
||||
|
||||
final AppStrings strings;
|
||||
final int selectedPeriod;
|
||||
final ValueChanged<int> onSelected;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final periods = [strings.oneWeek, strings.oneMonth, strings.sixMonths];
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(child: _SectionHeader(title: strings.evolution)),
|
||||
SegmentedButton<int>(
|
||||
segments: [
|
||||
for (var i = 0; i < periods.length; i++)
|
||||
ButtonSegment(value: i, label: Text(periods[i])),
|
||||
],
|
||||
selected: {selectedPeriod},
|
||||
onSelectionChanged: (selection) => onSelected(selection.first),
|
||||
showSelectedIcon: false,
|
||||
style: ButtonStyle(
|
||||
visualDensity: VisualDensity.compact,
|
||||
textStyle: const WidgetStatePropertyAll(
|
||||
TextStyle(fontSize: 10, fontWeight: FontWeight.w700),
|
||||
),
|
||||
padding: const WidgetStatePropertyAll(
|
||||
EdgeInsets.symmetric(horizontal: 9),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ChartCard extends StatelessWidget {
|
||||
const _ChartCard({required this.strings, required this.selectedPeriod});
|
||||
|
||||
final AppStrings strings;
|
||||
final int selectedPeriod;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _Panel(
|
||||
padding: const EdgeInsets.fromLTRB(12, 18, 12, 12),
|
||||
child: SizedBox(
|
||||
height: 170,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: CustomPaint(
|
||||
painter: _TrendPainter(period: selectedPeriod),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 6,
|
||||
right: 6,
|
||||
bottom: 0,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
'01.07',
|
||||
style: TextStyle(color: EvolioColors.muted, fontSize: 10),
|
||||
),
|
||||
const Text(
|
||||
'10.07',
|
||||
style: TextStyle(color: EvolioColors.muted, fontSize: 10),
|
||||
),
|
||||
Text(
|
||||
strings.latest.toUpperCase(),
|
||||
style: const TextStyle(
|
||||
color: EvolioColors.magenta,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TrendPainter extends CustomPainter {
|
||||
const _TrendPainter({required this.period});
|
||||
|
||||
final int period;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final chartHeight = size.height - 26;
|
||||
final gridPaint = Paint()
|
||||
..color = Colors.white.withValues(alpha: 0.05)
|
||||
..strokeWidth = 1;
|
||||
for (var i = 0; i < 4; i++) {
|
||||
final y = chartHeight * i / 3;
|
||||
canvas.drawLine(Offset(0, y), Offset(size.width, y), gridPaint);
|
||||
}
|
||||
|
||||
final phase = period * 0.35;
|
||||
final line = Path();
|
||||
for (var i = 0; i <= 60; i++) {
|
||||
final progress = i / 60;
|
||||
final wave = math.sin(progress * math.pi * 3 + phase) * 8;
|
||||
final y = chartHeight * (0.72 - progress * 0.32) + wave;
|
||||
final point = Offset(size.width * progress, y);
|
||||
if (i == 0) {
|
||||
line.moveTo(point.dx, point.dy);
|
||||
} else {
|
||||
line.lineTo(point.dx, point.dy);
|
||||
}
|
||||
}
|
||||
final area = Path.from(line)
|
||||
..lineTo(size.width, chartHeight)
|
||||
..lineTo(0, chartHeight)
|
||||
..close();
|
||||
canvas.drawPath(
|
||||
area,
|
||||
Paint()
|
||||
..shader = LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
EvolioColors.magenta.withValues(alpha: 0.3),
|
||||
EvolioColors.magenta.withValues(alpha: 0),
|
||||
],
|
||||
).createShader(Rect.fromLTWH(0, 0, size.width, chartHeight)),
|
||||
);
|
||||
canvas.drawPath(
|
||||
line,
|
||||
Paint()
|
||||
..color = EvolioColors.magenta
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2.2
|
||||
..strokeCap = StrokeCap.round,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _TrendPainter oldDelegate) =>
|
||||
oldDelegate.period != period;
|
||||
}
|
||||
|
||||
class _MetricsGrid extends StatelessWidget {
|
||||
const _MetricsGrid({required this.strings});
|
||||
|
||||
final AppStrings strings;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final metrics = [
|
||||
(strings.chest, '104.5', Icons.straighten, EvolioColors.cyan),
|
||||
(strings.waist, '82.0', Icons.accessibility_new, EvolioColors.lime),
|
||||
(strings.hips, '98.2', Icons.data_usage, EvolioColors.magenta),
|
||||
(strings.arms, '38.5', Icons.fitness_center, const Color(0xFFFFB4EE)),
|
||||
];
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final columnCount = constraints.maxWidth >= 620 ? 4 : 2;
|
||||
final width =
|
||||
(constraints.maxWidth - 12 * (columnCount - 1)) / columnCount;
|
||||
return Wrap(
|
||||
spacing: 12,
|
||||
runSpacing: 12,
|
||||
children: [
|
||||
for (final metric in metrics)
|
||||
SizedBox(
|
||||
width: width,
|
||||
child: _MetricCard(
|
||||
label: metric.$1,
|
||||
value: metric.$2,
|
||||
icon: metric.$3,
|
||||
accent: metric.$4,
|
||||
unit: strings.centimeters,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MetricCard extends StatelessWidget {
|
||||
const _MetricCard({
|
||||
required this.label,
|
||||
required this.value,
|
||||
required this.icon,
|
||||
required this.accent,
|
||||
required this.unit,
|
||||
});
|
||||
|
||||
final String label;
|
||||
final String value;
|
||||
final IconData icon;
|
||||
final Color accent;
|
||||
final String unit;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _Panel(
|
||||
accent: accent.withValues(alpha: 0.55),
|
||||
padding: const EdgeInsets.all(15),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: _CapsLabel(label)),
|
||||
Icon(icon, color: accent, size: 19),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 17),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
initialValue: value,
|
||||
keyboardType: const TextInputType.numberWithOptions(
|
||||
decimal: true,
|
||||
),
|
||||
style: const TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
semanticCounterText: '$label, $unit',
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4),
|
||||
child: Text(
|
||||
unit,
|
||||
style: const TextStyle(
|
||||
color: EvolioColors.muted,
|
||||
fontSize: 11,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _GuidanceCard extends StatelessWidget {
|
||||
const _GuidanceCard({required this.strings});
|
||||
|
||||
final AppStrings strings;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: EvolioColors.cyan.withValues(alpha: 0.07),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: EvolioColors.cyan.withValues(alpha: 0.18)),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.lightbulb_outline,
|
||||
color: EvolioColors.cyan,
|
||||
size: 19,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
strings.measurementGuidance,
|
||||
style: const TextStyle(
|
||||
color: EvolioColors.muted,
|
||||
fontSize: 12,
|
||||
height: 1.4,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CapsLabel extends StatelessWidget {
|
||||
const _CapsLabel(this.text);
|
||||
|
||||
final String text;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Text(
|
||||
text.toUpperCase(),
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
abstract final class EvolioColors {
|
||||
static const background = Color(0xFF101315);
|
||||
static const surface = Color(0xFF191D20);
|
||||
static const surfaceRaised = Color(0xFF22272B);
|
||||
static const cyan = Color(0xFF51E5DC);
|
||||
static const lime = Color(0xFFB8EE70);
|
||||
static const magenta = Color(0xFFE77ADD);
|
||||
static const text = Color(0xFFF0F4F3);
|
||||
static const muted = Color(0xFF9DA9A7);
|
||||
}
|
||||
|
||||
abstract final class EvolioTheme {
|
||||
static ThemeData get dark {
|
||||
const scheme = ColorScheme.dark(
|
||||
primary: EvolioColors.cyan,
|
||||
onPrimary: Color(0xFF00201E),
|
||||
secondary: EvolioColors.magenta,
|
||||
onSecondary: Color(0xFF32102F),
|
||||
tertiary: EvolioColors.lime,
|
||||
onTertiary: Color(0xFF172500),
|
||||
surface: EvolioColors.surface,
|
||||
onSurface: EvolioColors.text,
|
||||
error: Color(0xFFFFB4AB),
|
||||
);
|
||||
|
||||
return ThemeData(
|
||||
brightness: Brightness.dark,
|
||||
colorScheme: scheme,
|
||||
scaffoldBackgroundColor: EvolioColors.background,
|
||||
useMaterial3: true,
|
||||
fontFamily: 'sans-serif',
|
||||
textTheme: const TextTheme(
|
||||
displayLarge: TextStyle(
|
||||
color: EvolioColors.text,
|
||||
fontSize: 48,
|
||||
height: 1,
|
||||
fontWeight: FontWeight.w800,
|
||||
letterSpacing: -1.5,
|
||||
),
|
||||
headlineMedium: TextStyle(
|
||||
color: EvolioColors.text,
|
||||
fontSize: 24,
|
||||
height: 1.2,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
titleMedium: TextStyle(
|
||||
color: EvolioColors.text,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 1.5,
|
||||
),
|
||||
bodyMedium: TextStyle(
|
||||
color: EvolioColors.text,
|
||||
fontSize: 15,
|
||||
height: 1.45,
|
||||
),
|
||||
labelSmall: TextStyle(
|
||||
color: EvolioColors.muted,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 1.25,
|
||||
),
|
||||
),
|
||||
navigationBarTheme: const NavigationBarThemeData(
|
||||
backgroundColor: EvolioColors.background,
|
||||
indicatorColor: Color(0x2451E5DC),
|
||||
height: 72,
|
||||
labelTextStyle: WidgetStatePropertyAll(
|
||||
TextStyle(fontSize: 10, fontWeight: FontWeight.w700),
|
||||
),
|
||||
),
|
||||
inputDecorationTheme: const InputDecorationTheme(
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
isDense: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AppStrings {
|
||||
const AppStrings._(this.locale);
|
||||
|
||||
final Locale locale;
|
||||
|
||||
static const supportedLocales = [Locale('en'), Locale('fr')];
|
||||
|
||||
static AppStrings of(BuildContext context) {
|
||||
return AppStrings._(Localizations.localeOf(context));
|
||||
}
|
||||
|
||||
bool get _fr => locale.languageCode == 'fr';
|
||||
|
||||
String get appName => 'EvolioHealth';
|
||||
String get overview => _fr ? 'Aperçu' : 'Overview';
|
||||
String get activity => _fr ? 'Activité' : 'Activity';
|
||||
String get measurements => _fr ? 'Mesures' : 'Measurements';
|
||||
String get photos => _fr ? 'Photos' : 'Photos';
|
||||
String get settings => _fr ? 'Paramètres' : 'Settings';
|
||||
String get currentWeight => _fr ? 'Poids actuel' : 'Current weight';
|
||||
String get weightChange =>
|
||||
_fr ? 'depuis la dernière mesure' : 'since last measurement';
|
||||
String get connectScale => _fr ? 'Connecter la balance' : 'Connect scale';
|
||||
String get scaleUnavailable => _fr
|
||||
? 'La connexion BLE sera disponible dans une prochaine étape.'
|
||||
: 'BLE connection will be available in a future step.';
|
||||
String get silhouetteTitle =>
|
||||
_fr ? 'Silhouette indicative' : 'Indicative silhouette';
|
||||
String get silhouetteHint => _fr
|
||||
? 'Aperçu paramétrique basé sur vos mesures'
|
||||
: 'Parametric preview based on your measurements';
|
||||
String get silhouetteDisclaimer => _fr
|
||||
? 'Visualisation indicative uniquement — ce n’est ni une reconstruction fidèle ni une analyse médicale.'
|
||||
: 'Indicative visualization only — not a faithful reconstruction or medical analysis.';
|
||||
String get evolution => _fr ? 'Évolution' : 'Evolution';
|
||||
String get oneWeek => _fr ? '1 sem.' : '1 wk';
|
||||
String get oneMonth => _fr ? '1 mois' : '1 mo';
|
||||
String get sixMonths => _fr ? '6 mois' : '6 mo';
|
||||
String get latest => _fr ? 'Récent' : 'Latest';
|
||||
String get bodyMeasurements => _fr ? 'Mensurations' : 'Body measurements';
|
||||
String get chest => _fr ? 'Poitrine' : 'Chest';
|
||||
String get waist => _fr ? 'Taille' : 'Waist';
|
||||
String get hips => _fr ? 'Hanches' : 'Hips';
|
||||
String get arms => _fr ? 'Bras' : 'Arms';
|
||||
String get saveSession => _fr ? 'Enregistrer la session' : 'Save session';
|
||||
String get saveUnavailable => _fr
|
||||
? 'Cette maquette ne persiste pas encore les mesures.'
|
||||
: 'This prototype does not persist measurements yet.';
|
||||
String get measurementGuidance => _fr
|
||||
? 'Pour suivre une tendance fiable, mesurez-vous dans des conditions similaires.'
|
||||
: 'For a reliable trend, measure under similar conditions.';
|
||||
String get dateLabel => _fr ? '14 juillet' : 'July 14';
|
||||
String get kilograms => 'kg';
|
||||
String get centimeters => 'cm';
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.13.1"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.3"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
flutter_localizations:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
intl:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: intl
|
||||
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.20.2"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.0.2"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.10"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.19"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.13.0"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.2"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.11"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.2.0"
|
||||
sdks:
|
||||
dart: ">=3.12.0 <4.0.0"
|
||||
flutter: ">=3.18.0-18.0.pre.54"
|
||||
|
After Width: | Height: | Size: 528 KiB |
|
After Width: | Height: | Size: 251 KiB |
@@ -0,0 +1,77 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
abstract final class EvolioColors {
|
||||
static const background = Color(0xFF101315);
|
||||
static const surface = Color(0xFF191D20);
|
||||
static const surfaceRaised = Color(0xFF22272B);
|
||||
static const cyan = Color(0xFF51E5DC);
|
||||
static const lime = Color(0xFFB8EE70);
|
||||
static const magenta = Color(0xFFE77ADD);
|
||||
static const text = Color(0xFFF0F4F3);
|
||||
static const muted = Color(0xFF9DA9A7);
|
||||
}
|
||||
|
||||
abstract final class EvolioTheme {
|
||||
static ThemeData get dark {
|
||||
const scheme = ColorScheme.dark(
|
||||
primary: EvolioColors.cyan,
|
||||
onPrimary: Color(0xFF00201E),
|
||||
secondary: EvolioColors.magenta,
|
||||
onSecondary: Color(0xFF32102F),
|
||||
tertiary: EvolioColors.lime,
|
||||
onTertiary: Color(0xFF172500),
|
||||
surface: EvolioColors.surface,
|
||||
onSurface: EvolioColors.text,
|
||||
error: Color(0xFFFFB4AB),
|
||||
);
|
||||
|
||||
return ThemeData(
|
||||
brightness: Brightness.dark,
|
||||
colorScheme: scheme,
|
||||
scaffoldBackgroundColor: EvolioColors.background,
|
||||
useMaterial3: true,
|
||||
fontFamily: 'sans-serif',
|
||||
textTheme: const TextTheme(
|
||||
displayLarge: TextStyle(
|
||||
color: EvolioColors.text,
|
||||
fontSize: 52,
|
||||
height: 1,
|
||||
fontWeight: FontWeight.w800,
|
||||
letterSpacing: -1.5,
|
||||
),
|
||||
headlineMedium: TextStyle(
|
||||
color: EvolioColors.text,
|
||||
fontSize: 26,
|
||||
height: 1.2,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
titleMedium: TextStyle(
|
||||
color: EvolioColors.text,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 1.5,
|
||||
),
|
||||
bodyMedium: TextStyle(
|
||||
color: EvolioColors.text,
|
||||
fontSize: 15,
|
||||
height: 1.45,
|
||||
),
|
||||
labelSmall: TextStyle(
|
||||
color: EvolioColors.muted,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 1.25,
|
||||
),
|
||||
),
|
||||
inputDecorationTheme: const InputDecorationTheme(
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
isDense: true,
|
||||
),
|
||||
snackBarTheme: const SnackBarThemeData(
|
||||
backgroundColor: EvolioColors.surfaceRaised,
|
||||
contentTextStyle: TextStyle(color: EvolioColors.text),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AppStrings {
|
||||
const AppStrings._(this.locale);
|
||||
|
||||
final Locale locale;
|
||||
|
||||
static const supportedLocales = [Locale('en'), Locale('fr')];
|
||||
|
||||
static AppStrings of(BuildContext context) =>
|
||||
AppStrings._(Localizations.localeOf(context));
|
||||
|
||||
bool get _fr => locale.languageCode == 'fr';
|
||||
|
||||
String get appName => 'EvolioHealth';
|
||||
String get overview => _fr ? 'Aperçu' : 'Overview';
|
||||
String get activity => _fr ? 'Activité' : 'Activity';
|
||||
String get measurements => _fr ? 'Mesures' : 'Measurements';
|
||||
String get photos => _fr ? 'Photos' : 'Photos';
|
||||
String get exports => _fr ? 'Exports' : 'Exports';
|
||||
String get settings => _fr ? 'Paramètres' : 'Settings';
|
||||
String get greeting => _fr ? 'Bonjour, Alex' : 'Hello, Alex';
|
||||
String get subtitle => _fr
|
||||
? 'Voici l’évolution de vos mesures personnelles.'
|
||||
: 'Here is the evolution of your personal measurements.';
|
||||
String get currentWeight => _fr ? 'Poids actuel' : 'Current weight';
|
||||
String get lastMeasurement => _fr ? 'Dernière mesure' : 'Last measurement';
|
||||
String get bodyFat => _fr ? 'Masse grasse estimée' : 'Estimated body fat';
|
||||
String get pendingSync => _fr ? 'Synchronisation' : 'Synchronization';
|
||||
String get synchronized => _fr ? 'À jour' : 'Up to date';
|
||||
String get newSession => _fr ? 'Nouvelle session' : 'New session';
|
||||
String get prototypeNotice => _fr
|
||||
? 'Cette maquette n’est pas encore connectée à l’API EvolioHealth.'
|
||||
: 'This prototype is not connected to the EvolioHealth API yet.';
|
||||
String get silhouetteTitle =>
|
||||
_fr ? 'Silhouette indicative' : 'Indicative silhouette';
|
||||
String get silhouetteHint => _fr
|
||||
? 'Aperçu paramétrique basé sur vos mesures'
|
||||
: 'Parametric preview based on your measurements';
|
||||
String get silhouetteDisclaimer => _fr
|
||||
? 'Visualisation indicative uniquement — ce n’est ni une reconstruction fidèle ni une analyse médicale.'
|
||||
: 'Indicative visualization only — not a faithful reconstruction or medical analysis.';
|
||||
String get evolution => _fr ? 'Évolution du poids' : 'Weight evolution';
|
||||
String get oneWeek => _fr ? '1 sem.' : '1 wk';
|
||||
String get oneMonth => _fr ? '1 mois' : '1 mo';
|
||||
String get sixMonths => _fr ? '6 mois' : '6 mo';
|
||||
String get latest => _fr ? 'Récent' : 'Latest';
|
||||
String get bodyMeasurements => _fr ? 'Mensurations' : 'Body measurements';
|
||||
String get chest => _fr ? 'Poitrine' : 'Chest';
|
||||
String get waist => _fr ? 'Taille' : 'Waist';
|
||||
String get hips => _fr ? 'Hanches' : 'Hips';
|
||||
String get arms => _fr ? 'Bras' : 'Arms';
|
||||
String get shoulders => _fr ? 'Épaules' : 'Shoulders';
|
||||
String get thighs => _fr ? 'Cuisses' : 'Thighs';
|
||||
String get history => _fr ? 'Voir l’historique' : 'View history';
|
||||
String get guidance => _fr
|
||||
? 'Pour suivre une tendance fiable, effectuez vos mesures dans des conditions similaires.'
|
||||
: 'For a reliable trend, take measurements under similar conditions.';
|
||||
String get dateLabel => _fr ? '14 juillet 2026' : 'July 14, 2026';
|
||||
String get kilograms => 'kg';
|
||||
String get centimeters => 'cm';
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.13.1"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.3"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
flutter_localizations:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
intl:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: intl
|
||||
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.20.2"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.0.2"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.10"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.19"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.13.0"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.2"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.11"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.2.0"
|
||||
sdks:
|
||||
dart: ">=3.12.0 <4.0.0"
|
||||
flutter: ">=3.18.0-18.0.pre.54"
|
||||
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 344 KiB |