Commit f9141f05bb6fcc1e7e270c6dd25440b54b8c05f2

Authored by 常守达
1 parent 2267996c

feat(chart): 时间展示兼容

... ... @@ -555,7 +555,7 @@ class _LatestHrvCard extends StatelessWidget {
return const SizedBox.shrink();
}
final latestDataTimeText = DateFormat('HH:mm')
final latestDataTimeText = DateFormat.jm()
.format(DateTime.fromMillisecondsSinceEpoch(latestDataTime * 1000));
return Container(
margin: EdgeInsets.only(bottom: 12),
... ...
... ... @@ -8,6 +8,7 @@ import 'package:doublefeel_flutter/l10n/l10n_extensions.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:lottie/lottie.dart';
import '../../controllers/today_controller.dart';
... ... @@ -353,7 +354,16 @@ class TodayHrvChartCard extends StatelessWidget {
if ((seconds - rounded * 3600).abs() > 30) return null;
if (rounded < 0 || rounded > 24) return null;
if (rounded % 6 != 0) return null;
return '${rounded.toString().padLeft(2, '0')}:00';
final is24Hour = MediaQuery.of(Get.context!).alwaysUse24HourFormat;
if (is24Hour) {
return '${rounded.toString().padLeft(2, '0')}:00';
} else {
final now = DateTime.now();
final time = DateTime(now.year, now.month, now.day)
.add(Duration(seconds: seconds.round()));
return DateFormat.j().format(time);
}
}
/// 按 state(1=过载/2=注意/3=正常/4=优秀)返回颜色
... ... @@ -499,10 +509,18 @@ class TodayHrvChartCard extends StatelessWidget {
/// 将距当天0点的秒数转为 HH:MM 格式(用于 tooltip)
String _formatHour(double seconds) {
final total = seconds.round();
final h = (total ~/ 3600).clamp(0, 23).toString().padLeft(2, '0');
final m = ((total % 3600) ~/ 60).toString().padLeft(2, '0');
return '$h:$m';
final is24Hour = MediaQuery.of(Get.context!).alwaysUse24HourFormat;
if (is24Hour) {
final total = seconds.round();
final h = (total ~/ 3600).clamp(0, 23).toString().padLeft(2, '0');
final m = ((total % 3600) ~/ 60).toString().padLeft(2, '0');
return '$h:$m';
} else {
final now = DateTime.now();
final time = DateTime(now.year, now.month, now.day)
.add(Duration(seconds: seconds.round()));
return DateFormat.jm().format(time);
}
}
}
... ...
... ... @@ -92,66 +92,66 @@ class PhoneLoginView extends GetView<LoginController> {
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// 手机号输入框
Obx(() {
final phoneNotEmpty =
controller.phoneInput.value.isNotEmpty;
return TextField(
controller: controller.phoneController,
keyboardType: TextInputType.phone,
maxLength: 13,
contextMenuBuilder: (BuildContext context,
EditableTextState editableTextState) {
// 获取默认的菜单项列表
List<ContextMenuButtonItem> buttonItems =
editableTextState.contextMenuButtonItems;
TextField(
controller: controller.phoneController,
keyboardType: TextInputType.phone,
maxLength: 13,
contextMenuBuilder: (BuildContext context,
EditableTextState editableTextState) {
// 获取默认的菜单项列表
List<ContextMenuButtonItem> buttonItems =
editableTextState.contextMenuButtonItems;
// 例如:只保留“粘贴” (Paste)
buttonItems.removeWhere((item) =>
item.type != ContextMenuButtonType.paste);
return AdaptiveTextSelectionToolbar.buttonItems(
anchors: editableTextState.contextMenuAnchors,
buttonItems: buttonItems,
);
},
inputFormatters: [
FilteringTextInputFormatter.allow(
RegExp(r'[0-9\s]')),
_PhoneTextInputFormatter(),
],
style: TextStyle(
color: context.colors.textPrimary,
// 例如:只保留“粘贴” (Paste)
buttonItems.removeWhere((item) =>
item.type != ContextMenuButtonType.paste);
return AdaptiveTextSelectionToolbar.buttonItems(
anchors: editableTextState.contextMenuAnchors,
buttonItems: buttonItems,
);
},
inputFormatters: [
FilteringTextInputFormatter.allow(
RegExp(r'[0-9\s]')),
_PhoneTextInputFormatter(),
],
style: TextStyle(
color: context.colors.textPrimary,
fontSize: 16,
fontWeight: FontWeight.w400,
),
decoration: InputDecoration(
hintText: l10n.phoneLoginPhoneHint,
hintStyle: TextStyle(
color: context.colors.textTertiary,
fontSize: 16,
fontWeight: FontWeight.w400,
),
decoration: InputDecoration(
hintText: l10n.phoneLoginPhoneHint,
hintStyle: TextStyle(
color: context.colors.textTertiary,
fontSize: 16,
),
counterText: '',
filled: true,
fillColor: Color(0xFFF3F3F3),
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 16,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(27),
borderSide: BorderSide.none,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(27),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(27),
borderSide: BorderSide(
color: context.colors.primary,
width: 1,
),
counterText: '',
filled: true,
fillColor: const Color(0xFFF3F3F3),
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 16,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(27),
borderSide: BorderSide.none,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(27),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(27),
borderSide: BorderSide(
color: context.colors.primary,
width: 1,
),
suffixIcon: phoneNotEmpty
),
suffixIcon: Obx(() {
final phoneNotEmpty =
controller.phoneInput.value.isNotEmpty;
return phoneNotEmpty
? GestureDetector(
onTap: controller.phoneController.clear,
child: Icon(
... ... @@ -160,82 +160,84 @@ class PhoneLoginView extends GetView<LoginController> {
size: 18,
),
)
: null,
),
);
}),
: const SizedBox.shrink();
}),
),
),
const SizedBox(height: 16),
// 验证码输入框(内嵌发送按钮)
Obx(() {
final countdown = controller.countdownSeconds.value;
final isSending = controller.isSendingCode.value;
final isCounting = countdown > 0;
final hasSent = controller.hasSentCode.value;
String btnText = hasSent
? l10n.phoneLoginResend
: l10n.phoneLoginSendCode;
if (isSending) {
btnText = l10n.phoneLoginSending;
} else if (isCounting) {
btnText = l10n.phoneLoginSentCountdown(countdown);
}
return TextField(
controller: controller.codeController,
focusNode: controller.codeFocusNode,
keyboardType: TextInputType.number,
maxLength: 6,
contextMenuBuilder: (BuildContext context,
EditableTextState editableTextState) {
// 获取默认的菜单项列表
List<ContextMenuButtonItem> buttonItems =
editableTextState.contextMenuButtonItems;
TextField(
controller: controller.codeController,
focusNode: controller.codeFocusNode,
keyboardType: TextInputType.number,
maxLength: 6,
contextMenuBuilder: (BuildContext context,
EditableTextState editableTextState) {
// 获取默认的菜单项列表
List<ContextMenuButtonItem> buttonItems =
editableTextState.contextMenuButtonItems;
// 例如:只保留“粘贴” (Paste)
buttonItems.removeWhere((item) =>
item.type != ContextMenuButtonType.paste);
return AdaptiveTextSelectionToolbar.buttonItems(
anchors: editableTextState.contextMenuAnchors,
buttonItems: buttonItems,
);
},
style: TextStyle(
color: context.colors.textPrimary,
// 例如:只保留“粘贴” (Paste)
buttonItems.removeWhere((item) =>
item.type != ContextMenuButtonType.paste);
return AdaptiveTextSelectionToolbar.buttonItems(
anchors: editableTextState.contextMenuAnchors,
buttonItems: buttonItems,
);
},
style: TextStyle(
color: context.colors.textPrimary,
fontSize: 16,
fontWeight: FontWeight.w400,
),
decoration: InputDecoration(
hintText: l10n.phoneLoginCodeHint,
hintStyle: TextStyle(
color: context.colors.textTertiary,
fontSize: 16,
fontWeight: FontWeight.w400,
),
decoration: InputDecoration(
hintText: l10n.phoneLoginCodeHint,
hintStyle: TextStyle(
color: context.colors.textTertiary,
fontSize: 16,
),
counterText: '',
filled: true,
fillColor: Color(0xFFF3F3F3),
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 16,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(27),
borderSide: BorderSide.none,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(27),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(27),
borderSide: BorderSide(
color: context.colors.primary,
width: 1,
),
counterText: '',
filled: true,
fillColor: const Color(0xFFF3F3F3),
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 16,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(27),
borderSide: BorderSide.none,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(27),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(27),
borderSide: BorderSide(
color: context.colors.primary,
width: 1,
),
suffixIcon: Padding(
),
suffixIcon: Obx(() {
final countdown =
controller.countdownSeconds.value;
final isSending = controller.isSendingCode.value;
final isCounting = countdown > 0;
final hasSent = controller.hasSentCode.value;
String btnText = hasSent
? l10n.phoneLoginResend
: l10n.phoneLoginSendCode;
if (isSending) {
btnText = l10n.phoneLoginSending;
} else if (isCounting) {
btnText =
l10n.phoneLoginSentCountdown(countdown);
}
return Padding(
padding: const EdgeInsets.only(right: 12),
child: TextButton(
onPressed: controller.canRequestCode
... ... @@ -257,12 +259,12 @@ class PhoneLoginView extends GetView<LoginController> {
),
child: Text(btnText),
),
),
suffixIconConstraints:
const BoxConstraints(minWidth: 0),
),
);
}),
);
}),
suffixIconConstraints:
const BoxConstraints(minWidth: 0),
),
),
const SizedBox(height: 56),
... ...