feat(apps): 重构 UI 架构为 presentation 层并新增 l10n 国际化支持
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
import 'package:flutter/material.dart' hide BackButton;
|
||||
import 'package:social_app/core/l10n/l10n.dart';
|
||||
|
||||
import '../../../../app/di/injection.dart';
|
||||
import '../../../../core/theme/design_tokens.dart';
|
||||
import '../../../../shared/widgets/app_button.dart';
|
||||
import '../../../../shared/widgets/toast/toast.dart';
|
||||
import '../../../../shared/widgets/toast/toast_type.dart';
|
||||
import '../../data/calendar_api.dart';
|
||||
|
||||
class CalendarShareDialog extends StatefulWidget {
|
||||
final String eventId;
|
||||
final String eventTitle;
|
||||
final bool canInvite;
|
||||
final bool canEdit;
|
||||
|
||||
const CalendarShareDialog({
|
||||
super.key,
|
||||
required this.eventId,
|
||||
required this.eventTitle,
|
||||
this.canInvite = false,
|
||||
this.canEdit = false,
|
||||
});
|
||||
|
||||
static Future<void> show(
|
||||
BuildContext context,
|
||||
String eventId,
|
||||
String eventTitle, {
|
||||
bool canInvite = false,
|
||||
bool canEdit = false,
|
||||
}) {
|
||||
return showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (context) => CalendarShareDialog(
|
||||
eventId: eventId,
|
||||
eventTitle: eventTitle,
|
||||
canInvite: canInvite,
|
||||
canEdit: canEdit,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
State<CalendarShareDialog> createState() => _CalendarShareDialogState();
|
||||
}
|
||||
|
||||
class _CalendarShareDialogState extends State<CalendarShareDialog> {
|
||||
final _phoneController = TextEditingController();
|
||||
final bool _permissionView = true;
|
||||
bool _permissionEdit = false;
|
||||
bool _permissionInvite = false;
|
||||
bool _isLoading = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_phoneController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _handleShare() async {
|
||||
final l10n = context.l10n;
|
||||
final phone = _phoneController.text.trim();
|
||||
if (phone.isEmpty) {
|
||||
Toast.show(
|
||||
context,
|
||||
l10n.calendarSharePhoneRequired,
|
||||
type: ToastType.error,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() => _isLoading = true);
|
||||
|
||||
try {
|
||||
final api = sl<CalendarApi>();
|
||||
await api.share(
|
||||
widget.eventId,
|
||||
phone: phone,
|
||||
view: _permissionView,
|
||||
edit: _permissionEdit,
|
||||
invite: _permissionInvite,
|
||||
);
|
||||
if (mounted) {
|
||||
Toast.show(
|
||||
context,
|
||||
l10n.calendarShareInviteSent,
|
||||
type: ToastType.success,
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
Toast.show(
|
||||
context,
|
||||
l10n.calendarShareInviteFailed,
|
||||
type: ToastType.error,
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() => _isLoading = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
|
||||
return Container(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.of(context).viewInsets.bottom,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.background,
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(AppRadius.lg),
|
||||
),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(AppSpacing.lg),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
l10n.calendarShareTitle,
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
icon: const Icon(Icons.close),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
Text(widget.eventTitle, style: const TextStyle(fontSize: 16)),
|
||||
const SizedBox(height: AppSpacing.lg),
|
||||
TextField(
|
||||
controller: _phoneController,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.calendarSharePhoneLabel,
|
||||
hintText: l10n.calendarSharePhoneHint,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||
),
|
||||
),
|
||||
keyboardType: TextInputType.phone,
|
||||
),
|
||||
const SizedBox(height: AppSpacing.lg),
|
||||
Text(
|
||||
l10n.calendarSharePermissionTitle,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.sm),
|
||||
_buildPermissionSwitch(
|
||||
l10n.calendarSharePermissionView,
|
||||
l10n.calendarSharePermissionViewDesc,
|
||||
true,
|
||||
null,
|
||||
),
|
||||
_buildPermissionSwitch(
|
||||
l10n.calendarSharePermissionEdit,
|
||||
l10n.calendarSharePermissionEditDesc,
|
||||
_permissionEdit,
|
||||
widget.canEdit
|
||||
? (v) => setState(() => _permissionEdit = v)
|
||||
: null,
|
||||
),
|
||||
_buildPermissionSwitch(
|
||||
l10n.calendarSharePermissionInvite,
|
||||
l10n.calendarSharePermissionInviteDesc,
|
||||
_permissionInvite,
|
||||
widget.canInvite
|
||||
? (v) => setState(() => _permissionInvite = v)
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: AppSpacing.lg),
|
||||
AppButton(
|
||||
text: l10n.calendarShareSendInvite,
|
||||
onPressed: _isLoading ? null : _handleShare,
|
||||
isLoading: _isLoading,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPermissionSwitch(
|
||||
String title,
|
||||
String description,
|
||||
bool value,
|
||||
ValueChanged<bool>? onChanged,
|
||||
) {
|
||||
final enabled = onChanged != null;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: AppSpacing.xs),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(color: enabled ? null : Colors.grey),
|
||||
),
|
||||
Text(
|
||||
description,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: enabled ? Colors.grey : Colors.grey.shade400,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Switch(value: value, onChanged: enabled ? onChanged : null),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user