didvan-app/lib/views/splash/splash.dart

471 lines
18 KiB
Dart

import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/assets.dart';
import 'package:didvan/main.dart';
import 'package:didvan/providers/media.dart';
import 'package:didvan/providers/server_data.dart';
import 'package:didvan/providers/theme.dart';
import 'package:didvan/providers/user.dart';
import 'package:didvan/routes/routes.dart';
import 'package:didvan/services/app_initalizer.dart';
import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/storage/storage.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart';
import 'package:universal_html/html.dart' as html;
class Splash extends StatefulWidget {
const Splash({Key? key}) : super(key: key);
@override
State<Splash> createState() => _SplashState();
}
class _SplashState extends State<Splash> with TickerProviderStateMixin {
bool _errorOccured = false;
late ThemeProvider themeProvider;
late UserProvider userProvider;
late MediaProvider mediaProvider;
late AnimationController _pulseController;
late Animation<double> _pulseAnimation;
@override
void initState() {
super.initState();
// Initialize animation controller
_pulseController = AnimationController(
duration: const Duration(milliseconds: 1500),
vsync: this,
);
_pulseAnimation = Tween<double>(
begin: 1.0,
end: 1.1,
).animate(CurvedAnimation(
parent: _pulseController,
curve: Curves.easeInOut,
));
// Start pulse animation
_pulseController.repeat(reverse: true);
themeProvider = context.read<ThemeProvider>();
userProvider = context.read<UserProvider>();
mediaProvider = context.read<MediaProvider>();
_initialize(themeProvider, userProvider, mediaProvider);
}
@override
void dispose() {
_pulseController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final isDark = Theme.of(context).brightness == Brightness.light;
return AnnotatedRegion<SystemUiOverlayStyle>(
value: DesignConfig.systemUiOverlayStyle.copyWith(
systemNavigationBarColor: colorScheme.background,
statusBarColor: Colors.transparent,
statusBarIconBrightness: isDark ? Brightness.light : Brightness.dark,
),
child: Scaffold(
backgroundColor: colorScheme.background,
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
colorScheme.background,
colorScheme.background.withOpacity(0.8),
colorScheme.primary.withOpacity(0.05),
],
),
),
child: Stack(
children: [
// Floating particles background
_buildFloatingParticles(colorScheme),
SafeArea(
child: Column(
children: [
// Top section with logo
Expanded(
flex: 3,
child: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 32),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Logo with subtle animation
TweenAnimationBuilder<double>(
duration: const Duration(milliseconds: 800),
tween: Tween(begin: 0.0, end: 1.0),
builder: (context, value, child) {
return Transform.scale(
scale: 0.7 + (0.3 * value),
child: Opacity(
opacity: value,
child: AnimatedBuilder(
animation: _pulseAnimation,
builder: (context, child) {
return Transform.scale(
scale: _pulseAnimation.value,
child: Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
gradient: RadialGradient(
colors: [
colorScheme.primary.withOpacity(0.05),
Colors.transparent,
],
),
boxShadow: [
BoxShadow(
color: colorScheme.primary.withOpacity(0.15),
blurRadius: 30,
spreadRadius: 8,
offset: const Offset(0, 15),
),
],
),
child: SvgPicture.asset(
Assets.horizontalLogoWithText,
height: 80,
),
),
);
},
),
),
);
},
),
],
),
),
),
// Bottom section with loading/error
Expanded(
flex: 2,
child: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 48),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (!_errorOccured) ...[
// Modern loading indicator
_buildModernLoader(colorScheme),
const SizedBox(height: 24),
// Loading text with fade animation
AnimatedBuilder(
animation: _pulseController,
builder: (context, child) {
return Opacity(
opacity: 0.4 + (0.4 * _pulseController.value),
child: Text(
'در حال بارگذاری...',
style: TextStyle(
fontSize: 16,
color: colorScheme.checkFav.withOpacity(0.7),
fontWeight: FontWeight.w400,
letterSpacing: 0.8,
),
),
);
},
),
],
if (_errorOccured) ...[
// Error state with simple and clean design
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Simple error icon
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: colorScheme.error.withOpacity(0.1),
borderRadius: BorderRadius.circular(30),
),
child: Icon(
Icons.wifi_off_rounded,
size: 28,
color: colorScheme.error,
),
),
const SizedBox(height: 20),
// Clean error message
Text(
'مشکل در اتصال',
style: TextStyle(
fontSize: 16,
color: colorScheme.checkFav,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 8),
Text(
'لطفاً اتصال خود را بررسی کنید',
style: TextStyle(
fontSize: 14,
color: colorScheme.checkFav.withOpacity(0.6),
fontWeight: FontWeight.w400,
),
),
const SizedBox(height: 24),
// Simple retry button
_buildRetryButton(colorScheme),
],
),
],
],
),
),
),
// Brand tagline at bottom
Padding(
padding: const EdgeInsets.only(bottom: 32),
child: TweenAnimationBuilder<double>(
duration: const Duration(milliseconds: 1000),
tween: Tween(begin: 0.0, end: 1.0),
builder: (context, value, child) {
return Opacity(
opacity: value * 0.6,
child: Text(
'توسعه یافته توسط فرتاک',
style: TextStyle(
fontSize: 14,
color: colorScheme.checkFav,
fontWeight: FontWeight.w500,
letterSpacing: 1,
),
),
);
},
),
),
],
),
),
],
),
),
),
);
}
Widget _buildFloatingParticles(ColorScheme colorScheme) {
return AnimatedBuilder(
animation: _pulseController,
builder: (context, child) {
return Stack(
children: [
// Particle 1
Positioned(
top: 100 + (20 * _pulseController.value),
right: 50 + (10 * _pulseController.value),
child: Container(
width: 6,
height: 6,
decoration: BoxDecoration(
color: colorScheme.primary.withOpacity(0.3),
shape: BoxShape.circle,
),
),
),
// Particle 2
Positioned(
top: 200 + (15 * (1 - _pulseController.value)),
left: 80 + (8 * _pulseController.value),
child: Container(
width: 4,
height: 4,
decoration: BoxDecoration(
color: colorScheme.secondary.withOpacity(0.4),
shape: BoxShape.circle,
),
),
),
// Particle 3
Positioned(
bottom: 180 + (25 * _pulseController.value),
right: 100 + (12 * (1 - _pulseController.value)),
child: Container(
width: 8,
height: 8,
decoration: BoxDecoration(
color: colorScheme.primary.withOpacity(0.2),
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: colorScheme.primary.withOpacity(0.1),
blurRadius: 10,
spreadRadius: 3,
),
],
),
),
),
// Particle 4
Positioned(
bottom: 120 + (18 * (1 - _pulseController.value)),
left: 60 + (15 * _pulseController.value),
child: Container(
width: 5,
height: 5,
decoration: BoxDecoration(
color: colorScheme.tertiary.withOpacity(0.35),
shape: BoxShape.circle,
),
),
),
],
);
},
);
}
Widget _buildModernLoader(ColorScheme colorScheme) {
// استفاده از loading animation اصلی و قدیمی دیدوان
return SizedBox(
width: 60,
height: 60,
child: Image.asset(
Assets.loadingAnimation,
width: 60,
height: 60,
fit: BoxFit.contain,
),
);
}
Widget _buildRetryButton(ColorScheme colorScheme) {
return SizedBox(
width: 140,
height: 44,
child: ElevatedButton(
onPressed: () {
setState(() {
_errorOccured = false;
});
_initialize(themeProvider, userProvider, mediaProvider);
},
style: ElevatedButton.styleFrom(
backgroundColor: colorScheme.primary,
foregroundColor: colorScheme.onPrimary,
elevation: 0,
shadowColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(22),
),
),
child: const Text(
'تلاش مجدد',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
),
);
}
Future<void> _initialize(ThemeProvider themeProvider,
UserProvider userProvider, MediaProvider mediaProvider) async {
try {
print("checking if is in browser");
if (kIsWeb) {
print("detected browser");
html.window.onBeforeUnload.listen((event) {
StorageService.webStorage
.removeWhere((key, value) => key == 'image-cache');
});
}
final settingsData = await AppInitializer.initilizeSettings();
themeProvider.themeMode = settingsData.themeMode;
themeProvider.fontFamily = settingsData.fontFamily;
themeProvider.fontScale = settingsData.fontScale;
await AppInitializer.setupServices(navigatorKey.currentContext!);
final String? token = await userProvider.setAndGetToken();
print("detected token as $token");
if (token != null) {
RequestService.token = token;
if (!kIsWeb) {
await mediaProvider.getDownloadsList();
}
final result = await userProvider.getUserInfo();
if (!result) {
print("no results were returned for user info");
try {
StorageService.delete(key: 'token');
} catch (e) {
print("error in case of no user info result: $e");
// catch
}
navigatorKey.currentState!.pushNamedAndRemoveUntil(
Routes.splash,
(_) => false,
);
return;
}
print("got results for user info: $result");
await ServerDataProvider.getData();
}
print("token route is $token");
String extractedPath = initialURI?.path.toString() == '/' ? Routes.home : initialURI?.path.toString() ?? Routes.home;
final String destinationRoute = token == null ? Routes.authenticaion : extractedPath;
// Set showDialogs to false to prevent welcome popups
dynamic routeArguments = token == null ? {'isResetPassword': false} : {'showDialogs': false};
if (destinationRoute == Routes.home) {
print("destination route was home and init uri is $initialURI");
// (routeArguments as Map)['deepLinkUri'] = initialURI;
initialURI = null;
}
if(destinationRoute == Routes.authenticaion){
print("destination route is auth route");
routeArguments = false;
}
print("destination route: $destinationRoute, route args: $routeArguments");
await navigatorKey.currentState!.pushReplacementNamed(
destinationRoute,
arguments: routeArguments,
);
} catch (e) {
setState(() {
_errorOccured = true;
});
}
}
}