add language section for onboarding
This commit is contained in:
parent
9a4c51dafb
commit
4bc390c3e3
|
|
@ -1,8 +1,10 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import '../../extension/screenSize.dart';
|
import '../../extension/screenSize.dart';
|
||||||
import '../../gen/assets.gen.dart';
|
import '../../gen/assets.gen.dart';
|
||||||
import '../../res/colors.dart';
|
import '../../res/colors.dart';
|
||||||
|
import '../../widgets/language_selection_dialog.dart';
|
||||||
import 'login_page.dart';
|
import 'login_page.dart';
|
||||||
|
|
||||||
class OnboardingPage extends StatefulWidget {
|
class OnboardingPage extends StatefulWidget {
|
||||||
|
|
@ -16,6 +18,10 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||||
int currentIndex = 0;
|
int currentIndex = 0;
|
||||||
final PageController _pageController = PageController();
|
final PageController _pageController = PageController();
|
||||||
|
|
||||||
|
String _currentLanguage = '🇺🇲 English';
|
||||||
|
String _currentFlag = 'assets/icons/usa circle.svg';
|
||||||
|
final GlobalKey _languageKey = GlobalKey();
|
||||||
|
|
||||||
final List<String> imageAssets = [
|
final List<String> imageAssets = [
|
||||||
Assets.images.ounboarding1.path,
|
Assets.images.ounboarding1.path,
|
||||||
Assets.images.frame.path,
|
Assets.images.frame.path,
|
||||||
|
|
@ -35,6 +41,7 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
_markOnboardingComplete();
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(builder: (context) => LoginPage()),
|
MaterialPageRoute(builder: (context) => LoginPage()),
|
||||||
|
|
@ -42,6 +49,16 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _markOnboardingComplete() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.setBool('hasSeenOnboarding', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _resetOnboarding() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.remove('hasSeenOnboarding');
|
||||||
|
}
|
||||||
|
|
||||||
void _back() {
|
void _back() {
|
||||||
if (currentIndex > 0) {
|
if (currentIndex > 0) {
|
||||||
_pageController.previousPage(
|
_pageController.previousPage(
|
||||||
|
|
@ -59,6 +76,109 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.fromLTRB(
|
||||||
|
width / 15,
|
||||||
|
height / 20,
|
||||||
|
width / 15,
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
SizedBox(width: 50),
|
||||||
|
GestureDetector(
|
||||||
|
key: _languageKey,
|
||||||
|
onTap: () {
|
||||||
|
showLanguageSelectionOverlay(context, _currentLanguage, (
|
||||||
|
language,
|
||||||
|
flag,
|
||||||
|
) {
|
||||||
|
setState(() {
|
||||||
|
_currentLanguage = language;
|
||||||
|
_currentFlag = flag;
|
||||||
|
});
|
||||||
|
}, _languageKey);
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
child: ClipRRect(
|
||||||
|
key: ValueKey(_currentFlag),
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
child:
|
||||||
|
_currentFlag == 'placeholder'
|
||||||
|
? Container(
|
||||||
|
width: 24,
|
||||||
|
height: 18,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.primary.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.flag,
|
||||||
|
color: AppColors.primary,
|
||||||
|
size: 12,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: _currentFlag.endsWith('.svg')
|
||||||
|
? SvgPicture.asset(
|
||||||
|
_currentFlag,
|
||||||
|
width: 24,
|
||||||
|
height: 18,
|
||||||
|
)
|
||||||
|
: Image.asset(
|
||||||
|
_currentFlag,
|
||||||
|
width: 24,
|
||||||
|
height: 18,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
errorBuilder: (context, error, stackTrace) {
|
||||||
|
return Container(
|
||||||
|
width: 24,
|
||||||
|
height: 18,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.greyBorder
|
||||||
|
.withOpacity(0.2),
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.flag,
|
||||||
|
color: AppColors.textSecondary,
|
||||||
|
size: 12,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
child: Text(
|
||||||
|
_currentLanguage,
|
||||||
|
key: ValueKey(_currentLanguage),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: AppColors.textPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
SvgPicture.asset(
|
||||||
|
Assets.icons.arrowRight.path,
|
||||||
|
color: AppColors.textPrimary,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: PageView.builder(
|
child: PageView.builder(
|
||||||
controller: _pageController,
|
controller: _pageController,
|
||||||
|
|
@ -72,7 +192,8 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.fromLTRB(
|
padding: EdgeInsets.fromLTRB(
|
||||||
width / 15,
|
width / 15,
|
||||||
height / 40,
|
height /
|
||||||
|
60,
|
||||||
width / 15,
|
width / 15,
|
||||||
height / 30,
|
height / 30,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,19 @@ class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixi
|
||||||
void _onCardSelected(HuntCard card) async {
|
void _onCardSelected(HuntCard card) async {
|
||||||
final huntProvider = Provider.of<HuntState>(context, listen: false);
|
final huntProvider = Provider.of<HuntState>(context, listen: false);
|
||||||
|
|
||||||
|
// If the same card is selected, deselect it
|
||||||
|
if (huntProvider.selectedCard?.id == card.id) {
|
||||||
|
// Play sound for deselection too
|
||||||
|
await GameSoundService.playCardFlipSound();
|
||||||
|
Vibration.hasVibrator().then((hasVibrator) {
|
||||||
|
if (hasVibrator == true) {
|
||||||
|
Vibration.vibrate(duration: 50); // Lighter vibration for deselect
|
||||||
|
}
|
||||||
|
});
|
||||||
|
huntProvider.deselectCard();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await GameSoundService.playCardFlipSound();
|
await GameSoundService.playCardFlipSound();
|
||||||
Vibration.hasVibrator().then((hasVibrator) {
|
Vibration.hasVibrator().then((hasVibrator) {
|
||||||
if (hasVibrator == true) {
|
if (hasVibrator == true) {
|
||||||
|
|
@ -243,6 +256,7 @@ class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixi
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 12),
|
padding: const EdgeInsets.only(bottom: 12),
|
||||||
child: HuntCardWidget(
|
child: HuntCardWidget(
|
||||||
|
key: ValueKey(card.id),
|
||||||
card: card,
|
card: card,
|
||||||
onTap: () => _onCardSelected(card),
|
onTap: () => _onCardSelected(card),
|
||||||
isSelected: huntProvider.selectedCard?.id == card.id,
|
isSelected: huntProvider.selectedCard?.id == card.id,
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,11 @@ class HuntState extends ChangeNotifier {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void deselectCard() {
|
||||||
|
_selectedCard = null;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
void startHunt() {
|
void startHunt() {
|
||||||
if (_selectedCard != null) {
|
if (_selectedCard != null) {
|
||||||
_huntStartTime = DateTime.now();
|
_huntStartTime = DateTime.now();
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,21 @@ class _HuntCardWidgetState extends State<HuntCardWidget>
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(HuntCardWidget oldWidget) {
|
void didUpdateWidget(HuntCardWidget oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
if (widget.isFlipped != oldWidget.isFlipped) {
|
|
||||||
|
// Handle selection state changes first
|
||||||
|
if (widget.isSelected != oldWidget.isSelected) {
|
||||||
|
if (widget.isSelected) {
|
||||||
|
// Card is being selected
|
||||||
|
_flipController.forward();
|
||||||
|
} else {
|
||||||
|
// Card is being deselected - animate back to front
|
||||||
|
_flipController.reverse();
|
||||||
|
_scaleController.reverse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle flip animation based on isFlipped property if not already handled by selection
|
||||||
|
if (widget.isFlipped != oldWidget.isFlipped && widget.isSelected == oldWidget.isSelected) {
|
||||||
if (widget.isFlipped) {
|
if (widget.isFlipped) {
|
||||||
_flipController.forward();
|
_flipController.forward();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,42 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:lba/screens/auth/login_page.dart';
|
import 'package:lba/screens/auth/login_page.dart';
|
||||||
import 'package:lba/screens/auth/onboarding_page.dart';
|
import 'package:lba/screens/auth/onboarding_page.dart';
|
||||||
import 'package:lba/screens/mains/navigation/navigation.dart';
|
import 'package:lba/screens/mains/navigation/navigation.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
class SimpleAuthGate extends StatelessWidget {
|
class SimpleAuthGate extends StatefulWidget {
|
||||||
const SimpleAuthGate({super.key});
|
const SimpleAuthGate({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SimpleAuthGate> createState() => _SimpleAuthGateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SimpleAuthGateState extends State<SimpleAuthGate> {
|
||||||
|
bool? _isFirstTime;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_checkFirstTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _checkFirstTime() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
final hasSeenOnboarding = prefs.getBool('hasSeenOnboarding') ?? false;
|
||||||
|
setState(() {
|
||||||
|
_isFirstTime = !hasSeenOnboarding;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
if (_isFirstTime == null) {
|
||||||
|
return const Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return StreamBuilder<User?>(
|
return StreamBuilder<User?>(
|
||||||
stream: FirebaseAuth.instance.authStateChanges(),
|
stream: FirebaseAuth.instance.authStateChanges(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
|
|
@ -42,12 +72,8 @@ class SimpleAuthGate extends StatelessWidget {
|
||||||
return const MainScreen();
|
return const MainScreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
return _shouldShowOnboarding() ? const OnboardingPage() : const LoginPage();
|
return _isFirstTime! ? const OnboardingPage() : const LoginPage();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _shouldShowOnboarding() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,17 @@ class _LanguageSelectionOverlayState extends State<LanguageSelectionOverlay>
|
||||||
final targetPosition = _getTargetPosition();
|
final targetPosition = _getTargetPosition();
|
||||||
final screenWidth = MediaQuery.of(context).size.width;
|
final screenWidth = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
|
final overlayWidth = screenWidth * 0.45;
|
||||||
|
|
||||||
|
double? leftPosition;
|
||||||
|
double? rightPosition;
|
||||||
|
|
||||||
|
if (targetPosition.dx > screenWidth * 0.7) {
|
||||||
|
rightPosition = 16;
|
||||||
|
} else {
|
||||||
|
rightPosition = 24;
|
||||||
|
}
|
||||||
|
|
||||||
return AnimatedBuilder(
|
return AnimatedBuilder(
|
||||||
animation: _animationController,
|
animation: _animationController,
|
||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
|
|
@ -113,21 +124,23 @@ class _LanguageSelectionOverlayState extends State<LanguageSelectionOverlay>
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: _closeOverlay,
|
onTap: _closeOverlay,
|
||||||
behavior: HitTestBehavior.translucent,
|
behavior: HitTestBehavior.translucent,
|
||||||
child: Container(
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: double.infinity,
|
height: double.infinity,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Positioned(
|
Positioned(
|
||||||
right: targetPosition.dx + 10,
|
left: leftPosition,
|
||||||
|
right: rightPosition,
|
||||||
top: targetPosition.dy + 40,
|
top: targetPosition.dy + 40,
|
||||||
child: Transform.scale(
|
child: Transform.scale(
|
||||||
scale: _scaleAnimation.value,
|
scale: _scaleAnimation.value,
|
||||||
child: Opacity(
|
child: Opacity(
|
||||||
opacity: _opacityAnimation.value,
|
opacity: _opacityAnimation.value,
|
||||||
child: Container(
|
child: Container(
|
||||||
width:
|
width: overlayWidth,
|
||||||
screenWidth * 0.45, // Less than half screen width
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.surface,
|
color: AppColors.surface,
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius: BorderRadius.only(
|
||||||
|
|
@ -166,6 +179,7 @@ class _LanguageSelectionOverlayState extends State<LanguageSelectionOverlay>
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -177,7 +191,7 @@ class _LanguageSelectionOverlayState extends State<LanguageSelectionOverlay>
|
||||||
String flag,
|
String flag,
|
||||||
bool isSelected,
|
bool isSelected,
|
||||||
) {
|
) {
|
||||||
print('Building language option: $name with flag: $flag'); // Debug log
|
print('Building language option: $name with flag: $flag');
|
||||||
return Material(
|
return Material(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue