462 lines
14 KiB
Dart
462 lines
14 KiB
Dart
// ignore_for_file: unused_field, deprecated_member_use
|
|
|
|
import 'dart:io';
|
|
|
|
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_home_widget/home_widget_repository.dart';
|
|
import 'package:didvan/services/app_initalizer.dart';
|
|
import 'package:didvan/services/network/request.dart';
|
|
import 'package:didvan/services/network/request_helper.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:in_app_update/in_app_update.dart';
|
|
import 'package:package_info_plus/package_info_plus.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:universal_html/html.dart' as html;
|
|
import 'package:url_launcher/url_launcher.dart';
|
|
|
|
class Splash extends StatefulWidget {
|
|
const Splash({Key? key}) : super(key: key);
|
|
|
|
@override
|
|
State<Splash> createState() => _SplashState();
|
|
}
|
|
|
|
class _SplashState extends State<Splash> with SingleTickerProviderStateMixin {
|
|
bool _errorOccured = false;
|
|
late AnimationController _pulseController;
|
|
late Animation<double> _pulseAnimation;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
if (kIsWeb) {
|
|
final loader = html.document.getElementById('loading_indicator');
|
|
if (loader != null) {
|
|
loader.remove();
|
|
}
|
|
}
|
|
|
|
_setupAnimations();
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
_startInitialization();
|
|
});
|
|
}
|
|
|
|
void _setupAnimations() {
|
|
_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,
|
|
));
|
|
|
|
_pulseController.repeat(reverse: true);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_pulseController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
void _startInitialization() {
|
|
final themeProvider = context.read<ThemeProvider>();
|
|
final userProvider = context.read<UserProvider>();
|
|
final mediaProvider = context.read<MediaProvider>();
|
|
_initialize(themeProvider, userProvider, mediaProvider);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
|
final colorScheme = Theme.of(context).colorScheme;
|
|
|
|
return AnnotatedRegion<SystemUiOverlayStyle>(
|
|
value: SystemUiOverlayStyle(
|
|
statusBarColor: Colors.transparent,
|
|
statusBarIconBrightness: isDark ? Brightness.light : Brightness.dark,
|
|
systemNavigationBarColor: colorScheme.background,
|
|
),
|
|
child: Scaffold(
|
|
resizeToAvoidBottomInset: true,
|
|
backgroundColor: colorScheme.background,
|
|
body: SafeArea(
|
|
child: Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
const Spacer(flex: 2),
|
|
_buildLogo(),
|
|
const SizedBox(height: 60),
|
|
if (_errorOccured)
|
|
_buildErrorView(colorScheme)
|
|
else
|
|
_buildLoadingView(colorScheme),
|
|
const Spacer(flex: 3),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildLogo() {
|
|
return SvgPicture.asset(
|
|
Assets.horizontalLogoWithText,
|
|
height: 80,
|
|
);
|
|
}
|
|
|
|
Widget _buildLoadingView(ColorScheme colorScheme) {
|
|
return Column(
|
|
children: [
|
|
SizedBox(
|
|
width: 60,
|
|
height: 60,
|
|
child: Image.asset(
|
|
Assets.loadingAnimation,
|
|
fit: BoxFit.contain,
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
AnimatedBuilder(
|
|
animation: _pulseController,
|
|
builder: (context, child) {
|
|
return Opacity(
|
|
opacity: 0.4 + (0.4 * _pulseController.value),
|
|
child: Text(
|
|
'در حال بارگذاری...',
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
color: colorScheme.onBackground.withOpacity(0.6),
|
|
fontWeight: FontWeight.w400,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildErrorView(ColorScheme colorScheme) {
|
|
return Column(
|
|
children: [
|
|
Container(
|
|
width: 56,
|
|
height: 56,
|
|
decoration: BoxDecoration(
|
|
color: colorScheme.error.withOpacity(0.1),
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: Icon(
|
|
Icons.wifi_off_rounded,
|
|
size: 24,
|
|
color: colorScheme.error,
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Text(
|
|
'مشکل در اتصال',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
color: colorScheme.onBackground,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'لطفاً اتصال اینترنت خود را بررسی کنید',
|
|
style: TextStyle(
|
|
fontSize: 13,
|
|
color: colorScheme.onBackground.withOpacity(0.6),
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
ElevatedButton(
|
|
onPressed: () {
|
|
setState(() {
|
|
_errorOccured = false;
|
|
});
|
|
_startInitialization();
|
|
},
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: colorScheme.primary,
|
|
foregroundColor: Colors.white,
|
|
elevation: 0,
|
|
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 12),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
),
|
|
child: const Text('تلاش مجدد'),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Future<void> _initialize(ThemeProvider themeProvider,
|
|
UserProvider userProvider, MediaProvider mediaProvider) async {
|
|
try {
|
|
if (!mounted) return;
|
|
|
|
final settingsData = await AppInitializer.initilizeSettings();
|
|
themeProvider.themeMode = settingsData.themeMode;
|
|
themeProvider.fontFamily = settingsData.fontFamily;
|
|
themeProvider.fontScale = settingsData.fontScale;
|
|
|
|
if (mounted) {
|
|
await AppInitializer.setupServices(context);
|
|
}
|
|
|
|
final String? token = await userProvider.setAndGetToken();
|
|
if (token != null) {
|
|
RequestService.token = token;
|
|
}
|
|
|
|
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
|
|
bool stopForUpdate = await _checkForUpdate();
|
|
if (stopForUpdate) return;
|
|
}
|
|
|
|
if (kIsWeb) {
|
|
html.window.onBeforeUnload.listen((event) {
|
|
StorageService.webStorage
|
|
.removeWhere((key, value) => key == 'image-cache');
|
|
});
|
|
}
|
|
|
|
bool isMobileApp = !kIsWeb && (Platform.isAndroid || Platform.isIOS);
|
|
if (isMobileApp) {
|
|
final String? hasSeenOnboarding =
|
|
await StorageService.getValue(key: 'hasSeenOnboarding');
|
|
|
|
if (hasSeenOnboarding != 'true') {
|
|
if (mounted) {
|
|
Navigator.of(context).pushReplacementNamed(Routes.onboarding);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (token != null) {
|
|
if (!kIsWeb) {
|
|
await mediaProvider.getDownloadsList();
|
|
}
|
|
|
|
final result = await userProvider.getUserInfo();
|
|
|
|
if (!result) {
|
|
await StorageService.delete(key: 'token');
|
|
if (mounted) {
|
|
Navigator.of(context).pushNamedAndRemoveUntil(
|
|
Routes.splash,
|
|
(_) => false,
|
|
);
|
|
}
|
|
return;
|
|
}
|
|
await ServerDataProvider.getData();
|
|
}
|
|
|
|
_navigateToNextScreen(token);
|
|
} catch (e) {
|
|
debugPrint("Error in initialize: $e");
|
|
if (mounted) {
|
|
setState(() {
|
|
_errorOccured = true;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
void _navigateToNextScreen(String? token) async {
|
|
if (!mounted) return;
|
|
|
|
String extractedPath = initialURI?.path == '/'
|
|
? Routes.home
|
|
: (initialURI?.path ?? Routes.home);
|
|
|
|
final String destinationRoute =
|
|
token == null ? Routes.authenticaion : extractedPath;
|
|
|
|
dynamic routeArguments =
|
|
token == null ? {'isResetPassword': false} : {'showDialogs': false};
|
|
|
|
if (destinationRoute == Routes.home) {
|
|
initialURI = null;
|
|
}
|
|
|
|
if (destinationRoute == Routes.authenticaion) {
|
|
routeArguments = false;
|
|
}
|
|
|
|
if (token != null && HomeWidgetRepository.data != null) {
|
|
Navigator.of(context).pushReplacementNamed(
|
|
Routes.home,
|
|
arguments: {'showDialogs': false},
|
|
);
|
|
|
|
await HomeWidgetRepository.decideWhereToGoNotif();
|
|
|
|
if (HomeWidgetRepository.data != null) {
|
|
await StorageService.delete(
|
|
key:
|
|
'notification${AppInitializer.createNotificationId(HomeWidgetRepository.data!)}');
|
|
}
|
|
return;
|
|
}
|
|
|
|
Navigator.of(context).pushReplacementNamed(
|
|
destinationRoute,
|
|
arguments: routeArguments,
|
|
);
|
|
}
|
|
|
|
Future<bool> _checkForUpdate() async {
|
|
try {
|
|
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
|
bool isGooglePlay = packageInfo.installerStore == 'com.android.vending';
|
|
|
|
if (isGooglePlay) {
|
|
try {
|
|
AppUpdateInfo updateInfo = await InAppUpdate.checkForUpdate();
|
|
if (updateInfo.updateAvailability ==
|
|
UpdateAvailability.updateAvailable) {
|
|
if (updateInfo.immediateUpdateAllowed) {
|
|
await InAppUpdate.performImmediateUpdate();
|
|
return true;
|
|
} else if (updateInfo.flexibleUpdateAllowed) {
|
|
await InAppUpdate.startFlexibleUpdate();
|
|
await InAppUpdate.completeFlexibleUpdate();
|
|
return false;
|
|
}
|
|
}
|
|
} catch (e) {
|
|
return await _checkCustomApiUpdate(packageInfo);
|
|
}
|
|
} else {
|
|
return await _checkCustomApiUpdate(packageInfo);
|
|
}
|
|
} catch (e) {
|
|
debugPrint("Update check failed: $e");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Future<bool> _checkCustomApiUpdate(PackageInfo packageInfo) async {
|
|
final req = RequestService(
|
|
RequestHelper.checkVersion,
|
|
useAutherization: true,
|
|
);
|
|
|
|
await req.httpGet();
|
|
|
|
if (req.isSuccess && mounted) {
|
|
final result = req.result;
|
|
final apiVersion = result['version']?.toString();
|
|
final bool isMandatory = result['isMandatory'] ?? false;
|
|
final String downloadUrl =
|
|
result['appName'] ?? 'https://api.didvan.app/uploads/apk';
|
|
|
|
if (apiVersion != null &&
|
|
_isUpdateAvailable(packageInfo.version, apiVersion)) {
|
|
_showUpdateDialog(downloadUrl, isMandatory);
|
|
return isMandatory;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool _isUpdateAvailable(String currentVersion, String newVersion) {
|
|
try {
|
|
List<int> currentParts =
|
|
currentVersion.split('.').map(int.parse).toList();
|
|
List<int> newParts = newVersion.split('.').map(int.parse).toList();
|
|
|
|
for (int i = 0; i < newParts.length; i++) {
|
|
if (i >= currentParts.length) return true;
|
|
|
|
if (newParts[i] > currentParts[i]) return true;
|
|
if (newParts[i] < currentParts[i]) return false;
|
|
}
|
|
return false;
|
|
} catch (e) {
|
|
debugPrint("Version parsing error: $e");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void _showUpdateDialog(String url, bool isMandatory) {
|
|
if (!mounted) return;
|
|
|
|
showDialog(
|
|
context: context,
|
|
barrierDismissible: !isMandatory,
|
|
builder: (context) {
|
|
return PopScope(
|
|
canPop: !isMandatory,
|
|
child: AlertDialog(
|
|
title: const Text(
|
|
'بهروزرسانی دیدوان',
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(fontWeight: FontWeight.bold),
|
|
),
|
|
content: Text(
|
|
isMandatory
|
|
? 'نسخه جدیدی از دیدوان در دسترس است. برای ادامه استفاده از برنامه باید آن را بهروزرسانی کنید.'
|
|
: 'نسخه جدیدی از برنامه در دسترس است. آیا مایل به بهروزرسانی هستید؟',
|
|
textAlign: TextAlign.center,
|
|
),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
actionsAlignment: MainAxisAlignment.center,
|
|
actions: [
|
|
if (!isMandatory)
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
child:
|
|
const Text('بعداً', style: TextStyle(color: Colors.grey)),
|
|
),
|
|
ElevatedButton(
|
|
onPressed: () async {
|
|
final Uri uri = Uri.parse(url);
|
|
if (await canLaunchUrl(uri)) {
|
|
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
|
}
|
|
},
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Theme.of(context).primaryColor,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(10)),
|
|
),
|
|
child: const Text('دانلود و نصب',
|
|
style: TextStyle(color: Colors.white)),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|