feat(app): 完善播放协议搜索与空状态
This commit is contained in:
1 parent
671e0ce143
commit
ef29fc5f15
85 files changed
+2976
-768
No files matched your search
@@ -0,0 +1,106 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../core/theme/app_colors.dart';
|
||||
import '../../core/theme/app_dimens.dart';
|
||||
import '../../l10n/app_localizations.dart';
|
||||
import 'account_deactivate_logic.dart';
|
||||
|
||||
/// 注销原因:固定选项在标题下换行排列,补充说明留在输入框内。
|
||||
class DeactivateReasonField extends StatelessWidget {
|
||||
const DeactivateReasonField({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
final TextEditingController controller;
|
||||
final VoidCallback onChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final options = _options(l10n);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
l10n.accountDeactivateReason,
|
||||
style: const TextStyle(
|
||||
color: AppColors.textPrimary,
|
||||
fontSize: AppDimens.listTitle,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppDimens.spaceSm),
|
||||
Wrap(
|
||||
spacing: AppDimens.spaceSm,
|
||||
runSpacing: AppDimens.spaceSm,
|
||||
children: [
|
||||
for (var index = 0; index < options.length; index++)
|
||||
_chip(options[index], index, options),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: AppDimens.spaceMd),
|
||||
TextField(
|
||||
key: const Key('account-deactivate-reason'),
|
||||
controller: controller,
|
||||
maxLength: AccountDeactivateLogic.maxReasonLength,
|
||||
minLines: 3,
|
||||
maxLines: 4,
|
||||
textAlignVertical: TextAlignVertical.top,
|
||||
onChanged: (_) => onChanged(),
|
||||
decoration: InputDecoration(
|
||||
hintText: l10n.accountDeactivateReasonHint,
|
||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||
alignLabelWithHint: true,
|
||||
contentPadding: const EdgeInsets.all(AppDimens.cardPadding),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _chip(_ReasonOption option, int index, List<_ReasonOption> options) {
|
||||
final selected = option.custom
|
||||
? _customSelected(options)
|
||||
: controller.text == option.label;
|
||||
return ChoiceChip(
|
||||
key: Key('account-deactivate-reason-$index'),
|
||||
label: Text(option.label),
|
||||
selected: selected,
|
||||
onSelected: (_) => _select(option),
|
||||
);
|
||||
}
|
||||
|
||||
bool _customSelected(List<_ReasonOption> options) {
|
||||
final text = controller.text.trim();
|
||||
if (text.isEmpty) return false;
|
||||
return options
|
||||
.where((item) => !item.custom)
|
||||
.every((item) => item.label != text);
|
||||
}
|
||||
|
||||
void _select(_ReasonOption option) {
|
||||
controller.text = option.custom ? '' : option.label;
|
||||
controller.selection = TextSelection.collapsed(
|
||||
offset: controller.text.length,
|
||||
);
|
||||
onChanged();
|
||||
}
|
||||
|
||||
List<_ReasonOption> _options(AppLocalizations l10n) => [
|
||||
_ReasonOption(l10n.accountDeactivateReasonUnused),
|
||||
_ReasonOption(l10n.accountDeactivateReasonContent),
|
||||
_ReasonOption(l10n.accountDeactivateReasonPrivacy),
|
||||
_ReasonOption(l10n.accountDeactivateReasonOther, custom: true),
|
||||
];
|
||||
}
|
||||
|
||||
class _ReasonOption {
|
||||
const _ReasonOption(this.label, {this.custom = false});
|
||||
|
||||
final String label;
|
||||
final bool custom;
|
||||
}
|
||||
@@ -10,132 +10,119 @@ import 'account_devices_page.dart';
|
||||
import 'account_export_page.dart';
|
||||
import 'account_profile_page.dart';
|
||||
|
||||
const _accountTitleWidth = 64.0;
|
||||
|
||||
/// 我的页账号入口卡;只在登录态由父页面展示。
|
||||
/// 设置页账号入口,登录后按列表展示。
|
||||
class AccountEntryCard extends StatelessWidget {
|
||||
const AccountEntryCard({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final rows = <_AccountRow>[
|
||||
_AccountRow(
|
||||
key: const Key('mine-account-profile'),
|
||||
icon: Icons.person_outline_rounded,
|
||||
title: l10n.accountProfile,
|
||||
page: const AccountProfilePage(),
|
||||
),
|
||||
_AccountRow(
|
||||
key: const Key('mine-account-devices'),
|
||||
icon: Icons.devices_other_rounded,
|
||||
title: l10n.accountDevices,
|
||||
page: const AccountDevicesPage(),
|
||||
),
|
||||
_AccountRow(
|
||||
key: const Key('mine-account-export'),
|
||||
icon: Icons.file_download_outlined,
|
||||
title: l10n.accountDataExport,
|
||||
page: const AccountExportPage(),
|
||||
),
|
||||
_AccountRow(
|
||||
key: const Key('mine-account-deactivate'),
|
||||
icon: Icons.person_remove_outlined,
|
||||
title: l10n.accountDeactivate,
|
||||
page: const AccountDeactivatePage(),
|
||||
),
|
||||
];
|
||||
return Material(
|
||||
key: const Key('mine-account-card'),
|
||||
color: AppColors.card,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
borderRadius: BorderRadius.circular(AppDimens.cardRadius),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: SizedBox(
|
||||
height: AppDimens.accountCardHeight,
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: _accountTitleWidth,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 12),
|
||||
child: Text(
|
||||
l10n.accountCenter,
|
||||
style: const TextStyle(
|
||||
fontSize: AppDimens.caption,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const VerticalDivider(
|
||||
width: 1,
|
||||
color: AppColors.outlineVariantDark,
|
||||
),
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
_entry(
|
||||
context,
|
||||
key: const Key('mine-account-profile'),
|
||||
icon: Icons.person_outline_rounded,
|
||||
title: l10n.accountProfile,
|
||||
page: const AccountProfilePage(),
|
||||
divider: true,
|
||||
),
|
||||
_entry(
|
||||
context,
|
||||
key: const Key('mine-account-devices'),
|
||||
icon: Icons.devices_other_rounded,
|
||||
title: l10n.accountDevices,
|
||||
page: const AccountDevicesPage(),
|
||||
divider: true,
|
||||
),
|
||||
_entry(
|
||||
context,
|
||||
key: const Key('mine-account-export'),
|
||||
icon: Icons.file_download_outlined,
|
||||
title: l10n.accountDataExport,
|
||||
page: const AccountExportPage(),
|
||||
divider: true,
|
||||
),
|
||||
_entry(
|
||||
context,
|
||||
key: const Key('mine-account-deactivate'),
|
||||
icon: Icons.person_remove_outlined,
|
||||
title: l10n.accountDeactivate,
|
||||
page: const AccountDeactivatePage(),
|
||||
divider: false,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _entry(
|
||||
BuildContext context, {
|
||||
required Key key,
|
||||
required IconData icon,
|
||||
required String title,
|
||||
required Widget page,
|
||||
required bool divider,
|
||||
}) {
|
||||
return Expanded(
|
||||
child: Row(
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
key: key,
|
||||
onTap: () => Navigator.of(
|
||||
context,
|
||||
).push<void>(MaterialPageRoute(builder: (_) => page)),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
color: AppColors.blueSoft,
|
||||
size: AppDimens.accountIcon,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
title,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontSize: AppDimens.accountLabel,
|
||||
height: 1.15,
|
||||
),
|
||||
),
|
||||
],
|
||||
for (var index = 0; index < rows.length; index++) ...[
|
||||
if (index > 0)
|
||||
const Divider(
|
||||
height: AppDimens.mineGridDividerWidth,
|
||||
thickness: AppDimens.mineGridDividerWidth,
|
||||
color: AppColors.outlineVariantDark,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (divider)
|
||||
const VerticalDivider(
|
||||
width: 1,
|
||||
color: AppColors.outlineVariantDark,
|
||||
),
|
||||
_AccountListTile(row: rows[index]),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AccountRow {
|
||||
const _AccountRow({
|
||||
required this.key,
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.page,
|
||||
});
|
||||
|
||||
final Key key;
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final Widget page;
|
||||
}
|
||||
|
||||
class _AccountListTile extends StatelessWidget {
|
||||
const _AccountListTile({required this.row});
|
||||
|
||||
final _AccountRow row;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
key: row.key,
|
||||
onTap: () => Navigator.of(
|
||||
context,
|
||||
).push<void>(MaterialPageRoute(builder: (_) => row.page)),
|
||||
child: SizedBox(
|
||||
height: AppDimens.mineMoreRowHeight,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: AppDimens.spaceLg),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
row.icon,
|
||||
color: AppColors.blueSoft,
|
||||
size: AppDimens.accountIcon,
|
||||
),
|
||||
const SizedBox(width: AppDimens.spaceMd),
|
||||
Expanded(
|
||||
child: Text(
|
||||
row.title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: AppColors.textPrimary,
|
||||
fontSize: AppDimens.listTitle,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Icon(
|
||||
Icons.chevron_right_rounded,
|
||||
color: AppColors.textSecondary,
|
||||
size: AppDimens.accountIcon,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user