292 lines
9.2 KiB
Dart
292 lines
9.2 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:patbond_flutter/core/theme/app_theme.dart';
|
|
import 'package:patbond_flutter/data/demo_data.dart';
|
|
import 'package:patbond_flutter/models/models.dart';
|
|
import 'package:patbond_flutter/widgets/common.dart';
|
|
|
|
class ServicesPage extends StatefulWidget {
|
|
const ServicesPage({
|
|
required this.showPersonal,
|
|
required this.locationWeather,
|
|
super.key,
|
|
});
|
|
|
|
final bool showPersonal;
|
|
final LocationWeather locationWeather;
|
|
|
|
@override
|
|
State<ServicesPage> createState() => _ServicesPageState();
|
|
}
|
|
|
|
class _ServicesPageState extends State<ServicesPage> {
|
|
late bool personal;
|
|
String query = '';
|
|
String filter = '全部';
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
personal = widget.showPersonal;
|
|
}
|
|
|
|
@override
|
|
void didUpdateWidget(covariant ServicesPage oldWidget) {
|
|
super.didUpdateWidget(oldWidget);
|
|
if (oldWidget.showPersonal != widget.showPersonal) {
|
|
personal = widget.showPersonal;
|
|
}
|
|
}
|
|
|
|
List<ServiceProviderModel> get providers {
|
|
final keyword = query.trim().toLowerCase();
|
|
var list = serviceProviders.where((provider) {
|
|
final matchesType = personal
|
|
? provider.kind == ProviderKind.personal
|
|
: provider.kind != ProviderKind.personal;
|
|
final matchesQuery =
|
|
keyword.isEmpty ||
|
|
provider.name.toLowerCase().contains(keyword) ||
|
|
provider.description.toLowerCase().contains(keyword) ||
|
|
provider.tags.any((tag) => tag.toLowerCase().contains(keyword));
|
|
return matchesType && matchesQuery;
|
|
}).toList();
|
|
|
|
if (!personal) {
|
|
if (filter == '距离优先') {
|
|
list.sort(
|
|
(a, b) => double.parse(
|
|
a.distance.replaceAll('km', ''),
|
|
).compareTo(double.parse(b.distance.replaceAll('km', ''))),
|
|
);
|
|
} else if (filter == '24H急诊') {
|
|
list = list.where((item) => item.tags.contains('24H急诊')).toList();
|
|
} else if (filter == '美容洗护') {
|
|
list = list
|
|
.where((item) => item.kind == ProviderKind.grooming)
|
|
.toList();
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
|
|
Future<void> book(ServiceProviderModel provider) async {
|
|
final confirmed = await showDialog<bool>(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
title: const Text('确认预约'),
|
|
content: Text('预约 ${provider.name}\n服务价格:¥${provider.price} 起'),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context, false),
|
|
child: const Text('取消'),
|
|
),
|
|
FilledButton(
|
|
onPressed: () => Navigator.pop(context, true),
|
|
child: const Text('确认'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
if (confirmed == true && mounted) {
|
|
ScaffoldMessenger.of(
|
|
context,
|
|
).showSnackBar(SnackBar(content: Text('已提交「${provider.name}」预约(演示)')));
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return ListView(
|
|
padding: const EdgeInsets.fromLTRB(16, 12, 16, 28),
|
|
children: [
|
|
SegmentedButton<bool>(
|
|
segments: const [
|
|
ButtonSegment(
|
|
value: false,
|
|
icon: Icon(Icons.apartment_outlined),
|
|
label: Text('专业机构'),
|
|
),
|
|
ButtonSegment(
|
|
value: true,
|
|
icon: Icon(Icons.person_pin_circle_outlined),
|
|
label: Text('个人服务'),
|
|
),
|
|
],
|
|
selected: {personal},
|
|
showSelectedIcon: false,
|
|
onSelectionChanged: (value) => setState(() {
|
|
personal = value.first;
|
|
filter = '全部';
|
|
}),
|
|
),
|
|
const SizedBox(height: 10),
|
|
Row(
|
|
children: [
|
|
const Icon(
|
|
Icons.near_me_outlined,
|
|
size: 17,
|
|
color: AppColors.primary,
|
|
),
|
|
const SizedBox(width: 5),
|
|
Text(
|
|
'正在展示 ${widget.locationWeather.displayArea} 附近的服务',
|
|
style: Theme.of(context).textTheme.bodySmall,
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 14),
|
|
TextField(
|
|
onChanged: (value) => setState(() => query = value),
|
|
decoration: InputDecoration(
|
|
hintText: personal ? '搜索遛狗、寄养或上门喂养…' : '搜索宠物医院、美容或服务…',
|
|
prefixIcon: const Icon(Icons.search),
|
|
suffixIcon: IconButton(
|
|
tooltip: '筛选',
|
|
onPressed: () => ScaffoldMessenger.of(
|
|
context,
|
|
).showSnackBar(const SnackBar(content: Text('可使用下方快捷筛选条件'))),
|
|
icon: const Icon(Icons.tune),
|
|
),
|
|
),
|
|
),
|
|
if (!personal) ...[
|
|
const SizedBox(height: 12),
|
|
SingleChildScrollView(
|
|
scrollDirection: Axis.horizontal,
|
|
child: Row(
|
|
children: ['全部', '距离优先', '24H急诊', '美容洗护']
|
|
.map(
|
|
(value) => Padding(
|
|
padding: const EdgeInsets.only(right: 8),
|
|
child: ChoiceChip(
|
|
label: Text(value),
|
|
selected: filter == value,
|
|
onSelected: (_) => setState(() => filter = value),
|
|
),
|
|
),
|
|
)
|
|
.toList(),
|
|
),
|
|
),
|
|
],
|
|
const SizedBox(height: 16),
|
|
if (providers.isEmpty)
|
|
const EmptyState(message: '没有找到符合条件的服务')
|
|
else
|
|
...providers.map(
|
|
(provider) => Padding(
|
|
padding: const EdgeInsets.only(bottom: 14),
|
|
child: _ServiceCard(
|
|
provider: provider,
|
|
onBook: () => book(provider),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class _ServiceCard extends StatelessWidget {
|
|
const _ServiceCard({required this.provider, required this.onBook});
|
|
|
|
final ServiceProviderModel provider;
|
|
final VoidCallback onBook;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Card(
|
|
clipBehavior: Clip.antiAlias,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Stack(
|
|
children: [
|
|
AspectRatio(
|
|
aspectRatio: 16 / 7,
|
|
child: RemoteImage(url: provider.image),
|
|
),
|
|
if (provider.verified)
|
|
const Positioned(
|
|
left: 12,
|
|
top: 12,
|
|
child: TagPill('认证服务', color: AppColors.success),
|
|
),
|
|
Positioned(
|
|
right: 12,
|
|
bottom: 10,
|
|
child: DecoratedBox(
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withAlpha(235),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 9,
|
|
vertical: 5,
|
|
),
|
|
child: Text(
|
|
'⭐ ${provider.rating}',
|
|
style: const TextStyle(fontWeight: FontWeight.w800),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Text(
|
|
provider.name,
|
|
style: Theme.of(context).textTheme.titleMedium,
|
|
),
|
|
),
|
|
Text(
|
|
provider.distance,
|
|
style: Theme.of(context).textTheme.bodySmall,
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 6),
|
|
Text(provider.description),
|
|
const SizedBox(height: 10),
|
|
Wrap(
|
|
spacing: 7,
|
|
runSpacing: 7,
|
|
children: provider.tags.map(TagPill.new).toList(),
|
|
),
|
|
const SizedBox(height: 14),
|
|
Row(
|
|
children: [
|
|
Text(
|
|
'¥${provider.price}',
|
|
style: const TextStyle(
|
|
color: AppColors.primary,
|
|
fontSize: 20,
|
|
fontWeight: FontWeight.w900,
|
|
),
|
|
),
|
|
const Text(' 起', style: TextStyle(color: AppColors.muted)),
|
|
const Spacer(),
|
|
FilledButton.icon(
|
|
onPressed: onBook,
|
|
icon: const Icon(Icons.calendar_month_outlined, size: 18),
|
|
label: const Text('立即预约'),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|