253 lines
9.5 KiB
Dart
253 lines
9.5 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
void showLanguageSelectionOverlay(
|
|
BuildContext context,
|
|
String currentLanguage,
|
|
Function(String, String) onLanguageSelected,
|
|
GlobalKey languageTileKey,
|
|
) {
|
|
final overlay = Overlay.of(context);
|
|
late OverlayEntry overlayEntry;
|
|
|
|
final RenderBox renderBox = languageTileKey.currentContext?.findRenderObject() as RenderBox;
|
|
final position = renderBox.localToGlobal(Offset.zero);
|
|
final size = renderBox.size;
|
|
|
|
overlayEntry = OverlayEntry(
|
|
builder: (context) => LanguageSelectionOverlay(
|
|
onLanguageSelected: (language, flag) {
|
|
onLanguageSelected(language, flag);
|
|
overlayEntry.remove();
|
|
},
|
|
onDismiss: () {
|
|
overlayEntry.remove();
|
|
},
|
|
currentLanguage: currentLanguage,
|
|
position: position,
|
|
triggerSize: size,
|
|
),
|
|
);
|
|
|
|
overlay.insert(overlayEntry);
|
|
}
|
|
|
|
class LanguageSelectionOverlay extends StatefulWidget {
|
|
final Function(String, String) onLanguageSelected;
|
|
final VoidCallback onDismiss;
|
|
final String currentLanguage;
|
|
final Offset position;
|
|
final Size triggerSize;
|
|
|
|
const LanguageSelectionOverlay({
|
|
super.key,
|
|
required this.onLanguageSelected,
|
|
required this.onDismiss,
|
|
required this.currentLanguage,
|
|
required this.position,
|
|
required this.triggerSize,
|
|
});
|
|
|
|
@override
|
|
State<LanguageSelectionOverlay> createState() => _LanguageSelectionOverlayState();
|
|
}
|
|
|
|
class _LanguageSelectionOverlayState extends State<LanguageSelectionOverlay>
|
|
with TickerProviderStateMixin {
|
|
late AnimationController _animationController;
|
|
late Animation<double> _scaleAnimation;
|
|
late Animation<double> _opacityAnimation;
|
|
|
|
final List<Map<String, String>> languages = [
|
|
{'name': 'English', 'flag': 'assets/icons/usa circle.svg'},
|
|
{'name': 'العربية', 'flag': 'assets/icons/arab circle.svg'},
|
|
{'name': 'فارسی', 'flag': 'assets/icons/iran circle.svg'},
|
|
];
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_animationController = AnimationController(
|
|
duration: const Duration(milliseconds: 200),
|
|
vsync: this,
|
|
);
|
|
|
|
_scaleAnimation = Tween<double>(
|
|
begin: 0.8,
|
|
end: 1.0,
|
|
).animate(CurvedAnimation(
|
|
parent: _animationController,
|
|
curve: Curves.easeOutBack,
|
|
));
|
|
|
|
_opacityAnimation = Tween<double>(
|
|
begin: 0.0,
|
|
end: 1.0,
|
|
).animate(CurvedAnimation(
|
|
parent: _animationController,
|
|
curve: Curves.easeOut,
|
|
));
|
|
|
|
_animationController.forward();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_animationController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final screenSize = MediaQuery.of(context).size;
|
|
final overlayWidth = screenSize.width * 0.45;
|
|
const overlayHeight = 180.0;
|
|
|
|
double left = widget.position.dx;
|
|
double top = widget.position.dy + widget.triggerSize.height + 8;
|
|
|
|
if (left + overlayWidth > screenSize.width) {
|
|
left = screenSize.width - overlayWidth - 16;
|
|
}
|
|
if (left < 16) {
|
|
left = 16;
|
|
}
|
|
|
|
if (top + overlayHeight > screenSize.height) {
|
|
top = widget.position.dy - overlayHeight - 8;
|
|
}
|
|
|
|
return GestureDetector(
|
|
onTap: widget.onDismiss,
|
|
child: Material(
|
|
color: Colors.transparent,
|
|
child: Stack(
|
|
children: [
|
|
Container(
|
|
width: screenSize.width,
|
|
height: screenSize.height,
|
|
color: Colors.transparent,
|
|
),
|
|
|
|
Positioned(
|
|
left: left,
|
|
top: top,
|
|
child: AnimatedBuilder(
|
|
animation: _animationController,
|
|
builder: (context, child) {
|
|
return Transform.scale(
|
|
scale: _scaleAnimation.value,
|
|
child: Opacity(
|
|
opacity: _opacityAnimation.value,
|
|
child: Container(
|
|
width: overlayWidth,
|
|
decoration: BoxDecoration(
|
|
color: Theme.of(context).cardColor,
|
|
borderRadius: BorderRadius.circular(12),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.1),
|
|
blurRadius: 10,
|
|
offset: const Offset(0, 4),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: languages.map((language) {
|
|
final isSelected = language['name'] == widget.currentLanguage;
|
|
final isRTL = language['name'] == 'العربية' || language['name'] == 'فارسی';
|
|
|
|
return GestureDetector(
|
|
onTap: () {
|
|
widget.onLanguageSelected(
|
|
language['name']!,
|
|
language['flag']!,
|
|
);
|
|
},
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 16,
|
|
vertical: 12,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: isSelected
|
|
? Theme.of(context).primaryColor.withOpacity(0.1)
|
|
: Colors.transparent,
|
|
borderRadius: languages.indexOf(language) == 0
|
|
? const BorderRadius.only(
|
|
topLeft: Radius.circular(12),
|
|
topRight: Radius.circular(12),
|
|
)
|
|
: languages.indexOf(language) == languages.length - 1
|
|
? const BorderRadius.only(
|
|
bottomLeft: Radius.circular(12),
|
|
bottomRight: Radius.circular(12),
|
|
)
|
|
: null,
|
|
),
|
|
child: Row(
|
|
textDirection: isRTL ? TextDirection.rtl : TextDirection.ltr,
|
|
children: [
|
|
ClipRRect(
|
|
borderRadius: BorderRadius.circular(4),
|
|
child: Image.asset(
|
|
language['flag']!,
|
|
width: 24,
|
|
height: 18,
|
|
fit: BoxFit.cover,
|
|
errorBuilder: (context, error, stackTrace) {
|
|
return Container(
|
|
width: 24,
|
|
height: 18,
|
|
decoration: BoxDecoration(
|
|
color: Theme.of(context).dividerColor,
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
child: Icon(
|
|
Icons.flag,
|
|
color: Theme.of(context).textTheme.bodyMedium?.color,
|
|
size: 12,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Text(
|
|
language['name']!,
|
|
textDirection: isRTL ? TextDirection.rtl : TextDirection.ltr,
|
|
style: TextStyle(
|
|
fontSize: 15,
|
|
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500,
|
|
color: isSelected
|
|
? Theme.of(context).primaryColor
|
|
: Theme.of(context).textTheme.bodyLarge?.color,
|
|
),
|
|
),
|
|
),
|
|
if (isSelected)
|
|
Icon(
|
|
Icons.check,
|
|
color: Theme.of(context).primaryColor,
|
|
size: 18,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}).toList(),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|