312 lines
11 KiB
Dart
312 lines
11 KiB
Dart
import 'package:didvan/config/design_config.dart';
|
|
import 'package:didvan/config/theme_data.dart';
|
|
import 'package:didvan/views/widgets/didvan/text.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_svg/flutter_svg.dart';
|
|
|
|
class DidvanBNB extends StatelessWidget {
|
|
final int currentTabIndex;
|
|
final void Function(int index) onTabChanged;
|
|
|
|
const DidvanBNB(
|
|
{Key? key, required this.currentTabIndex, required this.onTabChanged})
|
|
: super(key: key);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Container(
|
|
height: 72,
|
|
decoration: BoxDecoration(
|
|
color: Theme.of(context).colorScheme.surface,
|
|
borderRadius:
|
|
const BorderRadius.vertical(top: Radius.circular(0)),
|
|
border: const Border(
|
|
top: BorderSide(
|
|
color: Color.fromARGB(255, 224, 224, 224),
|
|
width: 1.5,
|
|
),
|
|
),
|
|
),
|
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
|
child: Row(
|
|
children: [
|
|
_NavBarItem(
|
|
isSelected: currentTabIndex == 1,
|
|
title: 'رسانه',
|
|
selectedIconPath: 'lib/assets/icons/media selected.svg', // Selected SVG icon
|
|
unselectedIconPath: 'lib/assets/icons/media.svg', // Unselected SVG icon
|
|
onTap: () => onTabChanged(1),
|
|
),
|
|
_NavBarItem(
|
|
isSelected: currentTabIndex == 4,
|
|
title: 'هوشان',
|
|
selectedIconPath: 'lib/assets/icons/houshan_selected.svg', // Selected SVG icon
|
|
unselectedIconPath: 'lib/assets/icons/bot.svg', // Unselected SVG icon
|
|
onTap: () => onTabChanged(4),
|
|
),
|
|
_NavBarItem(
|
|
isSelected: currentTabIndex == 0,
|
|
title: 'خانه',
|
|
selectedIconPath: DesignConfig.isDark
|
|
? 'lib/assets/icons/selected home.svg'
|
|
: 'lib/assets/icons/selected home.svg',
|
|
unselectedIconPath: DesignConfig.isDark
|
|
? 'assets/images/logos/logo-vertical-dark.svg'
|
|
: 'lib/assets/icons/home2.svg', // Unselected SVG icon
|
|
onTap: () => onTabChanged(0),
|
|
),
|
|
_NavBarItem(
|
|
isSelected: currentTabIndex == 2,
|
|
title: 'نبض صنعت',
|
|
selectedIconPath: DesignConfig.isDark
|
|
? 'lib/assets/icons/stats_nav_icon_dark_solid.svg'
|
|
: 'lib/assets/icons/chart 2_solid.svg', // Selected SVG icon
|
|
unselectedIconPath: DesignConfig.isDark
|
|
? 'lib/assets/icons/stats_nav_icon_dark.svg'
|
|
: 'lib/assets/icons/chart 2.svg', // Unselected SVG icon
|
|
onTap: () => onTabChanged(2),
|
|
),
|
|
_NavBarItem(
|
|
isSelected: currentTabIndex == 3,
|
|
title: 'کاوش',
|
|
selectedIconPath: 'lib/assets/icons/discover_solid.svg', // Selected SVG icon
|
|
unselectedIconPath: 'lib/assets/icons/discover.svg', // Unselected SVG icon
|
|
onTap: () => onTabChanged(3),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _NavBarItem extends StatefulWidget {
|
|
final VoidCallback onTap;
|
|
final bool isSelected;
|
|
final String title;
|
|
final String selectedIconPath;
|
|
final String unselectedIconPath;
|
|
final bool isHomeButton;
|
|
|
|
const _NavBarItem({
|
|
Key? key,
|
|
required this.isSelected,
|
|
required this.title,
|
|
required this.selectedIconPath,
|
|
required this.unselectedIconPath,
|
|
required this.onTap,
|
|
this.isHomeButton = false,
|
|
}) : super(key: key);
|
|
|
|
@override
|
|
State<_NavBarItem> createState() => _NavBarItemState();
|
|
}
|
|
|
|
class _NavBarItemState extends State<_NavBarItem>
|
|
with TickerProviderStateMixin {
|
|
late AnimationController _scaleController;
|
|
late AnimationController _bounceController;
|
|
late AnimationController _rippleController;
|
|
late Animation<double> _scaleAnimation;
|
|
late Animation<double> _bounceAnimation;
|
|
late Animation<double> _rippleAnimation;
|
|
bool _isPressed = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
_scaleController = AnimationController(
|
|
duration: const Duration(milliseconds: 150),
|
|
vsync: this,
|
|
);
|
|
|
|
_bounceController = AnimationController(
|
|
duration: const Duration(milliseconds: 600),
|
|
vsync: this,
|
|
);
|
|
|
|
_rippleController = AnimationController(
|
|
duration: const Duration(milliseconds: 400),
|
|
vsync: this,
|
|
);
|
|
|
|
_scaleAnimation = Tween<double>(
|
|
begin: 1.0,
|
|
end: 0.85,
|
|
).animate(CurvedAnimation(
|
|
parent: _scaleController,
|
|
curve: Curves.easeInOut,
|
|
));
|
|
|
|
_bounceAnimation = Tween<double>(
|
|
begin: 1.0,
|
|
end: 1.2,
|
|
).animate(CurvedAnimation(
|
|
parent: _bounceController,
|
|
curve: Curves.elasticOut,
|
|
));
|
|
|
|
_rippleAnimation = Tween<double>(
|
|
begin: 0.0,
|
|
end: 1.0,
|
|
).animate(CurvedAnimation(
|
|
parent: _rippleController,
|
|
curve: Curves.easeOut,
|
|
));
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_scaleController.dispose();
|
|
_bounceController.dispose();
|
|
_rippleController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
void _onTapDown() {
|
|
setState(() {
|
|
_isPressed = true;
|
|
});
|
|
_scaleController.forward();
|
|
_rippleController.forward();
|
|
}
|
|
|
|
void _onTapUp() {
|
|
setState(() {
|
|
_isPressed = false;
|
|
});
|
|
_scaleController.reverse();
|
|
_rippleController.reverse();
|
|
|
|
if (widget.isSelected) {
|
|
_bounceController.forward().then((_) {
|
|
_bounceController.reverse();
|
|
});
|
|
}
|
|
|
|
widget.onTap();
|
|
}
|
|
|
|
void _onTapCancel() {
|
|
setState(() {
|
|
_isPressed = false;
|
|
});
|
|
_scaleController.reverse();
|
|
_rippleController.reverse();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Expanded(
|
|
child: Tooltip(
|
|
message: widget.title,
|
|
decoration: BoxDecoration(
|
|
color: Theme.of(context).colorScheme.title,
|
|
borderRadius: DesignConfig.highBorderRadius,
|
|
boxShadow: DesignConfig.defaultShadow,
|
|
),
|
|
child: GestureDetector(
|
|
onTapDown: (_) => _onTapDown(),
|
|
onTapUp: (_) => _onTapUp(),
|
|
onTapCancel: _onTapCancel,
|
|
child: Container(
|
|
color: Colors.transparent,
|
|
child: Stack(
|
|
alignment: Alignment.center,
|
|
children: [
|
|
AnimatedBuilder(
|
|
animation: _rippleAnimation,
|
|
builder: (context, child) {
|
|
return Container(
|
|
width: 60 * _rippleAnimation.value,
|
|
height: 60 * _rippleAnimation.value,
|
|
decoration: BoxDecoration(
|
|
shape: BoxShape.circle,
|
|
color: Theme.of(context).colorScheme.primary.withOpacity(
|
|
0.1 * (1 - _rippleAnimation.value),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
Column(
|
|
children: [
|
|
const SizedBox(height: 4),
|
|
AnimatedBuilder(
|
|
animation: Listenable.merge([_scaleAnimation, _bounceAnimation]),
|
|
builder: (context, child) {
|
|
final scale = _scaleAnimation.value *
|
|
(widget.isSelected ? _bounceAnimation.value : 1.0);
|
|
|
|
return Transform.scale(
|
|
scale: scale,
|
|
child: AnimatedContainer(
|
|
padding: EdgeInsets.all(widget.isHomeButton ? 8 : 4),
|
|
duration: DesignConfig.lowAnimationDuration,
|
|
|
|
decoration:
|
|
BoxDecoration(
|
|
shape: BoxShape.circle,
|
|
color: widget.isSelected
|
|
? Theme.of(context).colorScheme.primary.withOpacity(0.1)
|
|
: Colors.transparent,
|
|
boxShadow: widget.isSelected
|
|
? [
|
|
BoxShadow(
|
|
color: Theme.of(context).colorScheme.primary.withOpacity(0.3),
|
|
blurRadius: 8,
|
|
spreadRadius: 1,
|
|
)
|
|
]
|
|
: null,
|
|
),
|
|
child: SizedBox(
|
|
width: widget.isHomeButton ? 50 : (widget.isSelected ? 50 : 32),
|
|
height: widget.isHomeButton ? 50 : (widget.isSelected ? 50 : 32),
|
|
child: AnimatedSwitcher(
|
|
duration: const Duration(milliseconds: 300),
|
|
transitionBuilder: (child, animation) {
|
|
return ScaleTransition(
|
|
scale: animation,
|
|
child: RotationTransition(
|
|
turns: Tween<double>(begin: 0.8, end: 1.0).animate(animation),
|
|
child: child,
|
|
),
|
|
);
|
|
},
|
|
child: SvgPicture.asset(
|
|
widget.isSelected ? widget.selectedIconPath : widget.unselectedIconPath,
|
|
key: ValueKey(widget.isSelected),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
AnimatedSwitcher(
|
|
duration: const Duration(milliseconds: 200),
|
|
child: (!widget.isHomeButton && !widget.isSelected)
|
|
? AnimatedOpacity(
|
|
opacity: _isPressed ? 0.6 : 1.0,
|
|
duration: const Duration(milliseconds: 150),
|
|
child: DidvanText(
|
|
widget.title,
|
|
style: Theme.of(context).textTheme.bodySmall,
|
|
color: const Color.fromARGB(255, 102, 102, 102),
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
)
|
|
: const SizedBox.shrink(),
|
|
),
|
|
const Spacer(),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
} |