feat:增加TDesign组件库
This commit is contained in:
@@ -1,157 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
TextStyle _titleStyle() {
|
||||
return TextStyle(fontSize: 16, fontWeight: FontWeight.w600);
|
||||
}
|
||||
|
||||
TextStyle _contentStyle() {
|
||||
return TextStyle(fontSize: 16, fontWeight: FontWeight.w500);
|
||||
}
|
||||
|
||||
OutlineInputBorder _border(ColorScheme colors) {
|
||||
return OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: colors.onSurface.withAlpha(100), width: 1.5),
|
||||
);
|
||||
}
|
||||
|
||||
OutlineInputBorder _enabledBorder(ColorScheme colors) {
|
||||
return OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: colors.onSurface.withAlpha(100), width: 1.5),
|
||||
);
|
||||
}
|
||||
|
||||
OutlineInputBorder _focusedBorder(ColorScheme colors) {
|
||||
return OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: colors.primary, width: 2),
|
||||
);
|
||||
}
|
||||
|
||||
EdgeInsetsGeometry _containerPadding() {
|
||||
return EdgeInsets.symmetric(horizontal: 16, vertical: 10);
|
||||
}
|
||||
|
||||
Widget buildFormTime({
|
||||
required BuildContext context,
|
||||
required String name,
|
||||
required String title,
|
||||
required DateTime? initialValue,
|
||||
required ValueChanged<DateTime?> onChanged,
|
||||
double width = 100,
|
||||
String hintText = '请选择时间',
|
||||
}) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
final alphaColor = colors.onSurface.withAlpha(100);
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
Container(width: width, child: Text(title, style: _titleStyle())),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: FormBuilderDateTimePicker(
|
||||
name: name,
|
||||
inputType: InputType.time,
|
||||
initialTime: TimeOfDay.now(),
|
||||
initialValue: initialValue,
|
||||
format: DateFormat.Hm(),
|
||||
onChanged: (value) => onChanged(value),
|
||||
style: _contentStyle(),
|
||||
decoration: InputDecoration(
|
||||
hintText: hintText,
|
||||
hintStyle: TextStyle(color: alphaColor),
|
||||
prefixIcon: Icon(Icons.schedule_rounded, size: 24),
|
||||
border: _border(colors),
|
||||
enabledBorder: _enabledBorder(colors),
|
||||
focusedBorder: _focusedBorder(colors),
|
||||
filled: true,
|
||||
fillColor: colors.surfaceContainer,
|
||||
contentPadding: _containerPadding(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildFormSlider({
|
||||
required BuildContext context,
|
||||
required String name,
|
||||
required String title,
|
||||
required String label,
|
||||
required double min,
|
||||
required double max,
|
||||
required double initialValue,
|
||||
required int divisions,
|
||||
required ValueChanged<double?> onChanged,
|
||||
double width = 100,
|
||||
}) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
final alphaColor = colors.onSurface.withAlpha(100);
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
Container(width: width, child: Text(title, style: _titleStyle())),
|
||||
const SizedBox(width: 8),
|
||||
|
||||
Expanded(
|
||||
child: FormBuilderSlider(
|
||||
name: name,
|
||||
min: min,
|
||||
max: max,
|
||||
initialValue: initialValue,
|
||||
divisions: divisions,
|
||||
onChanged: (value) => onChanged(value),
|
||||
label: label,
|
||||
decoration: const InputDecoration(
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildFormRadioGroup({
|
||||
required BuildContext context,
|
||||
required String name,
|
||||
required String title,
|
||||
required String initialValue,
|
||||
required List<String> items,
|
||||
required ValueChanged<String?> onChanged,
|
||||
double width = 100,
|
||||
}) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
final alphaColor = colors.onSurface.withAlpha(100);
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
Container(width: width, child: Text(title, style: _titleStyle())),
|
||||
const SizedBox(width: 8),
|
||||
|
||||
Expanded(
|
||||
child: FormBuilderRadioGroup<String>(
|
||||
name: name,
|
||||
options:
|
||||
items
|
||||
.map(
|
||||
(item) =>
|
||||
FormBuilderFieldOption(value: item, child: Text(item)),
|
||||
)
|
||||
.toList(),
|
||||
initialValue: initialValue,
|
||||
activeColor: colors.primary,
|
||||
decoration: const InputDecoration(
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
onChanged: (value) => onChanged(value),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:fitnote/utils/record_utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:getwidget/getwidget.dart';
|
||||
import 'package:tdesign_flutter/tdesign_flutter.dart';
|
||||
|
||||
Widget buildCardTitle({
|
||||
required BuildContext context,
|
||||
@@ -70,17 +69,14 @@ Widget buildProgressItem({
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
GFProgressBar(
|
||||
animation: true,
|
||||
lineHeight: 20,
|
||||
percentage: progress,
|
||||
progressBarColor: color,
|
||||
child: Text(
|
||||
'${roundNum(progress * 100)}%',
|
||||
textAlign: TextAlign.end,
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
TDProgress(
|
||||
type: TDProgressType.linear,
|
||||
progressStatus: TDProgressStatus.primary,
|
||||
value: progress,
|
||||
color: color,
|
||||
strokeWidth: 20,
|
||||
progressLabelPosition: TDProgressLabelPosition.inside,
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ class _GoalProgressState extends State<GoalProgress> {
|
||||
const SizedBox(height: 28),
|
||||
buildProgressItem(
|
||||
title: '目标体重',
|
||||
current: provider.latestWeight.toString(),
|
||||
current: provider.latestWeight.toStringAsFixed(1),
|
||||
target: AppConfig.goalWeight.toString(),
|
||||
unit: RecordType.weight.unit,
|
||||
progress: calWeightProgress(
|
||||
|
||||
@@ -9,9 +9,9 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_common/flutter_common.dart';
|
||||
import 'package:flutter_common/widget/common_widget.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:getwidget/getwidget.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tdesign_flutter/tdesign_flutter.dart';
|
||||
|
||||
class RecordDaily extends StatefulWidget {
|
||||
const RecordDaily({super.key});
|
||||
@@ -172,7 +172,7 @@ class _RecordDailyState extends State<RecordDaily> {
|
||||
break;
|
||||
case RecordType.sport:
|
||||
provider.updateSportProject(AppConfig.sportProjectList[0]);
|
||||
provider.updateSportDuration(30);
|
||||
provider.updateSportDuration(AppConfig.initSpot);
|
||||
break;
|
||||
case RecordType.sleep:
|
||||
provider.updateSleepStartTime('');
|
||||
@@ -202,64 +202,44 @@ class _RecordDailyState extends State<RecordDaily> {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: GFButton(
|
||||
onPressed: () {
|
||||
child: TDButton(
|
||||
text: '取消',
|
||||
theme: TDButtonTheme.light,
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
_resetRecord();
|
||||
},
|
||||
color: Colors.grey,
|
||||
text: "取消",
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: GFButton(
|
||||
onPressed: () {
|
||||
child: TDButton(
|
||||
text: '删除',
|
||||
theme: TDButtonTheme.danger,
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
_deleteRecord(type);
|
||||
},
|
||||
color: Colors.red,
|
||||
text: "删除",
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: GFButton(
|
||||
onPressed: () {
|
||||
child: TDButton(
|
||||
text: '确定',
|
||||
theme: TDButtonTheme.primary,
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
_saveRecord();
|
||||
},
|
||||
color: colors.primary,
|
||||
text: "确定",
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _resetRecord() {
|
||||
final provider = context.read<HealthProvider>();
|
||||
if (!provider.isEditing) {
|
||||
switch (provider.type) {
|
||||
case RecordType.weight:
|
||||
provider.updateWeight(0);
|
||||
break;
|
||||
case RecordType.sport:
|
||||
provider.updateSportProject('跑步');
|
||||
provider.updateSportDuration(0);
|
||||
break;
|
||||
case RecordType.sleep:
|
||||
provider.updateSleepStartTime('');
|
||||
provider.updateSleepEndTime('');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _deleteRecord(RecordType type) async{
|
||||
void _deleteRecord(RecordType type) async {
|
||||
final provider = context.read<HealthProvider>();
|
||||
|
||||
switch(type) {
|
||||
switch (type) {
|
||||
case RecordType.weight:
|
||||
provider.updateWeight(0);
|
||||
break;
|
||||
@@ -351,7 +331,9 @@ class _RecordDailyState extends State<RecordDaily> {
|
||||
String startTime = record.sleepStartTime;
|
||||
String endTime = record.sleepEndTime;
|
||||
num difference = calSleepMinDuration(startTime, endTime) - lastValue;
|
||||
String formattedDiff = NumberFormat('+0.00;-0.00').format(difference / 60);
|
||||
String formattedDiff = NumberFormat(
|
||||
'+0.00;-0.00',
|
||||
).format(difference / 60);
|
||||
change = '较上次 $formattedDiff ${type.unit}';
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ import 'package:fitnote/models/health.dart';
|
||||
import 'package:fitnote/provider/health_provider.dart';
|
||||
import 'package:fitnote/service/health_service.dart';
|
||||
import 'package:fitnote/utils/record_utils.dart';
|
||||
import 'package:fitnote/widget/form.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tdesign_flutter/tdesign_flutter.dart';
|
||||
|
||||
class RecordForm extends StatefulWidget {
|
||||
final RecordType type;
|
||||
@@ -63,6 +63,88 @@ class _RecordFormState extends State<RecordForm> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFormSleepContent(HealthProvider provider) {
|
||||
final startTime = provider.formItem.sleepStartTime;
|
||||
final endTime = provider.formItem.sleepEndTime;
|
||||
|
||||
return TDCellGroup(
|
||||
theme: TDCellGroupTheme.cardTheme,
|
||||
cells: [
|
||||
TDCell(
|
||||
title: '入睡时间',
|
||||
note: startTime,
|
||||
arrow: true,
|
||||
leftIcon: Icons.access_alarm,
|
||||
onClick: (click) {
|
||||
TDPicker.showDatePicker(
|
||||
context,
|
||||
title: '选择时间',
|
||||
onConfirm: (selected) {
|
||||
provider.updateSleepStartTime(getTimeFromSelectDate(selected));
|
||||
|
||||
final startTime = provider.formItem.sleepStartTime;
|
||||
final endTime = provider.formItem.sleepEndTime;
|
||||
calSleepDuration(startTime, endTime);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
useYear: false,
|
||||
useMonth: false,
|
||||
useDay: false,
|
||||
useHour: true,
|
||||
useMinute: true,
|
||||
useSecond: false,
|
||||
dateStart: [1999, 01, 01],
|
||||
dateEnd: [2099, 12, 31],
|
||||
initialDate: [
|
||||
DateTime.now().year,
|
||||
DateTime.now().month,
|
||||
DateTime.now().day,
|
||||
DateTime.now().hour,
|
||||
DateTime.now().minute,
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
TDCell(
|
||||
title: '起床时间',
|
||||
note: endTime,
|
||||
arrow: true,
|
||||
leftIcon: Icons.access_alarm,
|
||||
onClick: (click) {
|
||||
TDPicker.showDatePicker(
|
||||
context,
|
||||
title: '选择时间',
|
||||
onConfirm: (selected) {
|
||||
provider.updateSleepEndTime(getTimeFromSelectDate(selected));
|
||||
|
||||
final startTime = provider.formItem.sleepStartTime;
|
||||
final endTime = provider.formItem.sleepEndTime;
|
||||
calSleepDuration(startTime, endTime);
|
||||
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
useYear: false,
|
||||
useMonth: false,
|
||||
useDay: false,
|
||||
useHour: true,
|
||||
useMinute: true,
|
||||
useSecond: false,
|
||||
dateStart: [1999, 01, 01],
|
||||
dateEnd: [2099, 12, 31],
|
||||
initialDate: [
|
||||
DateTime.now().year,
|
||||
DateTime.now().month,
|
||||
DateTime.now().day,
|
||||
DateTime.now().hour,
|
||||
DateTime.now().minute,
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFormContent() {
|
||||
final provider = context.watch<HealthProvider>();
|
||||
|
||||
@@ -73,16 +155,24 @@ class _RecordFormState extends State<RecordForm> {
|
||||
children: [
|
||||
_buildFormTitle('记录体重'),
|
||||
SizedBox(height: 20),
|
||||
buildFormSlider(
|
||||
context: context,
|
||||
name: 'slider',
|
||||
title: '体重(Kg)',
|
||||
label: provider.formItem.weight.toString(),
|
||||
min: 70,
|
||||
max: 75,
|
||||
initialValue: 72,
|
||||
divisions: 50,
|
||||
onChanged: (value) => provider.updateWeight(value!),
|
||||
TDSlider(
|
||||
boxDecoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
sliderThemeData: TDSliderThemeData.capsule(
|
||||
context: context,
|
||||
scaleFormatter: (double value) {
|
||||
return '${value.toStringAsFixed(1)} Kg';
|
||||
},
|
||||
showThumbValue: true,
|
||||
min: AppConfig.minWeight,
|
||||
max: AppConfig.maxWeight,
|
||||
),
|
||||
value: provider.formItem.weight.toDouble(),
|
||||
leftLabel: AppConfig.minWeight.toString(),
|
||||
rightLabel: AppConfig.maxWeight.toString(),
|
||||
onChanged: (value) => provider.updateWeight(value),
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
],
|
||||
@@ -93,63 +183,62 @@ class _RecordFormState extends State<RecordForm> {
|
||||
children: [
|
||||
_buildFormTitle('记录运动'),
|
||||
SizedBox(height: 24),
|
||||
buildFormRadioGroup(
|
||||
context: context,
|
||||
title: '运动类型',
|
||||
name: 'sport',
|
||||
initialValue: provider.formItem.sportProject,
|
||||
items: AppConfig.sportProjectList,
|
||||
onChanged: (value) => provider.updateSportProject(value!),
|
||||
TDRadioGroup(
|
||||
selectId:
|
||||
AppConfig.sportProjectList
|
||||
.indexOf(provider.formItem.sportProject)
|
||||
.toString(),
|
||||
passThrough: true,
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.all(0),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
return TDRadio(
|
||||
id: index.toString(),
|
||||
title: AppConfig.sportProjectList[index],
|
||||
);
|
||||
},
|
||||
itemCount: AppConfig.sportProjectList.length,
|
||||
),
|
||||
onRadioGroupChange:
|
||||
(value) => {
|
||||
provider.updateSportProject(
|
||||
AppConfig.sportProjectList[int.parse(value!)],
|
||||
),
|
||||
},
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
buildFormSlider(
|
||||
context: context,
|
||||
name: 'slider',
|
||||
title: '时长(分钟)',
|
||||
label: provider.formItem.sportDuration.toString(),
|
||||
min: 0,
|
||||
max: 120,
|
||||
initialValue: 30,
|
||||
divisions: 12,
|
||||
onChanged: (value) => provider.updateSportDuration(value!),
|
||||
TDSlider(
|
||||
boxDecoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
sliderThemeData: TDSliderThemeData.capsule(
|
||||
context: context,
|
||||
scaleFormatter: (double value) {
|
||||
return '${value.toStringAsFixed(0)} 分钟';
|
||||
},
|
||||
showThumbValue: true,
|
||||
divisions: 11,
|
||||
min: AppConfig.minSpot,
|
||||
max: AppConfig.maxSpot,
|
||||
),
|
||||
value: provider.formItem.sportDuration.toDouble(),
|
||||
leftLabel: AppConfig.minSpot.toString(),
|
||||
rightLabel: AppConfig.maxSpot.toString(),
|
||||
onChanged: (value) => provider.updateSportDuration(value),
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
],
|
||||
);
|
||||
case RecordType.sleep:
|
||||
final startTime = provider.formItem.sleepStartTime;
|
||||
final endTime = provider.formItem.sleepEndTime;
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildFormTitle('记录睡眠'),
|
||||
SizedBox(height: 24),
|
||||
buildFormTime(
|
||||
context: context,
|
||||
name: 'startTime',
|
||||
title: '入睡时间',
|
||||
initialValue: timeOfDayToDateTime(parseTime(startTime)),
|
||||
onChanged: (time) {
|
||||
provider.updateSleepStartTime(formatTime(time!));
|
||||
final startTime = provider.formItem.sleepStartTime;
|
||||
final endTime = provider.formItem.sleepEndTime;
|
||||
calSleepDuration(startTime, endTime);
|
||||
},
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
buildFormTime(
|
||||
context: context,
|
||||
name: 'endTime',
|
||||
title: '起床时间',
|
||||
initialValue: timeOfDayToDateTime(parseTime(endTime)),
|
||||
onChanged: (time) {
|
||||
provider.updateSleepEndTime(formatTime(time!));
|
||||
final startTime = provider.formItem.sleepStartTime;
|
||||
final endTime = provider.formItem.sleepEndTime;
|
||||
calSleepDuration(startTime, endTime);
|
||||
},
|
||||
),
|
||||
_buildFormSleepContent(provider),
|
||||
SizedBox(height: 10),
|
||||
_buildSleepDuration(provider.formItem),
|
||||
SizedBox(height: 10),
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import 'package:fitnote/provider/health_provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_common/widget/common_widget.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class Welcome extends StatefulWidget {
|
||||
const Welcome({super.key});
|
||||
|
||||
Reference in New Issue
Block a user