fix(client): 审计修复——device_limit 相对时间走 relativeTime 单源+裸 Icons 换 PangolinIcons+Payment onDone 三处接通+内联双语文案收进 AppText
This commit is contained in:
@@ -565,6 +565,9 @@ abstract class AppText {
|
|||||||
String get switchPayMethod; // 换一种支付方式 / Switch payment method
|
String get switchPayMethod; // 换一种支付方式 / Switch payment method
|
||||||
String get cancelOrder; // 取消订单 / Cancel order
|
String get cancelOrder; // 取消订单 / Cancel order
|
||||||
String get qrNotSupported;
|
String get qrNotSupported;
|
||||||
|
String get orderCreateFailed; // 下单失败,请稍后重试 / Failed to create order, please retry(errorZh/En 为空时兜底)
|
||||||
|
String get payUnsupportedRenderType; // 暂不支持,请换支付方式 / Unsupported, switch payment method
|
||||||
|
String get payNoActiveOrder; // 暂无进行中的订单 / No active order
|
||||||
|
|
||||||
// ── 支付渠道 switch + 订单 ──
|
// ── 支付渠道 switch + 订单 ──
|
||||||
String get payChannel;
|
String get payChannel;
|
||||||
|
|||||||
@@ -427,6 +427,12 @@ class StringsEn extends AppText {
|
|||||||
String get cancelOrder => 'Cancel order';
|
String get cancelOrder => 'Cancel order';
|
||||||
@override
|
@override
|
||||||
String get qrNotSupported => 'Copy and open in Alipay';
|
String get qrNotSupported => 'Copy and open in Alipay';
|
||||||
|
@override
|
||||||
|
String get orderCreateFailed => 'Failed to create order, please retry';
|
||||||
|
@override
|
||||||
|
String get payUnsupportedRenderType => 'Unsupported, switch payment method';
|
||||||
|
@override
|
||||||
|
String get payNoActiveOrder => 'No active order';
|
||||||
|
|
||||||
// ── 支付渠道 switch + 订单 ──
|
// ── 支付渠道 switch + 订单 ──
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -428,6 +428,12 @@ class StringsEs extends AppText {
|
|||||||
String get cancelOrder => 'Cancelar pedido';
|
String get cancelOrder => 'Cancelar pedido';
|
||||||
@override
|
@override
|
||||||
String get qrNotSupported => 'Copia el contenido y ábrelo en Alipay';
|
String get qrNotSupported => 'Copia el contenido y ábrelo en Alipay';
|
||||||
|
@override
|
||||||
|
String get orderCreateFailed => 'Error al crear el pedido, inténtalo de nuevo más tarde';
|
||||||
|
@override
|
||||||
|
String get payUnsupportedRenderType => 'No compatible, cambia el método de pago';
|
||||||
|
@override
|
||||||
|
String get payNoActiveOrder => 'No hay pedidos activos';
|
||||||
|
|
||||||
// ── 支付渠道 switch + 订单 ──
|
// ── 支付渠道 switch + 订单 ──
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -428,6 +428,12 @@ class StringsJa extends AppText {
|
|||||||
String get cancelOrder => '注文をキャンセル';
|
String get cancelOrder => '注文をキャンセル';
|
||||||
@override
|
@override
|
||||||
String get qrNotSupported => '内容をコピーしてAlipayで開いてください';
|
String get qrNotSupported => '内容をコピーしてAlipayで開いてください';
|
||||||
|
@override
|
||||||
|
String get orderCreateFailed => '注文の作成に失敗しました。しばらくして再試行してください';
|
||||||
|
@override
|
||||||
|
String get payUnsupportedRenderType => '未対応です。支払い方法を変更してください';
|
||||||
|
@override
|
||||||
|
String get payNoActiveOrder => '進行中の注文はありません';
|
||||||
|
|
||||||
// ── 支付渠道 switch + 订单 ──
|
// ── 支付渠道 switch + 订单 ──
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -428,6 +428,12 @@ class StringsKo extends AppText {
|
|||||||
String get cancelOrder => '주문 취소';
|
String get cancelOrder => '주문 취소';
|
||||||
@override
|
@override
|
||||||
String get qrNotSupported => '내용을 복사해 Alipay에서 여세요';
|
String get qrNotSupported => '내용을 복사해 Alipay에서 여세요';
|
||||||
|
@override
|
||||||
|
String get orderCreateFailed => '주문 생성에 실패했습니다. 잠시 후 다시 시도하세요';
|
||||||
|
@override
|
||||||
|
String get payUnsupportedRenderType => '지원되지 않습니다. 결제 방법을 변경하세요';
|
||||||
|
@override
|
||||||
|
String get payNoActiveOrder => '진행 중인 주문이 없습니다';
|
||||||
|
|
||||||
// ── 支付渠道 switch + 订单 ──
|
// ── 支付渠道 switch + 订单 ──
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -428,6 +428,12 @@ class StringsRu extends AppText {
|
|||||||
String get cancelOrder => 'Отменить заказ';
|
String get cancelOrder => 'Отменить заказ';
|
||||||
@override
|
@override
|
||||||
String get qrNotSupported => 'Скопируйте и откройте в Alipay';
|
String get qrNotSupported => 'Скопируйте и откройте в Alipay';
|
||||||
|
@override
|
||||||
|
String get orderCreateFailed => 'Не удалось создать заказ, повторите попытку позже';
|
||||||
|
@override
|
||||||
|
String get payUnsupportedRenderType => 'Не поддерживается, смените способ оплаты';
|
||||||
|
@override
|
||||||
|
String get payNoActiveOrder => 'Нет активных заказов';
|
||||||
|
|
||||||
// ── 支付渠道 switch + 订单 ──
|
// ── 支付渠道 switch + 订单 ──
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -421,6 +421,12 @@ class StringsZh extends AppText {
|
|||||||
String get cancelOrder => '取消订单';
|
String get cancelOrder => '取消订单';
|
||||||
@override
|
@override
|
||||||
String get qrNotSupported => '请复制内容后在支付宝内打开';
|
String get qrNotSupported => '请复制内容后在支付宝内打开';
|
||||||
|
@override
|
||||||
|
String get orderCreateFailed => '下单失败,请稍后重试';
|
||||||
|
@override
|
||||||
|
String get payUnsupportedRenderType => '暂不支持,请换支付方式';
|
||||||
|
@override
|
||||||
|
String get payNoActiveOrder => '暂无进行中的订单';
|
||||||
|
|
||||||
// ── 支付渠道 switch + 订单 ──
|
// ── 支付渠道 switch + 订单 ──
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -64,8 +64,8 @@ class AccountPage extends ConsumerWidget {
|
|||||||
NavView.purchase,
|
NavView.purchase,
|
||||||
PurchaseScreen(
|
PurchaseScreen(
|
||||||
t: t,
|
t: t,
|
||||||
onOrderCreated: () => Navigator.of(context)
|
onOrderCreated: () => Navigator.of(context).push(MaterialPageRoute(
|
||||||
.push(MaterialPageRoute(builder: (_) => PaymentScreen(t: t))),
|
builder: (_) => PaymentScreen(t: t, onDone: () => Navigator.of(context).pop()))),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -87,8 +87,9 @@ class AccountPage extends ConsumerWidget {
|
|||||||
NavView.purchase,
|
NavView.purchase,
|
||||||
PurchaseScreen(
|
PurchaseScreen(
|
||||||
t: t,
|
t: t,
|
||||||
onOrderCreated: () => Navigator.of(context)
|
onOrderCreated: () => Navigator.of(context).push(MaterialPageRoute(
|
||||||
.push(MaterialPageRoute(builder: (_) => PaymentScreen(t: t))),
|
builder: (_) =>
|
||||||
|
PaymentScreen(t: t, onDone: () => Navigator.of(context).pop()))),
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
Divider(height: 1, color: c.border),
|
Divider(height: 1, color: c.border),
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import '../services/auth_api.dart';
|
|||||||
import '../services/device_identity.dart';
|
import '../services/device_identity.dart';
|
||||||
import '../state/account_providers.dart';
|
import '../state/account_providers.dart';
|
||||||
import '../state/auth_provider.dart';
|
import '../state/auth_provider.dart';
|
||||||
|
import '../widgets/pangolin_icons.dart';
|
||||||
|
|
||||||
class DeviceLimitScreen extends ConsumerStatefulWidget {
|
class DeviceLimitScreen extends ConsumerStatefulWidget {
|
||||||
const DeviceLimitScreen({super.key, required this.t, required this.limit});
|
const DeviceLimitScreen({super.key, required this.t, required this.limit});
|
||||||
@@ -84,7 +85,7 @@ class _DeviceLimitScreenState extends ConsumerState<DeviceLimitScreen> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 32),
|
const SizedBox(height: 32),
|
||||||
Icon(Icons.devices_other_rounded, size: 40, color: c.accent),
|
Icon(PangolinIcons.monitorSmartphone, size: 40, color: c.accent),
|
||||||
const SizedBox(height: 14),
|
const SizedBox(height: 14),
|
||||||
Text(t.deviceLimitTitle,
|
Text(t.deviceLimitTitle,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
@@ -97,7 +98,7 @@ class _DeviceLimitScreenState extends ConsumerState<DeviceLimitScreen> {
|
|||||||
if (removableCount > 0)
|
if (removableCount > 0)
|
||||||
OutlinedButton.icon(
|
OutlinedButton.icon(
|
||||||
onPressed: _busyUuid != null ? null : () => _removeOldest(devices, currentId),
|
onPressed: _busyUuid != null ? null : () => _removeOldest(devices, currentId),
|
||||||
icon: const Icon(Icons.history_rounded, size: 18),
|
icon: const Icon(PangolinIcons.clock, size: 18),
|
||||||
label: Text(t.deviceLimitRemoveOldest),
|
label: Text(t.deviceLimitRemoveOldest),
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
foregroundColor: c.accent,
|
foregroundColor: c.accent,
|
||||||
@@ -136,7 +137,9 @@ class _DeviceLimitScreenState extends ConsumerState<DeviceLimitScreen> {
|
|||||||
final busy = _busyUuid == d.uuid;
|
final busy = _busyUuid == d.uuid;
|
||||||
final sub = d.online
|
final sub = d.online
|
||||||
? t.devOnline
|
? t.devOnline
|
||||||
: (d.lastSeen != null ? '${t.devLastOnline} ${_rel(d.lastSeen!)}' : t.devOffline);
|
: (d.lastSeen != null
|
||||||
|
? '${t.devLastOnline} ${t.lang.relativeTime(DateTime.now().difference(d.lastSeen!.toLocal()))}'
|
||||||
|
: t.devOffline);
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@@ -184,18 +187,9 @@ class _DeviceLimitScreenState extends ConsumerState<DeviceLimitScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static IconData _platformIcon(DevicePlatform p) => switch (p) {
|
static IconData _platformIcon(DevicePlatform p) => switch (p) {
|
||||||
DevicePlatform.ios || DevicePlatform.android => Icons.smartphone_rounded,
|
DevicePlatform.ios || DevicePlatform.android => PangolinIcons.smartphone,
|
||||||
DevicePlatform.windows || DevicePlatform.linux => Icons.computer_rounded,
|
DevicePlatform.windows || DevicePlatform.linux => PangolinIcons.monitor,
|
||||||
DevicePlatform.macos => Icons.laptop_mac_rounded,
|
DevicePlatform.macos => PangolinIcons.laptop,
|
||||||
DevicePlatform.unknown => Icons.devices_other_rounded,
|
DevicePlatform.unknown => PangolinIcons.monitorSmartphone,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 相对时间(粗粒度):x 分钟/小时/天前。
|
|
||||||
static String _rel(DateTime t) {
|
|
||||||
final d = DateTime.now().toUtc().difference(t.toUtc());
|
|
||||||
if (d.inMinutes < 1) return '刚刚';
|
|
||||||
if (d.inMinutes < 60) return '${d.inMinutes} 分钟前';
|
|
||||||
if (d.inHours < 24) return '${d.inHours} 小时前';
|
|
||||||
return '${d.inDays} 天前';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -252,7 +252,17 @@ class _OrderDetailView extends ConsumerWidget {
|
|||||||
builder: (_) => PurchaseScreen(
|
builder: (_) => PurchaseScreen(
|
||||||
t: t,
|
t: t,
|
||||||
onOrderCreated: () => Navigator.of(context).push(
|
onOrderCreated: () => Navigator.of(context).push(
|
||||||
MaterialPageRoute(builder: (_) => PaymentScreen(t: t)),
|
MaterialPageRoute(
|
||||||
|
builder: (_) => PaymentScreen(
|
||||||
|
t: t,
|
||||||
|
onDone: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
// 支付页「完成/取消」回到订单详情:失效列表 + 本单详情,拉新状态。
|
||||||
|
ref.invalidate(ordersProvider);
|
||||||
|
ref.invalidate(orderDetailProvider(order.orderNo));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -209,8 +209,9 @@ class _PaymentScreenState extends ConsumerState<PaymentScreen> {
|
|||||||
Text(t.qrNotSupported, style: PangolinText.caption.copyWith(color: c.fg3)),
|
Text(t.qrNotSupported, style: PangolinText.caption.copyWith(color: c.fg3)),
|
||||||
]));
|
]));
|
||||||
default:
|
default:
|
||||||
renderBody = _card(c, child: Text('${session.renderType}: ${_zh ? "暂不支持,请换支付方式" : "Unsupported, switch method"}',
|
renderBody = _card(c,
|
||||||
style: PangolinText.body.copyWith(color: c.fg2)));
|
child: Text('${session.renderType}: ${t.payUnsupportedRenderType}',
|
||||||
|
style: PangolinText.body.copyWith(color: c.fg2)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return ListView(padding: const EdgeInsets.fromLTRB(20, 14, 20, 24), children: [
|
return ListView(padding: const EdgeInsets.fromLTRB(20, 14, 20, 24), children: [
|
||||||
@@ -306,7 +307,7 @@ class _PaymentScreenState extends ConsumerState<PaymentScreen> {
|
|||||||
PaymentPhase.succeeded => _succeeded(context, s),
|
PaymentPhase.succeeded => _succeeded(context, s),
|
||||||
PaymentPhase.failed => _failed(context, ref, s),
|
PaymentPhase.failed => _failed(context, ref, s),
|
||||||
PaymentPhase.idle => Center(
|
PaymentPhase.idle => Center(
|
||||||
child: Text(_zh ? '暂无进行中的订单' : 'No active order',
|
child: Text(t.payNoActiveOrder,
|
||||||
style: PangolinText.body.copyWith(color: c.fg3))),
|
style: PangolinText.body.copyWith(color: c.fg3))),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ class PurchaseScreen extends ConsumerStatefulWidget {
|
|||||||
|
|
||||||
class _PurchaseScreenState extends ConsumerState<PurchaseScreen> {
|
class _PurchaseScreenState extends ConsumerState<PurchaseScreen> {
|
||||||
AppText get t => widget.t;
|
AppText get t => widget.t;
|
||||||
|
bool get _zh => t.lang == AppLang.zh;
|
||||||
|
|
||||||
// 支付渠道:默认 USDT·加密货币。切换即刷新套餐卡显价币种。
|
// 支付渠道:默认 USDT·加密货币。切换即刷新套餐卡显价币种。
|
||||||
PayChannel _channel = PayChannel.usdt;
|
PayChannel _channel = PayChannel.usdt;
|
||||||
@@ -59,9 +60,7 @@ class _PurchaseScreenState extends ConsumerState<PurchaseScreen> {
|
|||||||
// 下单失败给可见 toast,别让用户以为「点了没反应」。
|
// 下单失败给可见 toast,别让用户以为「点了没反应」。
|
||||||
showPangolinToast(
|
showPangolinToast(
|
||||||
context,
|
context,
|
||||||
t.lang == AppLang.zh
|
(_zh ? st.errorZh : st.errorEn) ?? t.orderCreateFailed,
|
||||||
? (st.errorZh ?? '下单失败,请稍后重试')
|
|
||||||
: (st.errorEn ?? 'Failed to create order, please retry'),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,8 +76,7 @@ class _PurchaseScreenState extends ConsumerState<PurchaseScreen> {
|
|||||||
error: (_, __) => Center(
|
error: (_, __) => Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(40),
|
padding: const EdgeInsets.all(40),
|
||||||
child: Text(t.lang == AppLang.zh ? '加载失败,请重试' : 'Failed to load, retry',
|
child: Text(t.lang.loadFailedRetry, style: PangolinText.body.copyWith(color: c.fg3)),
|
||||||
style: PangolinText.body.copyWith(color: c.fg3)),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
data: (items) {
|
data: (items) {
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ class UpdateBanner extends ConsumerWidget {
|
|||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () =>
|
onPressed: () =>
|
||||||
ref.read(dismissedUpdateVersionProvider.notifier).state = info.latestVersion,
|
ref.read(dismissedUpdateVersionProvider.notifier).state = info.latestVersion,
|
||||||
icon: Icon(Icons.close, size: 18, color: c.fg3),
|
icon: Icon(PangolinIcons.x, size: 18, color: c.fg3),
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
constraints: const BoxConstraints(minWidth: 36, minHeight: 36),
|
constraints: const BoxConstraints(minWidth: 36, minHeight: 36),
|
||||||
tooltip: t.updateLater,
|
tooltip: t.updateLater,
|
||||||
|
|||||||
Reference in New Issue
Block a user