Files
Siro/siro_rider/lib/views/widgets/mydialoug.dart
2026-06-14 05:48:58 +03:00

686 lines
26 KiB
Dart

import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import '../../constant/colors.dart';
import '../../constant/style.dart';
import '../../controller/functions/tts.dart';
import '../../controller/functions/translate_helper.dart';
// ─────────────────────────────────────────────────────────────────────────────
// Config
// ─────────────────────────────────────────────────────────────────────────────
class _DC {
static const Duration animDuration = Duration(milliseconds: 280);
static const double blur = 20.0;
static const double radius = 24.0;
static const Color barrierColor = Color(0x66000000);
}
// ─────────────────────────────────────────────────────────────────────────────
// Shared animated wrapper — every dialog uses this
// ─────────────────────────────────────────────────────────────────────────────
class _DialogShell extends StatelessWidget {
final Widget child;
const _DialogShell({required this.child});
@override
Widget build(BuildContext context) {
return TweenAnimationBuilder<double>(
duration: _DC.animDuration,
tween: Tween(begin: 0.0, end: 1.0),
curve: Curves.easeOutBack,
builder: (_, v, c) => Transform.scale(
scale: 0.88 + (0.12 * v),
child: Opacity(opacity: v.clamp(0.0, 1.0), child: c),
),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: _DC.blur, sigmaY: _DC.blur),
child: Dialog(
backgroundColor: Colors.transparent,
insetPadding:
const EdgeInsets.symmetric(horizontal: 28, vertical: 40),
child: child,
),
),
);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// Shared glass card
// ─────────────────────────────────────────────────────────────────────────────
class _GlassCard extends StatelessWidget {
final Widget child;
const _GlassCard({required this.child});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(_DC.radius),
gradient: LinearGradient(
colors: [
AppColor.secondaryColor.withOpacity(0.95),
AppColor.secondaryColor.withOpacity(0.88),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.18),
blurRadius: 40,
spreadRadius: -4,
offset: const Offset(0, 16),
),
BoxShadow(
color: AppColor.primaryColor.withOpacity(0.08),
blurRadius: 20,
offset: const Offset(0, 4),
),
],
border: Border.all(
color: AppColor.secondaryColor.withOpacity(0.6),
width: 1.2,
),
),
child: child,
);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// Shared bottom action row
// ─────────────────────────────────────────────────────────────────────────────
class _ActionRow extends StatelessWidget {
final VoidCallback onCancel;
final VoidCallback onConfirm;
final String confirmLabel;
final bool isDestructive;
const _ActionRow({
required this.onCancel,
required this.onConfirm,
this.confirmLabel = 'OK',
this.isDestructive = false,
});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
border: Border(
top: BorderSide(color: Colors.grey.withOpacity(0.15), width: 1),
),
),
child: Row(
children: [
// Cancel
Expanded(
child: _ActionButton(
label: 'Cancel'.tr,
color: Colors.grey[600]!,
backgroundColor: Colors.grey.withOpacity(0.07),
onPressed: () {
HapticFeedback.lightImpact();
onCancel();
},
isLeft: true,
),
),
Container(width: 1, height: 52, color: Colors.grey.withOpacity(0.15)),
// Confirm
Expanded(
child: _ActionButton(
label: confirmLabel,
color: isDestructive ? AppColor.redColor : AppColor.primaryColor,
backgroundColor: isDestructive
? AppColor.redColor.withOpacity(0.07)
: AppColor.primaryColor.withOpacity(0.07),
onPressed: () {
HapticFeedback.mediumImpact();
onConfirm();
},
isLeft: false,
isBold: true,
),
),
],
),
);
}
}
class _ActionButton extends StatelessWidget {
final String label;
final Color color;
final Color backgroundColor;
final VoidCallback onPressed;
final bool isLeft;
final bool isBold;
const _ActionButton({
required this.label,
required this.color,
required this.backgroundColor,
required this.onPressed,
required this.isLeft,
this.isBold = false,
});
@override
Widget build(BuildContext context) {
return Material(
color: Colors.transparent,
child: InkWell(
onTap: onPressed,
borderRadius: BorderRadius.only(
bottomLeft: isLeft ? const Radius.circular(_DC.radius) : Radius.zero,
bottomRight:
!isLeft ? const Radius.circular(_DC.radius) : Radius.zero,
),
child: Container(
height: 52,
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.only(
bottomLeft:
isLeft ? const Radius.circular(_DC.radius) : Radius.zero,
bottomRight:
!isLeft ? const Radius.circular(_DC.radius) : Radius.zero,
),
),
alignment: Alignment.center,
child: Text(
label,
style: TextStyle(
color: color,
fontSize: 15,
fontWeight: isBold ? FontWeight.w700 : FontWeight.w500,
letterSpacing: -0.2,
),
),
),
),
);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// TTS speak button
// ─────────────────────────────────────────────────────────────────────────────
class _SpeakButton extends StatefulWidget {
final List<String> texts;
const _SpeakButton({required this.texts});
@override
State<_SpeakButton> createState() => _SpeakButtonState();
}
class _SpeakButtonState extends State<_SpeakButton>
with SingleTickerProviderStateMixin {
bool _speaking = false;
late AnimationController _pulse;
@override
void initState() {
super.initState();
_pulse = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 900),
lowerBound: 0.92,
upperBound: 1.0,
);
}
@override
void dispose() {
_pulse.dispose();
super.dispose();
}
Future<void> _onTap() async {
if (_speaking) return;
HapticFeedback.selectionClick();
setState(() => _speaking = true);
_pulse.repeat(reverse: true);
final tts = Get.find<TextToSpeechController>();
for (final t in widget.texts) {
await tts.speakText(t);
}
_pulse.stop();
_pulse.reset();
if (mounted) setState(() => _speaking = false);
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: _onTap,
child: AnimatedBuilder(
animation: _pulse,
builder: (_, child) =>
Transform.scale(scale: _pulse.value, child: child),
child: AnimatedContainer(
duration: const Duration(milliseconds: 250),
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
decoration: BoxDecoration(
color: _speaking
? AppColor.primaryColor.withOpacity(0.15)
: AppColor.primaryColor.withOpacity(0.08),
borderRadius: BorderRadius.circular(30),
border: Border.all(
color: AppColor.primaryColor.withOpacity(_speaking ? 0.4 : 0.15),
width: 1,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
_speaking
? CupertinoIcons.speaker_3_fill
: CupertinoIcons.speaker_2_fill,
color: AppColor.primaryColor,
size: 16,
),
const SizedBox(width: 6),
Text(
_speaking ? 'Speaking...'.tr : 'Listen'.tr,
style: TextStyle(
color: AppColor.primaryColor,
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
],
),
),
),
);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// MyDialog — title + text content
// ─────────────────────────────────────────────────────────────────────────────
class MyDialog extends GetxController {
void getDialog(
String title,
String? midTitle,
VoidCallback onPressed, {
IconData? icon,
bool isDestructive = false,
}) {
HapticFeedback.mediumImpact();
Get.dialog(
Builder(builder: (dialogContext) {
return _DialogShell(
child: _GlassCard(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// ── Body ──────────────────────────────────────────────
Padding(
padding: const EdgeInsets.fromLTRB(24, 28, 24, 20),
child: Column(
children: [
// Icon badge
Container(
width: 56,
height: 56,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: (isDestructive
? AppColor.redColor
: AppColor.primaryColor)
.withOpacity(0.1),
border: Border.all(
color: (isDestructive
? AppColor.redColor
: AppColor.primaryColor)
.withOpacity(0.2),
),
),
child: Icon(
icon ??
(isDestructive
? Icons.warning_amber_rounded
: Icons.info_outline_rounded),
color: isDestructive
? AppColor.redColor
: AppColor.primaryColor,
size: 26,
),
),
const SizedBox(height: 16),
// Title
Text(
title,
textAlign: TextAlign.center,
style: AppStyle.title.copyWith(
fontSize: 18,
fontWeight: FontWeight.w700,
letterSpacing: -0.4,
color: AppColor.writeColor,
),
),
if (midTitle != null && midTitle.isNotEmpty) ...[
const SizedBox(height: 10),
Text(
midTitle,
textAlign: TextAlign.center,
style: AppStyle.subtitle.copyWith(
fontSize: 14.5,
height: 1.5,
color: Colors.grey[600],
),
),
const SizedBox(height: 16),
// TTS button
_SpeakButton(
texts: [title, if (midTitle.isNotEmpty) midTitle]),
],
],
),
),
// ── Actions ───────────────────────────────────────────
_ActionRow(
onCancel: () =>
Navigator.of(dialogContext, rootNavigator: true).pop(),
onConfirm: () {
// إغلاق الديالوج مباشرة باستخدام Navigator.pop
Navigator.of(dialogContext, rootNavigator: true).pop();
// تنفيذ الأمر بعد إغلاق الديالوج
Future.delayed(const Duration(milliseconds: 100), () {
onPressed();
});
},
confirmLabel: 'OK'.tr,
isDestructive: isDestructive,
),
],
),
),
);
}),
barrierDismissible: true,
barrierColor: _DC.barrierColor,
);
}
void getChatDialog(
String title,
String messageContent,
VoidCallback onPressed, {
IconData? icon,
}) {
HapticFeedback.mediumImpact();
String displayedText = messageContent;
bool isTranslated = false;
bool isLoading = false;
Get.dialog(
StatefulBuilder(
builder: (dialogContext, setState) {
return _DialogShell(
child: _GlassCard(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// ── Body ──────────────────────────────────────────────
Padding(
padding: const EdgeInsets.fromLTRB(24, 28, 24, 20),
child: Column(
children: [
// Icon badge
Container(
width: 56,
height: 56,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: AppColor.primaryColor.withOpacity(0.1),
border: Border.all(
color: AppColor.primaryColor.withOpacity(0.2),
),
),
child: Icon(
icon ?? Icons.chat_bubble_outline_rounded,
color: AppColor.primaryColor,
size: 26,
),
),
const SizedBox(height: 16),
// Title
Text(
title.tr,
textAlign: TextAlign.center,
style: AppStyle.title.copyWith(
fontSize: 18,
fontWeight: FontWeight.w700,
letterSpacing: -0.4,
color: AppColor.writeColor,
),
),
const SizedBox(height: 10),
if (isLoading)
const Padding(
padding: EdgeInsets.symmetric(vertical: 20),
child: Center(
child: CupertinoActivityIndicator(radius: 12),
),
)
else
Text(
displayedText,
textAlign: TextAlign.center,
style: AppStyle.subtitle.copyWith(
fontSize: 14.5,
height: 1.5,
color: Colors.grey[600],
),
),
const SizedBox(height: 16),
// TTS button
_SpeakButton(
texts: [title.tr, displayedText],
),
],
),
),
// ── Actions ───────────────────────────────────────────
Container(
decoration: BoxDecoration(
border: Border(
top: BorderSide(color: Colors.grey.withOpacity(0.15), width: 1),
),
),
child: Row(
children: [
// Translate Toggle
Expanded(
child: _ActionButton(
label: isTranslated ? 'Original'.tr : 'Translate'.tr,
color: AppColor.blueColor,
backgroundColor: AppColor.blueColor.withOpacity(0.07),
onPressed: () async {
if (isLoading) return;
HapticFeedback.lightImpact();
if (isTranslated) {
setState(() {
displayedText = messageContent;
isTranslated = false;
});
} else {
setState(() {
isLoading = true;
});
try {
final targetLang = Get.locale?.languageCode ?? 'ar';
final translated = await TranslateHelper.translateText(messageContent, targetLang);
setState(() {
displayedText = translated;
isTranslated = true;
isLoading = false;
});
} catch (e) {
setState(() {
isLoading = false;
});
}
}
},
isLeft: true,
),
),
Container(width: 1, height: 52, color: Colors.grey.withOpacity(0.15)),
// Confirm
Expanded(
child: _ActionButton(
label: 'OK'.tr,
color: AppColor.primaryColor,
backgroundColor: AppColor.primaryColor.withOpacity(0.07),
onPressed: () {
HapticFeedback.mediumImpact();
Navigator.of(dialogContext, rootNavigator: true).pop();
Future.delayed(const Duration(milliseconds: 100), () {
onPressed();
});
},
isLeft: false,
isBold: true,
),
),
],
),
),
],
),
),
);
},
),
barrierDismissible: true,
barrierColor: _DC.barrierColor,
);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// MyDialogContent — title + arbitrary widget content
// ─────────────────────────────────────────────────────────────────────────────
class MyDialogContent extends GetxController {
void getDialog(
String title,
Widget? content,
VoidCallback onPressed, {
IconData? icon,
bool isDestructive = false,
String confirmLabel = 'OK',
}) {
HapticFeedback.mediumImpact();
Get.dialog(
_DialogShell(
child: _GlassCard(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// ── Body ──────────────────────────────────────────────
Padding(
padding: const EdgeInsets.fromLTRB(24, 28, 24, 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header row: icon + title + TTS
Row(
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: (isDestructive
? AppColor.redColor
: AppColor.primaryColor)
.withOpacity(0.1),
),
child: Icon(
icon ??
(isDestructive
? Icons.warning_amber_rounded
: Icons.tune_rounded),
color: isDestructive
? AppColor.redColor
: AppColor.primaryColor,
size: 22,
),
),
const SizedBox(width: 12),
Expanded(
child: Text(
title,
style: AppStyle.title.copyWith(
fontSize: 17,
fontWeight: FontWeight.w700,
letterSpacing: -0.3,
color: AppColor.writeColor,
),
),
),
_SpeakButton(texts: [title]),
],
),
// Divider
Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Divider(
height: 1,
color: Colors.grey.withOpacity(0.15),
),
),
// Content
if (content != null) content,
],
),
),
// ── Actions ───────────────────────────────────────────
_ActionRow(
onCancel: () => Get.back(),
onConfirm: () {
Get.back(); // Dismiss dialog first
WidgetsBinding.instance.addPostFrameCallback((_) {
onPressed();
});
},
confirmLabel: confirmLabel.tr,
isDestructive: isDestructive,
),
],
),
),
),
barrierDismissible: true,
barrierColor: _DC.barrierColor,
);
}
}