From 3808161c883ca2c2d5a608a43e4df989f5d5014b Mon Sep 17 00:00:00 2001 From: "mr.mojtaba" Date: Sun, 20 Jul 2025 11:31:23 +0330 Subject: [PATCH] feat : app version check in splash --- lib/data/model/app_info_model.dart | 28 ++ lib/data/model/app_info_model.freezed.dart | 311 ++++++++++++++++++ lib/data/model/app_info_model.g.dart | 33 ++ lib/presentation/pages/splash/logic.dart | 46 ++- packages/core/lib/core.dart | 1 + .../lib/utils/extension/string_utils.dart | 2 + packages/core/pubspec.lock | 16 + packages/core/pubspec.yaml | 1 + pubspec.lock | 16 + 9 files changed, 443 insertions(+), 11 deletions(-) create mode 100644 lib/data/model/app_info_model.dart create mode 100644 lib/data/model/app_info_model.freezed.dart create mode 100644 lib/data/model/app_info_model.g.dart diff --git a/lib/data/model/app_info_model.dart b/lib/data/model/app_info_model.dart new file mode 100644 index 0000000..b564474 --- /dev/null +++ b/lib/data/model/app_info_model.dart @@ -0,0 +1,28 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'app_info_model.freezed.dart'; +part 'app_info_model.g.dart'; + +@freezed +abstract class AppInfoModel with _$AppInfoModel { + const factory AppInfoModel({ + required String key, + required Info info, + required String downloadLink, + }) = _AppInfoModel; + + factory AppInfoModel.fromJson(Map json) => + _$AppInfoModelFromJson(json); +} + +@freezed +abstract class Info with _$Info { + const factory Info({ + required String version, + required bool required, + required String module, + }) = _Info; + + factory Info.fromJson(Map json) => _$InfoFromJson(json); +} + diff --git a/lib/data/model/app_info_model.freezed.dart b/lib/data/model/app_info_model.freezed.dart new file mode 100644 index 0000000..149e987 --- /dev/null +++ b/lib/data/model/app_info_model.freezed.dart @@ -0,0 +1,311 @@ +// dart format width=80 +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'app_info_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; + +/// @nodoc +mixin _$AppInfoModel { + + String get key; Info get info; String get downloadLink; +/// Create a copy of AppInfoModel +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$AppInfoModelCopyWith get copyWith => _$AppInfoModelCopyWithImpl(this as AppInfoModel, _$identity); + + /// Serializes this AppInfoModel to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is AppInfoModel&&(identical(other.key, key) || other.key == key)&&(identical(other.info, info) || other.info == info)&&(identical(other.downloadLink, downloadLink) || other.downloadLink == downloadLink)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,key,info,downloadLink); + +@override +String toString() { + return 'AppInfoModel(key: $key, info: $info, downloadLink: $downloadLink)'; +} + + +} + +/// @nodoc +abstract mixin class $AppInfoModelCopyWith<$Res> { + factory $AppInfoModelCopyWith(AppInfoModel value, $Res Function(AppInfoModel) _then) = _$AppInfoModelCopyWithImpl; +@useResult +$Res call({ + String key, Info info, String downloadLink +}); + + +$InfoCopyWith<$Res> get info; + +} +/// @nodoc +class _$AppInfoModelCopyWithImpl<$Res> + implements $AppInfoModelCopyWith<$Res> { + _$AppInfoModelCopyWithImpl(this._self, this._then); + + final AppInfoModel _self; + final $Res Function(AppInfoModel) _then; + +/// Create a copy of AppInfoModel +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? key = null,Object? info = null,Object? downloadLink = null,}) { + return _then(_self.copyWith( +key: null == key ? _self.key : key // ignore: cast_nullable_to_non_nullable +as String,info: null == info ? _self.info : info // ignore: cast_nullable_to_non_nullable +as Info,downloadLink: null == downloadLink ? _self.downloadLink : downloadLink // ignore: cast_nullable_to_non_nullable +as String, + )); +} +/// Create a copy of AppInfoModel +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$InfoCopyWith<$Res> get info { + + return $InfoCopyWith<$Res>(_self.info, (value) { + return _then(_self.copyWith(info: value)); + }); +} +} + + +/// @nodoc +@JsonSerializable() + +class _AppInfoModel implements AppInfoModel { + const _AppInfoModel({required this.key, required this.info, required this.downloadLink}); + factory _AppInfoModel.fromJson(Map json) => _$AppInfoModelFromJson(json); + +@override final String key; +@override final Info info; +@override final String downloadLink; + +/// Create a copy of AppInfoModel +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$AppInfoModelCopyWith<_AppInfoModel> get copyWith => __$AppInfoModelCopyWithImpl<_AppInfoModel>(this, _$identity); + +@override +Map toJson() { + return _$AppInfoModelToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppInfoModel&&(identical(other.key, key) || other.key == key)&&(identical(other.info, info) || other.info == info)&&(identical(other.downloadLink, downloadLink) || other.downloadLink == downloadLink)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,key,info,downloadLink); + +@override +String toString() { + return 'AppInfoModel(key: $key, info: $info, downloadLink: $downloadLink)'; +} + + +} + +/// @nodoc +abstract mixin class _$AppInfoModelCopyWith<$Res> implements $AppInfoModelCopyWith<$Res> { + factory _$AppInfoModelCopyWith(_AppInfoModel value, $Res Function(_AppInfoModel) _then) = __$AppInfoModelCopyWithImpl; +@override @useResult +$Res call({ + String key, Info info, String downloadLink +}); + + +@override $InfoCopyWith<$Res> get info; + +} +/// @nodoc +class __$AppInfoModelCopyWithImpl<$Res> + implements _$AppInfoModelCopyWith<$Res> { + __$AppInfoModelCopyWithImpl(this._self, this._then); + + final _AppInfoModel _self; + final $Res Function(_AppInfoModel) _then; + +/// Create a copy of AppInfoModel +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? key = null,Object? info = null,Object? downloadLink = null,}) { + return _then(_AppInfoModel( +key: null == key ? _self.key : key // ignore: cast_nullable_to_non_nullable +as String,info: null == info ? _self.info : info // ignore: cast_nullable_to_non_nullable +as Info,downloadLink: null == downloadLink ? _self.downloadLink : downloadLink // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +/// Create a copy of AppInfoModel +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$InfoCopyWith<$Res> get info { + + return $InfoCopyWith<$Res>(_self.info, (value) { + return _then(_self.copyWith(info: value)); + }); +} +} + + +/// @nodoc +mixin _$Info { + + String get version; bool get required; String get module; +/// Create a copy of Info +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$InfoCopyWith get copyWith => _$InfoCopyWithImpl(this as Info, _$identity); + + /// Serializes this Info to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is Info&&(identical(other.version, version) || other.version == version)&&(identical(other.required, required) || other.required == required)&&(identical(other.module, module) || other.module == module)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,version,required,module); + +@override +String toString() { + return 'Info(version: $version, required: $required, module: $module)'; +} + + +} + +/// @nodoc +abstract mixin class $InfoCopyWith<$Res> { + factory $InfoCopyWith(Info value, $Res Function(Info) _then) = _$InfoCopyWithImpl; +@useResult +$Res call({ + String version, bool required, String module +}); + + + + +} +/// @nodoc +class _$InfoCopyWithImpl<$Res> + implements $InfoCopyWith<$Res> { + _$InfoCopyWithImpl(this._self, this._then); + + final Info _self; + final $Res Function(Info) _then; + +/// Create a copy of Info +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? version = null,Object? required = null,Object? module = null,}) { + return _then(_self.copyWith( +version: null == version ? _self.version : version // ignore: cast_nullable_to_non_nullable +as String,required: null == required ? _self.required : required // ignore: cast_nullable_to_non_nullable +as bool,module: null == module ? _self.module : module // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// @nodoc +@JsonSerializable() + +class _Info implements Info { + const _Info({required this.version, required this.required, required this.module}); + factory _Info.fromJson(Map json) => _$InfoFromJson(json); + +@override final String version; +@override final bool required; +@override final String module; + +/// Create a copy of Info +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$InfoCopyWith<_Info> get copyWith => __$InfoCopyWithImpl<_Info>(this, _$identity); + +@override +Map toJson() { + return _$InfoToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Info&&(identical(other.version, version) || other.version == version)&&(identical(other.required, required) || other.required == required)&&(identical(other.module, module) || other.module == module)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,version,required,module); + +@override +String toString() { + return 'Info(version: $version, required: $required, module: $module)'; +} + + +} + +/// @nodoc +abstract mixin class _$InfoCopyWith<$Res> implements $InfoCopyWith<$Res> { + factory _$InfoCopyWith(_Info value, $Res Function(_Info) _then) = __$InfoCopyWithImpl; +@override @useResult +$Res call({ + String version, bool required, String module +}); + + + + +} +/// @nodoc +class __$InfoCopyWithImpl<$Res> + implements _$InfoCopyWith<$Res> { + __$InfoCopyWithImpl(this._self, this._then); + + final _Info _self; + final $Res Function(_Info) _then; + +/// Create a copy of Info +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? version = null,Object? required = null,Object? module = null,}) { + return _then(_Info( +version: null == version ? _self.version : version // ignore: cast_nullable_to_non_nullable +as String,required: null == required ? _self.required : required // ignore: cast_nullable_to_non_nullable +as bool,module: null == module ? _self.module : module // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + +// dart format on diff --git a/lib/data/model/app_info_model.g.dart b/lib/data/model/app_info_model.g.dart new file mode 100644 index 0000000..3709ee1 --- /dev/null +++ b/lib/data/model/app_info_model.g.dart @@ -0,0 +1,33 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'app_info_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_AppInfoModel _$AppInfoModelFromJson(Map json) => + _AppInfoModel( + key: json['key'] as String, + info: Info.fromJson(json['info'] as Map), + downloadLink: json['downloadLink'] as String, + ); + +Map _$AppInfoModelToJson(_AppInfoModel instance) => + { + 'key': instance.key, + 'info': instance.info, + 'downloadLink': instance.downloadLink, + }; + +_Info _$InfoFromJson(Map json) => _Info( + version: json['version'] as String, + required: json['required'] as bool, + module: json['module'] as String, +); + +Map _$InfoToJson(_Info instance) => { + 'version': instance.version, + 'required': instance.required, + 'module': instance.module, +}; diff --git a/lib/presentation/pages/splash/logic.dart b/lib/presentation/pages/splash/logic.dart index 305ccac..695151f 100644 --- a/lib/presentation/pages/splash/logic.dart +++ b/lib/presentation/pages/splash/logic.dart @@ -2,6 +2,8 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:rasadyar_app/data/model/app_info_model.dart'; +import 'package:rasadyar_app/presentation/routes/app_pages.dart'; import 'package:rasadyar_auth/data/services/token_storage_service.dart'; import 'package:rasadyar_core/core.dart'; @@ -15,8 +17,9 @@ class SplashLogic extends GetxController with GetTickerProviderStateMixin { RxDouble percent = 0.0.obs; final RxnString _updateFilePath = RxnString(); final platform = MethodChannel('apk_installer'); - + final Dio _dio = Dio(); var tokenService = Get.find(); + AppInfoModel? appInfoModel; @override void onInit() { @@ -146,26 +149,47 @@ class SplashLogic extends GetxController with GetTickerProviderStateMixin { void onReady() { super.onReady(); - hasUpdated.value = !hasUpdated.value; + Future.delayed(const Duration(milliseconds: 250), () async { + try { + final isUpdateNeeded = await checkVersion(); + if (isUpdateNeeded) return; - //TODO - /*Future.delayed(const Duration(seconds: 1), () async { - var module = tokenService.appModule.value; - Get.offAndToNamed(getTargetPage(module)); - });*/ + final module = tokenService.appModule.value; + final target = getTargetPage(module); + Get.offAndToNamed(target); + } catch (e, st) { + debugPrint("onReady error: $e\n$st"); + } + }); } @override void onClose() { rotateController.dispose(); scaleController.dispose(); - super.onClose(); } + Future checkVersion() async { + try { + final info = await PackageInfo.fromPlatform(); + int version = info.version.versionNumber; + var res = await _dio.get("https://rsibackend.rasadyaar.ir/app/apk-info/"); + + appInfoModel = AppInfoModel.fromJson(res.data); + + if ((appInfoModel?.info.version.versionNumber ?? 0) > version) { + hasUpdated.value = !hasUpdated.value; + return true; + } + return false; + } catch (e) { + return false; + } + } + Future fileDownload() async { onUpdateDownload.value = true; - Dio dio = Dio(); final dir = await getApplicationDocumentsDirectory(); final filePath = "${dir.path}/app-release.apk"; final file = File(filePath); @@ -178,8 +202,8 @@ class SplashLogic extends GetxController with GetTickerProviderStateMixin { while (attempts < retryCount && !success) { try { - await dio.download( - 'https://everestacademy.ir/app/app-release.apk', + await _dio.download( + appInfoModel?.downloadLink ?? '', filePath, onReceiveProgress: (count, total) { if (total != -1 && total > 0) { diff --git a/packages/core/lib/core.dart b/packages/core/lib/core.dart index eb3c911..c4a96f8 100644 --- a/packages/core/lib/core.dart +++ b/packages/core/lib/core.dart @@ -28,6 +28,7 @@ export 'package:image_picker/image_picker.dart'; //Map and location export 'package:latlong2/latlong.dart'; +export 'package:package_info_plus/package_info_plus.dart'; export 'package:path_provider/path_provider.dart'; export 'package:permission_handler/permission_handler.dart' hide ServiceStatus; export 'package:persian_datetime_picker/persian_datetime_picker.dart'; diff --git a/packages/core/lib/utils/extension/string_utils.dart b/packages/core/lib/utils/extension/string_utils.dart index 47252a1..6e7062b 100644 --- a/packages/core/lib/utils/extension/string_utils.dart +++ b/packages/core/lib/utils/extension/string_utils.dart @@ -37,4 +37,6 @@ extension XString on String { final dateTime = DateTime.parse(this); return Jalali.fromDateTime(dateTime); } + + int get versionNumber => int.parse(replaceAll(".",'')); } \ No newline at end of file diff --git a/packages/core/pubspec.lock b/packages/core/pubspec.lock index 9cb141c..67e0b81 100644 --- a/packages/core/pubspec.lock +++ b/packages/core/pubspec.lock @@ -917,6 +917,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + package_info_plus: + dependency: "direct main" + description: + name: package_info_plus + sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" + url: "https://pub.dev" + source: hosted + version: "8.3.0" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" + url: "https://pub.dev" + source: hosted + version: "3.2.0" path: dependency: transitive description: diff --git a/packages/core/pubspec.yaml b/packages/core/pubspec.yaml index 852db42..ba3903e 100644 --- a/packages/core/pubspec.yaml +++ b/packages/core/pubspec.yaml @@ -14,6 +14,7 @@ dependencies: #utils device_info_plus: ^11.4.0 + package_info_plus: ^8.3.0 ##image_picker image_picker: ^1.1.2 diff --git a/pubspec.lock b/pubspec.lock index 2e46f88..0b09bc9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -949,6 +949,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + package_info_plus: + dependency: transitive + description: + name: package_info_plus + sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" + url: "https://pub.dev" + source: hosted + version: "8.3.0" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" + url: "https://pub.dev" + source: hosted + version: "3.2.0" path: dependency: transitive description: