first commit
This commit is contained in:
523
siro_rider/lib/views/widgets/mydialoug.dart
Normal file
523
siro_rider/lib/views/widgets/mydialoug.dart
Normal file
@@ -0,0 +1,523 @@
|
||||
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';
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user