Merge pull request #773 from wger-project/feature/improve-min-app-version

Refactor min app version handling
This commit is contained in:
Roland Geider
2025-04-04 18:53:54 +02:00
committed by GitHub
18 changed files with 1121 additions and 1150 deletions

View File

@@ -29,10 +29,6 @@
android:enableOnBackInvokedCallback="true"
android:networkSecurityConfig="@xml/network_security_config">
<meta-data
android:name="wger.check_min_app_version"
android:value="true" />
<activity
android:name=".MainActivity"
android:exported="true"

View File

@@ -31,9 +31,6 @@ const DEFAULT_SERVER_TEST = 'https://wger-master.rge.uber.space/';
const TESTSERVER_USER_NAME = 'user';
const TESTSERVER_PASSWORD = 'flutteruser';
/// Keys used in the android manifest
const MANIFEST_KEY_CHECK_UPDATE = 'wger.check_min_app_version';
/// Default impression for a workout session (neutral)
const DEFAULT_IMPRESSION = 2;

View File

@@ -54,6 +54,7 @@ import 'package:wger/screens/routine_list_screen.dart';
import 'package:wger/screens/routine_logs_screen.dart';
import 'package:wger/screens/routine_screen.dart';
import 'package:wger/screens/splash_screen.dart';
import 'package:wger/screens/update_app_screen.dart';
import 'package:wger/screens/weight_screen.dart';
import 'package:wger/theme/theme.dart';
import 'package:wger/widgets/core/about.dart';
@@ -82,11 +83,27 @@ void main() async {
await PreferenceHelper.instance.migrationSupportFunctionForSharedPreferences();
// Application
runApp(const riverpod.ProviderScope(child: MyApp()));
runApp(const riverpod.ProviderScope(child: MainApp()));
}
class MyApp extends StatelessWidget {
const MyApp();
class MainApp extends StatelessWidget {
const MainApp();
Widget _getHomeScreen(AuthProvider auth) {
if (auth.state == AuthState.loggedIn) {
return HomeTabsScreen();
} else if (auth.state == AuthState.updateRequired) {
return const UpdateAppScreen();
} else {
return FutureBuilder(
future: auth.tryAutoLogin(),
builder: (ctx, authResultSnapshot) =>
authResultSnapshot.connectionState == ConnectionState.waiting
? const SplashScreen()
: const AuthScreen(),
);
}
}
@override
Widget build(BuildContext context) {
@@ -161,15 +178,7 @@ class MyApp extends StatelessWidget {
highContrastTheme: wgerLightThemeHc,
highContrastDarkTheme: wgerDarkThemeHc,
themeMode: user.themeMode,
home: auth.isAuth
? HomeTabsScreen()
: FutureBuilder(
future: auth.tryAutoLogin(),
builder: (ctx, authResultSnapshot) =>
authResultSnapshot.connectionState == ConnectionState.waiting
? const SplashScreen()
: const AuthScreen(),
),
home: _getHomeScreen(auth),
routes: {
DashboardScreen.routeName: (ctx) => const DashboardScreen(),
FormScreen.routeName: (ctx) => const FormScreen(),

View File

@@ -16,7 +16,7 @@ part 'exercise_api.g.dart';
/// Basically this is just used as a convenience to create "real" exercise
/// objects and nothing more
@freezed
class ExerciseApiData with _$ExerciseApiData {
sealed class ExerciseApiData with _$ExerciseApiData {
factory ExerciseApiData({
required int id,
required String uuid,
@@ -52,7 +52,7 @@ class ExerciseApiData with _$ExerciseApiData {
/// Model for the search results returned from the /api/v2/exercise/search endpoint
///
@freezed
class ExerciseSearchDetails with _$ExerciseSearchDetails {
sealed class ExerciseSearchDetails with _$ExerciseSearchDetails {
factory ExerciseSearchDetails({
// ignore: invalid_annotation_target
@JsonKey(name: 'id') required int translationId,
@@ -70,7 +70,7 @@ class ExerciseSearchDetails with _$ExerciseSearchDetails {
}
@freezed
class ExerciseSearchEntry with _$ExerciseSearchEntry {
sealed class ExerciseSearchEntry with _$ExerciseSearchEntry {
factory ExerciseSearchEntry({
required String value,
required ExerciseSearchDetails data,
@@ -81,7 +81,7 @@ class ExerciseSearchEntry with _$ExerciseSearchEntry {
}
@freezed
class ExerciseApiSearch with _$ExerciseApiSearch {
sealed class ExerciseApiSearch with _$ExerciseApiSearch {
factory ExerciseApiSearch({
required List<ExerciseSearchEntry> suggestions,
}) = _ExerciseApiSearch;

File diff suppressed because it is too large Load Diff

View File

@@ -6,8 +6,7 @@ part of 'exercise_api.dart';
// JsonSerializableGenerator
// **************************************************************************
_$ExerciseBaseDataImpl _$$ExerciseBaseDataImplFromJson(Map<String, dynamic> json) =>
_$ExerciseBaseDataImpl(
_ExerciseBaseData _$ExerciseBaseDataFromJson(Map<String, dynamic> json) => _ExerciseBaseData(
id: (json['id'] as num).toInt(),
uuid: json['uuid'] as String,
variationId: (json['variations'] as num?)?.toInt() ?? null,
@@ -39,8 +38,7 @@ _$ExerciseBaseDataImpl _$$ExerciseBaseDataImplFromJson(Map<String, dynamic> json
(json['total_authors_history'] as List<dynamic>).map((e) => e as String).toList(),
);
Map<String, dynamic> _$$ExerciseBaseDataImplToJson(_$ExerciseBaseDataImpl instance) =>
<String, dynamic>{
Map<String, dynamic> _$ExerciseBaseDataToJson(_ExerciseBaseData instance) => <String, dynamic>{
'id': instance.id,
'uuid': instance.uuid,
'variations': instance.variationId,
@@ -58,8 +56,8 @@ Map<String, dynamic> _$$ExerciseBaseDataImplToJson(_$ExerciseBaseDataImpl instan
'total_authors_history': instance.authorsGlobal,
};
_$ExerciseSearchDetailsImpl _$$ExerciseSearchDetailsImplFromJson(Map<String, dynamic> json) =>
_$ExerciseSearchDetailsImpl(
_ExerciseSearchDetails _$ExerciseSearchDetailsFromJson(Map<String, dynamic> json) =>
_ExerciseSearchDetails(
translationId: (json['id'] as num).toInt(),
exerciseId: (json['base_id'] as num).toInt(),
name: json['name'] as String,
@@ -68,7 +66,7 @@ _$ExerciseSearchDetailsImpl _$$ExerciseSearchDetailsImplFromJson(Map<String, dyn
imageThumbnail: json['image_thumbnail'] as String?,
);
Map<String, dynamic> _$$ExerciseSearchDetailsImplToJson(_$ExerciseSearchDetailsImpl instance) =>
Map<String, dynamic> _$ExerciseSearchDetailsToJson(_ExerciseSearchDetails instance) =>
<String, dynamic>{
'id': instance.translationId,
'base_id': instance.exerciseId,
@@ -78,26 +76,24 @@ Map<String, dynamic> _$$ExerciseSearchDetailsImplToJson(_$ExerciseSearchDetailsI
'image_thumbnail': instance.imageThumbnail,
};
_$ExerciseSearchEntryImpl _$$ExerciseSearchEntryImplFromJson(Map<String, dynamic> json) =>
_$ExerciseSearchEntryImpl(
_ExerciseSearchEntry _$ExerciseSearchEntryFromJson(Map<String, dynamic> json) =>
_ExerciseSearchEntry(
value: json['value'] as String,
data: ExerciseSearchDetails.fromJson(json['data'] as Map<String, dynamic>),
);
Map<String, dynamic> _$$ExerciseSearchEntryImplToJson(_$ExerciseSearchEntryImpl instance) =>
Map<String, dynamic> _$ExerciseSearchEntryToJson(_ExerciseSearchEntry instance) =>
<String, dynamic>{
'value': instance.value,
'data': instance.data,
};
_$ExerciseApiSearchImpl _$$ExerciseApiSearchImplFromJson(Map<String, dynamic> json) =>
_$ExerciseApiSearchImpl(
_ExerciseApiSearch _$ExerciseApiSearchFromJson(Map<String, dynamic> json) => _ExerciseApiSearch(
suggestions: (json['suggestions'] as List<dynamic>)
.map((e) => ExerciseSearchEntry.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$$ExerciseApiSearchImplToJson(_$ExerciseApiSearchImpl instance) =>
<String, dynamic>{
Map<String, dynamic> _$ExerciseApiSearchToJson(_ExerciseApiSearch instance) => <String, dynamic>{
'suggestions': instance.suggestions,
};

View File

@@ -5,7 +5,7 @@ part 'ingredient_api.g.dart';
/// Model for the search results returned from the /api/v2/ingredient/search endpoint
@freezed
class IngredientApiSearchDetails with _$IngredientApiSearchDetails {
sealed class IngredientApiSearchDetails with _$IngredientApiSearchDetails {
factory IngredientApiSearchDetails({
required int id,
required String name,
@@ -19,7 +19,7 @@ class IngredientApiSearchDetails with _$IngredientApiSearchDetails {
}
@freezed
class IngredientApiSearchEntry with _$IngredientApiSearchEntry {
sealed class IngredientApiSearchEntry with _$IngredientApiSearchEntry {
factory IngredientApiSearchEntry({
required String value,
required IngredientApiSearchDetails data,
@@ -30,7 +30,7 @@ class IngredientApiSearchEntry with _$IngredientApiSearchEntry {
}
@freezed
class IngredientApiSearch with _$IngredientApiSearch {
sealed class IngredientApiSearch with _$IngredientApiSearch {
factory IngredientApiSearch({
required List<IngredientApiSearchEntry> suggestions,
}) = _IngredientApiSearch;

View File

@@ -1,3 +1,4 @@
// dart format width=80
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
@@ -9,94 +10,55 @@ part of 'ingredient_api.dart';
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
IngredientApiSearchDetails _$IngredientApiSearchDetailsFromJson(Map<String, dynamic> json) {
return _IngredientApiSearchDetails.fromJson(json);
}
/// @nodoc
mixin _$IngredientApiSearchDetails {
int get id => throw _privateConstructorUsedError;
String get name => throw _privateConstructorUsedError;
String? get image => throw _privateConstructorUsedError; // ignore: invalid_annotation_target
int get id;
String get name;
String? get image; // ignore: invalid_annotation_target
@JsonKey(name: 'image_thumbnail')
String? get imageThumbnail => throw _privateConstructorUsedError;
/// Serializes this IngredientApiSearchDetails to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
String? get imageThumbnail;
/// Create a copy of IngredientApiSearchDetails
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$IngredientApiSearchDetailsCopyWith<IngredientApiSearchDetails> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $IngredientApiSearchDetailsCopyWith<$Res> {
factory $IngredientApiSearchDetailsCopyWith(
IngredientApiSearchDetails value, $Res Function(IngredientApiSearchDetails) then) =
_$IngredientApiSearchDetailsCopyWithImpl<$Res, IngredientApiSearchDetails>;
@useResult
$Res call(
{int id,
String name,
String? image,
@JsonKey(name: 'image_thumbnail') String? imageThumbnail});
}
/// @nodoc
class _$IngredientApiSearchDetailsCopyWithImpl<$Res, $Val extends IngredientApiSearchDetails>
implements $IngredientApiSearchDetailsCopyWith<$Res> {
_$IngredientApiSearchDetailsCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of IngredientApiSearchDetails
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
$IngredientApiSearchDetailsCopyWith<IngredientApiSearchDetails> get copyWith =>
_$IngredientApiSearchDetailsCopyWithImpl<IngredientApiSearchDetails>(
this as IngredientApiSearchDetails, _$identity);
/// Serializes this IngredientApiSearchDetails to a JSON map.
Map<String, dynamic> toJson();
@override
$Res call({
Object? id = null,
Object? name = null,
Object? image = freezed,
Object? imageThumbnail = freezed,
}) {
return _then(_value.copyWith(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
image: freezed == image
? _value.image
: image // ignore: cast_nullable_to_non_nullable
as String?,
imageThumbnail: freezed == imageThumbnail
? _value.imageThumbnail
: imageThumbnail // ignore: cast_nullable_to_non_nullable
as String?,
) as $Val);
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is IngredientApiSearchDetails &&
(identical(other.id, id) || other.id == id) &&
(identical(other.name, name) || other.name == name) &&
(identical(other.image, image) || other.image == image) &&
(identical(other.imageThumbnail, imageThumbnail) ||
other.imageThumbnail == imageThumbnail));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, id, name, image, imageThumbnail);
@override
String toString() {
return 'IngredientApiSearchDetails(id: $id, name: $name, image: $image, imageThumbnail: $imageThumbnail)';
}
}
/// @nodoc
abstract class _$$IngredientApiSearchDetailsImplCopyWith<$Res>
implements $IngredientApiSearchDetailsCopyWith<$Res> {
factory _$$IngredientApiSearchDetailsImplCopyWith(_$IngredientApiSearchDetailsImpl value,
$Res Function(_$IngredientApiSearchDetailsImpl) then) =
__$$IngredientApiSearchDetailsImplCopyWithImpl<$Res>;
@override
abstract mixin class $IngredientApiSearchDetailsCopyWith<$Res> {
factory $IngredientApiSearchDetailsCopyWith(
IngredientApiSearchDetails value, $Res Function(IngredientApiSearchDetails) _then) =
_$IngredientApiSearchDetailsCopyWithImpl;
@useResult
$Res call(
{int id,
@@ -106,12 +68,12 @@ abstract class _$$IngredientApiSearchDetailsImplCopyWith<$Res>
}
/// @nodoc
class __$$IngredientApiSearchDetailsImplCopyWithImpl<$Res>
extends _$IngredientApiSearchDetailsCopyWithImpl<$Res, _$IngredientApiSearchDetailsImpl>
implements _$$IngredientApiSearchDetailsImplCopyWith<$Res> {
__$$IngredientApiSearchDetailsImplCopyWithImpl(_$IngredientApiSearchDetailsImpl _value,
$Res Function(_$IngredientApiSearchDetailsImpl) _then)
: super(_value, _then);
class _$IngredientApiSearchDetailsCopyWithImpl<$Res>
implements $IngredientApiSearchDetailsCopyWith<$Res> {
_$IngredientApiSearchDetailsCopyWithImpl(this._self, this._then);
final IngredientApiSearchDetails _self;
final $Res Function(IngredientApiSearchDetails) _then;
/// Create a copy of IngredientApiSearchDetails
/// with the given fields replaced by the non-null parameter values.
@@ -123,21 +85,21 @@ class __$$IngredientApiSearchDetailsImplCopyWithImpl<$Res>
Object? image = freezed,
Object? imageThumbnail = freezed,
}) {
return _then(_$IngredientApiSearchDetailsImpl(
return _then(_self.copyWith(
id: null == id
? _value.id
? _self.id
: id // ignore: cast_nullable_to_non_nullable
as int,
name: null == name
? _value.name
? _self.name
: name // ignore: cast_nullable_to_non_nullable
as String,
image: freezed == image
? _value.image
? _self.image
: image // ignore: cast_nullable_to_non_nullable
as String?,
imageThumbnail: freezed == imageThumbnail
? _value.imageThumbnail
? _self.imageThumbnail
: imageThumbnail // ignore: cast_nullable_to_non_nullable
as String?,
));
@@ -146,15 +108,14 @@ class __$$IngredientApiSearchDetailsImplCopyWithImpl<$Res>
/// @nodoc
@JsonSerializable()
class _$IngredientApiSearchDetailsImpl implements _IngredientApiSearchDetails {
_$IngredientApiSearchDetailsImpl(
class _IngredientApiSearchDetails implements IngredientApiSearchDetails {
_IngredientApiSearchDetails(
{required this.id,
required this.name,
required this.image,
@JsonKey(name: 'image_thumbnail') required this.imageThumbnail});
factory _$IngredientApiSearchDetailsImpl.fromJson(Map<String, dynamic> json) =>
_$$IngredientApiSearchDetailsImplFromJson(json);
factory _IngredientApiSearchDetails.fromJson(Map<String, dynamic> json) =>
_$IngredientApiSearchDetailsFromJson(json);
@override
final int id;
@@ -167,16 +128,26 @@ class _$IngredientApiSearchDetailsImpl implements _IngredientApiSearchDetails {
@JsonKey(name: 'image_thumbnail')
final String? imageThumbnail;
/// Create a copy of IngredientApiSearchDetails
/// with the given fields replaced by the non-null parameter values.
@override
String toString() {
return 'IngredientApiSearchDetails(id: $id, name: $name, image: $image, imageThumbnail: $imageThumbnail)';
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$IngredientApiSearchDetailsCopyWith<_IngredientApiSearchDetails> get copyWith =>
__$IngredientApiSearchDetailsCopyWithImpl<_IngredientApiSearchDetails>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$IngredientApiSearchDetailsToJson(
this,
);
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$IngredientApiSearchDetailsImpl &&
other is _IngredientApiSearchDetails &&
(identical(other.id, id) || other.id == id) &&
(identical(other.name, name) || other.name == name) &&
(identical(other.image, image) || other.image == image) &&
@@ -188,189 +159,87 @@ class _$IngredientApiSearchDetailsImpl implements _IngredientApiSearchDetails {
@override
int get hashCode => Object.hash(runtimeType, id, name, image, imageThumbnail);
/// Create a copy of IngredientApiSearchDetails
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$IngredientApiSearchDetailsImplCopyWith<_$IngredientApiSearchDetailsImpl> get copyWith =>
__$$IngredientApiSearchDetailsImplCopyWithImpl<_$IngredientApiSearchDetailsImpl>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$IngredientApiSearchDetailsImplToJson(
this,
);
String toString() {
return 'IngredientApiSearchDetails(id: $id, name: $name, image: $image, imageThumbnail: $imageThumbnail)';
}
}
abstract class _IngredientApiSearchDetails implements IngredientApiSearchDetails {
factory _IngredientApiSearchDetails(
{required final int id,
required final String name,
required final String? image,
@JsonKey(name: 'image_thumbnail') required final String? imageThumbnail}) =
_$IngredientApiSearchDetailsImpl;
/// @nodoc
abstract mixin class _$IngredientApiSearchDetailsCopyWith<$Res>
implements $IngredientApiSearchDetailsCopyWith<$Res> {
factory _$IngredientApiSearchDetailsCopyWith(
_IngredientApiSearchDetails value, $Res Function(_IngredientApiSearchDetails) _then) =
__$IngredientApiSearchDetailsCopyWithImpl;
@override
@useResult
$Res call(
{int id,
String name,
String? image,
@JsonKey(name: 'image_thumbnail') String? imageThumbnail});
}
factory _IngredientApiSearchDetails.fromJson(Map<String, dynamic> json) =
_$IngredientApiSearchDetailsImpl.fromJson;
/// @nodoc
class __$IngredientApiSearchDetailsCopyWithImpl<$Res>
implements _$IngredientApiSearchDetailsCopyWith<$Res> {
__$IngredientApiSearchDetailsCopyWithImpl(this._self, this._then);
@override
int get id;
@override
String get name;
@override
String? get image; // ignore: invalid_annotation_target
@override
@JsonKey(name: 'image_thumbnail')
String? get imageThumbnail;
final _IngredientApiSearchDetails _self;
final $Res Function(_IngredientApiSearchDetails) _then;
/// Create a copy of IngredientApiSearchDetails
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$IngredientApiSearchDetailsImplCopyWith<_$IngredientApiSearchDetailsImpl> get copyWith =>
throw _privateConstructorUsedError;
}
IngredientApiSearchEntry _$IngredientApiSearchEntryFromJson(Map<String, dynamic> json) {
return _IngredientApiSearchEntry.fromJson(json);
}
/// @nodoc
mixin _$IngredientApiSearchEntry {
String get value => throw _privateConstructorUsedError;
IngredientApiSearchDetails get data => throw _privateConstructorUsedError;
/// Serializes this IngredientApiSearchEntry to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of IngredientApiSearchEntry
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$IngredientApiSearchEntryCopyWith<IngredientApiSearchEntry> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $IngredientApiSearchEntryCopyWith<$Res> {
factory $IngredientApiSearchEntryCopyWith(
IngredientApiSearchEntry value, $Res Function(IngredientApiSearchEntry) then) =
_$IngredientApiSearchEntryCopyWithImpl<$Res, IngredientApiSearchEntry>;
@useResult
$Res call({String value, IngredientApiSearchDetails data});
$IngredientApiSearchDetailsCopyWith<$Res> get data;
}
/// @nodoc
class _$IngredientApiSearchEntryCopyWithImpl<$Res, $Val extends IngredientApiSearchEntry>
implements $IngredientApiSearchEntryCopyWith<$Res> {
_$IngredientApiSearchEntryCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of IngredientApiSearchEntry
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? value = null,
Object? data = null,
Object? id = null,
Object? name = null,
Object? image = freezed,
Object? imageThumbnail = freezed,
}) {
return _then(_value.copyWith(
value: null == value
? _value.value
: value // ignore: cast_nullable_to_non_nullable
return _then(_IngredientApiSearchDetails(
id: null == id
? _self.id
: id // ignore: cast_nullable_to_non_nullable
as int,
name: null == name
? _self.name
: name // ignore: cast_nullable_to_non_nullable
as String,
data: null == data
? _value.data
: data // ignore: cast_nullable_to_non_nullable
as IngredientApiSearchDetails,
) as $Val);
}
/// Create a copy of IngredientApiSearchEntry
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$IngredientApiSearchDetailsCopyWith<$Res> get data {
return $IngredientApiSearchDetailsCopyWith<$Res>(_value.data, (value) {
return _then(_value.copyWith(data: value) as $Val);
});
}
}
/// @nodoc
abstract class _$$IngredientApiSearchEntryImplCopyWith<$Res>
implements $IngredientApiSearchEntryCopyWith<$Res> {
factory _$$IngredientApiSearchEntryImplCopyWith(_$IngredientApiSearchEntryImpl value,
$Res Function(_$IngredientApiSearchEntryImpl) then) =
__$$IngredientApiSearchEntryImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({String value, IngredientApiSearchDetails data});
@override
$IngredientApiSearchDetailsCopyWith<$Res> get data;
}
/// @nodoc
class __$$IngredientApiSearchEntryImplCopyWithImpl<$Res>
extends _$IngredientApiSearchEntryCopyWithImpl<$Res, _$IngredientApiSearchEntryImpl>
implements _$$IngredientApiSearchEntryImplCopyWith<$Res> {
__$$IngredientApiSearchEntryImplCopyWithImpl(
_$IngredientApiSearchEntryImpl _value, $Res Function(_$IngredientApiSearchEntryImpl) _then)
: super(_value, _then);
/// Create a copy of IngredientApiSearchEntry
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? value = null,
Object? data = null,
}) {
return _then(_$IngredientApiSearchEntryImpl(
value: null == value
? _value.value
: value // ignore: cast_nullable_to_non_nullable
as String,
data: null == data
? _value.data
: data // ignore: cast_nullable_to_non_nullable
as IngredientApiSearchDetails,
image: freezed == image
? _self.image
: image // ignore: cast_nullable_to_non_nullable
as String?,
imageThumbnail: freezed == imageThumbnail
? _self.imageThumbnail
: imageThumbnail // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// @nodoc
@JsonSerializable()
class _$IngredientApiSearchEntryImpl implements _IngredientApiSearchEntry {
_$IngredientApiSearchEntryImpl({required this.value, required this.data});
mixin _$IngredientApiSearchEntry {
String get value;
IngredientApiSearchDetails get data;
factory _$IngredientApiSearchEntryImpl.fromJson(Map<String, dynamic> json) =>
_$$IngredientApiSearchEntryImplFromJson(json);
/// Create a copy of IngredientApiSearchEntry
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$IngredientApiSearchEntryCopyWith<IngredientApiSearchEntry> get copyWith =>
_$IngredientApiSearchEntryCopyWithImpl<IngredientApiSearchEntry>(
this as IngredientApiSearchEntry, _$identity);
@override
final String value;
@override
final IngredientApiSearchDetails data;
@override
String toString() {
return 'IngredientApiSearchEntry(value: $value, data: $data)';
}
/// Serializes this IngredientApiSearchEntry to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$IngredientApiSearchEntryImpl &&
other is IngredientApiSearchEntry &&
(identical(other.value, value) || other.value == value) &&
(identical(other.data, data) || other.data == data));
}
@@ -379,115 +248,209 @@ class _$IngredientApiSearchEntryImpl implements _IngredientApiSearchEntry {
@override
int get hashCode => Object.hash(runtimeType, value, data);
/// Create a copy of IngredientApiSearchEntry
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$IngredientApiSearchEntryImplCopyWith<_$IngredientApiSearchEntryImpl> get copyWith =>
__$$IngredientApiSearchEntryImplCopyWithImpl<_$IngredientApiSearchEntryImpl>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$IngredientApiSearchEntryImplToJson(
this,
);
String toString() {
return 'IngredientApiSearchEntry(value: $value, data: $data)';
}
}
abstract class _IngredientApiSearchEntry implements IngredientApiSearchEntry {
factory _IngredientApiSearchEntry(
{required final String value,
required final IngredientApiSearchDetails data}) = _$IngredientApiSearchEntryImpl;
/// @nodoc
abstract mixin class $IngredientApiSearchEntryCopyWith<$Res> {
factory $IngredientApiSearchEntryCopyWith(
IngredientApiSearchEntry value, $Res Function(IngredientApiSearchEntry) _then) =
_$IngredientApiSearchEntryCopyWithImpl;
@useResult
$Res call({String value, IngredientApiSearchDetails data});
factory _IngredientApiSearchEntry.fromJson(Map<String, dynamic> json) =
_$IngredientApiSearchEntryImpl.fromJson;
$IngredientApiSearchDetailsCopyWith<$Res> get data;
}
/// @nodoc
class _$IngredientApiSearchEntryCopyWithImpl<$Res>
implements $IngredientApiSearchEntryCopyWith<$Res> {
_$IngredientApiSearchEntryCopyWithImpl(this._self, this._then);
final IngredientApiSearchEntry _self;
final $Res Function(IngredientApiSearchEntry) _then;
/// Create a copy of IngredientApiSearchEntry
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? value = null,
Object? data = null,
}) {
return _then(_self.copyWith(
value: null == value
? _self.value
: value // ignore: cast_nullable_to_non_nullable
as String,
data: null == data
? _self.data
: data // ignore: cast_nullable_to_non_nullable
as IngredientApiSearchDetails,
));
}
/// Create a copy of IngredientApiSearchEntry
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$IngredientApiSearchDetailsCopyWith<$Res> get data {
return $IngredientApiSearchDetailsCopyWith<$Res>(_self.data, (value) {
return _then(_self.copyWith(data: value));
});
}
}
/// @nodoc
@JsonSerializable()
class _IngredientApiSearchEntry implements IngredientApiSearchEntry {
_IngredientApiSearchEntry({required this.value, required this.data});
factory _IngredientApiSearchEntry.fromJson(Map<String, dynamic> json) =>
_$IngredientApiSearchEntryFromJson(json);
@override
String get value;
final String value;
@override
IngredientApiSearchDetails get data;
final IngredientApiSearchDetails data;
/// Create a copy of IngredientApiSearchEntry
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$IngredientApiSearchEntryImplCopyWith<_$IngredientApiSearchEntryImpl> get copyWith =>
throw _privateConstructorUsedError;
@pragma('vm:prefer-inline')
_$IngredientApiSearchEntryCopyWith<_IngredientApiSearchEntry> get copyWith =>
__$IngredientApiSearchEntryCopyWithImpl<_IngredientApiSearchEntry>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$IngredientApiSearchEntryToJson(
this,
);
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _IngredientApiSearchEntry &&
(identical(other.value, value) || other.value == value) &&
(identical(other.data, data) || other.data == data));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, value, data);
@override
String toString() {
return 'IngredientApiSearchEntry(value: $value, data: $data)';
}
}
IngredientApiSearch _$IngredientApiSearchFromJson(Map<String, dynamic> json) {
return _IngredientApiSearch.fromJson(json);
/// @nodoc
abstract mixin class _$IngredientApiSearchEntryCopyWith<$Res>
implements $IngredientApiSearchEntryCopyWith<$Res> {
factory _$IngredientApiSearchEntryCopyWith(
_IngredientApiSearchEntry value, $Res Function(_IngredientApiSearchEntry) _then) =
__$IngredientApiSearchEntryCopyWithImpl;
@override
@useResult
$Res call({String value, IngredientApiSearchDetails data});
@override
$IngredientApiSearchDetailsCopyWith<$Res> get data;
}
/// @nodoc
class __$IngredientApiSearchEntryCopyWithImpl<$Res>
implements _$IngredientApiSearchEntryCopyWith<$Res> {
__$IngredientApiSearchEntryCopyWithImpl(this._self, this._then);
final _IngredientApiSearchEntry _self;
final $Res Function(_IngredientApiSearchEntry) _then;
/// Create a copy of IngredientApiSearchEntry
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$Res call({
Object? value = null,
Object? data = null,
}) {
return _then(_IngredientApiSearchEntry(
value: null == value
? _self.value
: value // ignore: cast_nullable_to_non_nullable
as String,
data: null == data
? _self.data
: data // ignore: cast_nullable_to_non_nullable
as IngredientApiSearchDetails,
));
}
/// Create a copy of IngredientApiSearchEntry
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$IngredientApiSearchDetailsCopyWith<$Res> get data {
return $IngredientApiSearchDetailsCopyWith<$Res>(_self.data, (value) {
return _then(_self.copyWith(data: value));
});
}
}
/// @nodoc
mixin _$IngredientApiSearch {
List<IngredientApiSearchEntry> get suggestions => throw _privateConstructorUsedError;
/// Serializes this IngredientApiSearch to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
List<IngredientApiSearchEntry> get suggestions;
/// Create a copy of IngredientApiSearch
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$IngredientApiSearchCopyWith<IngredientApiSearch> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $IngredientApiSearchCopyWith<$Res> {
factory $IngredientApiSearchCopyWith(
IngredientApiSearch value, $Res Function(IngredientApiSearch) then) =
_$IngredientApiSearchCopyWithImpl<$Res, IngredientApiSearch>;
@useResult
$Res call({List<IngredientApiSearchEntry> suggestions});
}
/// @nodoc
class _$IngredientApiSearchCopyWithImpl<$Res, $Val extends IngredientApiSearch>
implements $IngredientApiSearchCopyWith<$Res> {
_$IngredientApiSearchCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of IngredientApiSearch
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
$IngredientApiSearchCopyWith<IngredientApiSearch> get copyWith =>
_$IngredientApiSearchCopyWithImpl<IngredientApiSearch>(
this as IngredientApiSearch, _$identity);
/// Serializes this IngredientApiSearch to a JSON map.
Map<String, dynamic> toJson();
@override
$Res call({
Object? suggestions = null,
}) {
return _then(_value.copyWith(
suggestions: null == suggestions
? _value.suggestions
: suggestions // ignore: cast_nullable_to_non_nullable
as List<IngredientApiSearchEntry>,
) as $Val);
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is IngredientApiSearch &&
const DeepCollectionEquality().equals(other.suggestions, suggestions));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, const DeepCollectionEquality().hash(suggestions));
@override
String toString() {
return 'IngredientApiSearch(suggestions: $suggestions)';
}
}
/// @nodoc
abstract class _$$IngredientApiSearchImplCopyWith<$Res>
implements $IngredientApiSearchCopyWith<$Res> {
factory _$$IngredientApiSearchImplCopyWith(
_$IngredientApiSearchImpl value, $Res Function(_$IngredientApiSearchImpl) then) =
__$$IngredientApiSearchImplCopyWithImpl<$Res>;
@override
abstract mixin class $IngredientApiSearchCopyWith<$Res> {
factory $IngredientApiSearchCopyWith(
IngredientApiSearch value, $Res Function(IngredientApiSearch) _then) =
_$IngredientApiSearchCopyWithImpl;
@useResult
$Res call({List<IngredientApiSearchEntry> suggestions});
}
/// @nodoc
class __$$IngredientApiSearchImplCopyWithImpl<$Res>
extends _$IngredientApiSearchCopyWithImpl<$Res, _$IngredientApiSearchImpl>
implements _$$IngredientApiSearchImplCopyWith<$Res> {
__$$IngredientApiSearchImplCopyWithImpl(
_$IngredientApiSearchImpl _value, $Res Function(_$IngredientApiSearchImpl) _then)
: super(_value, _then);
class _$IngredientApiSearchCopyWithImpl<$Res> implements $IngredientApiSearchCopyWith<$Res> {
_$IngredientApiSearchCopyWithImpl(this._self, this._then);
final IngredientApiSearch _self;
final $Res Function(IngredientApiSearch) _then;
/// Create a copy of IngredientApiSearch
/// with the given fields replaced by the non-null parameter values.
@@ -496,9 +459,9 @@ class __$$IngredientApiSearchImplCopyWithImpl<$Res>
$Res call({
Object? suggestions = null,
}) {
return _then(_$IngredientApiSearchImpl(
return _then(_self.copyWith(
suggestions: null == suggestions
? _value._suggestions
? _self.suggestions
: suggestions // ignore: cast_nullable_to_non_nullable
as List<IngredientApiSearchEntry>,
));
@@ -507,12 +470,11 @@ class __$$IngredientApiSearchImplCopyWithImpl<$Res>
/// @nodoc
@JsonSerializable()
class _$IngredientApiSearchImpl implements _IngredientApiSearch {
_$IngredientApiSearchImpl({required final List<IngredientApiSearchEntry> suggestions})
class _IngredientApiSearch implements IngredientApiSearch {
_IngredientApiSearch({required final List<IngredientApiSearchEntry> suggestions})
: _suggestions = suggestions;
factory _$IngredientApiSearchImpl.fromJson(Map<String, dynamic> json) =>
_$$IngredientApiSearchImplFromJson(json);
factory _IngredientApiSearch.fromJson(Map<String, dynamic> json) =>
_$IngredientApiSearchFromJson(json);
final List<IngredientApiSearchEntry> _suggestions;
@override
@@ -522,16 +484,26 @@ class _$IngredientApiSearchImpl implements _IngredientApiSearch {
return EqualUnmodifiableListView(_suggestions);
}
/// Create a copy of IngredientApiSearch
/// with the given fields replaced by the non-null parameter values.
@override
String toString() {
return 'IngredientApiSearch(suggestions: $suggestions)';
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$IngredientApiSearchCopyWith<_IngredientApiSearch> get copyWith =>
__$IngredientApiSearchCopyWithImpl<_IngredientApiSearch>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$IngredientApiSearchToJson(
this,
);
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$IngredientApiSearchImpl &&
other is _IngredientApiSearch &&
const DeepCollectionEquality().equals(other._suggestions, _suggestions));
}
@@ -539,36 +511,44 @@ class _$IngredientApiSearchImpl implements _IngredientApiSearch {
@override
int get hashCode => Object.hash(runtimeType, const DeepCollectionEquality().hash(_suggestions));
/// Create a copy of IngredientApiSearch
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$IngredientApiSearchImplCopyWith<_$IngredientApiSearchImpl> get copyWith =>
__$$IngredientApiSearchImplCopyWithImpl<_$IngredientApiSearchImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$IngredientApiSearchImplToJson(
this,
);
String toString() {
return 'IngredientApiSearch(suggestions: $suggestions)';
}
}
abstract class _IngredientApiSearch implements IngredientApiSearch {
factory _IngredientApiSearch({required final List<IngredientApiSearchEntry> suggestions}) =
_$IngredientApiSearchImpl;
factory _IngredientApiSearch.fromJson(Map<String, dynamic> json) =
_$IngredientApiSearchImpl.fromJson;
/// @nodoc
abstract mixin class _$IngredientApiSearchCopyWith<$Res>
implements $IngredientApiSearchCopyWith<$Res> {
factory _$IngredientApiSearchCopyWith(
_IngredientApiSearch value, $Res Function(_IngredientApiSearch) _then) =
__$IngredientApiSearchCopyWithImpl;
@override
List<IngredientApiSearchEntry> get suggestions;
@useResult
$Res call({List<IngredientApiSearchEntry> suggestions});
}
/// @nodoc
class __$IngredientApiSearchCopyWithImpl<$Res> implements _$IngredientApiSearchCopyWith<$Res> {
__$IngredientApiSearchCopyWithImpl(this._self, this._then);
final _IngredientApiSearch _self;
final $Res Function(_IngredientApiSearch) _then;
/// Create a copy of IngredientApiSearch
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$IngredientApiSearchImplCopyWith<_$IngredientApiSearchImpl> get copyWith =>
throw _privateConstructorUsedError;
@pragma('vm:prefer-inline')
$Res call({
Object? suggestions = null,
}) {
return _then(_IngredientApiSearch(
suggestions: null == suggestions
? _self._suggestions
: suggestions // ignore: cast_nullable_to_non_nullable
as List<IngredientApiSearchEntry>,
));
}
}
// dart format on

View File

@@ -6,17 +6,15 @@ part of 'ingredient_api.dart';
// JsonSerializableGenerator
// **************************************************************************
_$IngredientApiSearchDetailsImpl _$$IngredientApiSearchDetailsImplFromJson(
Map<String, dynamic> json) =>
_$IngredientApiSearchDetailsImpl(
_IngredientApiSearchDetails _$IngredientApiSearchDetailsFromJson(Map<String, dynamic> json) =>
_IngredientApiSearchDetails(
id: (json['id'] as num).toInt(),
name: json['name'] as String,
image: json['image'] as String?,
imageThumbnail: json['image_thumbnail'] as String?,
);
Map<String, dynamic> _$$IngredientApiSearchDetailsImplToJson(
_$IngredientApiSearchDetailsImpl instance) =>
Map<String, dynamic> _$IngredientApiSearchDetailsToJson(_IngredientApiSearchDetails instance) =>
<String, dynamic>{
'id': instance.id,
'name': instance.name,
@@ -24,27 +22,26 @@ Map<String, dynamic> _$$IngredientApiSearchDetailsImplToJson(
'image_thumbnail': instance.imageThumbnail,
};
_$IngredientApiSearchEntryImpl _$$IngredientApiSearchEntryImplFromJson(Map<String, dynamic> json) =>
_$IngredientApiSearchEntryImpl(
_IngredientApiSearchEntry _$IngredientApiSearchEntryFromJson(Map<String, dynamic> json) =>
_IngredientApiSearchEntry(
value: json['value'] as String,
data: IngredientApiSearchDetails.fromJson(json['data'] as Map<String, dynamic>),
);
Map<String, dynamic> _$$IngredientApiSearchEntryImplToJson(
_$IngredientApiSearchEntryImpl instance) =>
Map<String, dynamic> _$IngredientApiSearchEntryToJson(_IngredientApiSearchEntry instance) =>
<String, dynamic>{
'value': instance.value,
'data': instance.data,
};
_$IngredientApiSearchImpl _$$IngredientApiSearchImplFromJson(Map<String, dynamic> json) =>
_$IngredientApiSearchImpl(
_IngredientApiSearch _$IngredientApiSearchFromJson(Map<String, dynamic> json) =>
_IngredientApiSearch(
suggestions: (json['suggestions'] as List<dynamic>)
.map((e) => IngredientApiSearchEntry.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$$IngredientApiSearchImplToJson(_$IngredientApiSearchImpl instance) =>
Map<String, dynamic> _$IngredientApiSearchToJson(_IngredientApiSearch instance) =>
<String, dynamic>{
'suggestions': instance.suggestions,
};

View File

@@ -38,6 +38,12 @@ enum LoginActions {
proceed,
}
enum AuthState {
updateRequired,
loggedIn,
loggedOut,
}
class AuthProvider with ChangeNotifier {
final _logger = Logger('AuthProvider');
@@ -46,6 +52,7 @@ class AuthProvider with ChangeNotifier {
String? serverVersion;
PackageInfo? applicationVersion;
Map<String, String> metadata = {};
AuthState state = AuthState.loggedOut;
static const MIN_APP_VERSION_URL = 'min-app-version';
static const SERVER_VERSION_URL = 'version';
@@ -54,7 +61,7 @@ class AuthProvider with ChangeNotifier {
late http.Client client;
AuthProvider([http.Client? client, bool? checkMetadata]) {
AuthProvider([http.Client? client]) {
this.client = client ?? http.Client();
}
@@ -83,27 +90,22 @@ class AuthProvider with ChangeNotifier {
}
/// Checking if there is a new version of the application.
Future<bool> applicationUpdateRequired([
String? version,
Map<String, String>? metadata,
]) async {
metadata ??= this.metadata;
if (!metadata.containsKey(MANIFEST_KEY_CHECK_UPDATE) ||
metadata[MANIFEST_KEY_CHECK_UPDATE] == 'false') {
return false;
}
Future<bool> applicationUpdateRequired([String? version]) async {
final applicationCurrentVersion = version ?? applicationVersion!.version;
final response = await client.get(makeUri(serverUrl!, MIN_APP_VERSION_URL));
final currentVersion = Version.parse(applicationCurrentVersion);
final requiredAppVersion = Version.parse(jsonDecode(response.body));
return requiredAppVersion > currentVersion;
final needUpdate = requiredAppVersion > currentVersion;
if (needUpdate) {
_logger.fine('Application update required: $requiredAppVersion > $currentVersion');
}
return needUpdate;
}
/// Registers a new user
Future<Map<String, LoginActions>> register({
Future<LoginActions> register({
required String username,
required String password,
required String email,
@@ -136,7 +138,7 @@ class AuthProvider with ChangeNotifier {
}
/// Authenticates a user
Future<Map<String, LoginActions>> login(
Future<LoginActions> login(
String username,
String password,
String serverUrl,
@@ -160,15 +162,14 @@ class AuthProvider with ChangeNotifier {
await initVersions(serverUrl);
// If update is required don't log in user
if (await applicationUpdateRequired(
applicationVersion!.version,
{MANIFEST_KEY_CHECK_UPDATE: 'true'},
)) {
return {'action': LoginActions.update};
if (await applicationUpdateRequired()) {
state = AuthState.updateRequired;
return LoginActions.update;
}
// Log user in
token = responseData['token'];
state = AuthState.loggedIn;
notifyListeners();
// store login data in shared preferences
@@ -181,7 +182,7 @@ class AuthProvider with ChangeNotifier {
prefs.setString(PREFS_USER, userData);
prefs.setString(PREFS_LAST_SERVER, serverData);
return {'action': LoginActions.proceed};
return LoginActions.proceed;
}
/// Loads the last server URL from which the user successfully logged in
@@ -195,23 +196,58 @@ class AuthProvider with ChangeNotifier {
return userData['serverUrl'] as String;
}
Future<bool> tryAutoLogin() async {
/// Tries to auto-login the user with the stored token
Future<void> tryAutoLogin() async {
final prefs = PreferenceHelper.asyncPref;
if (!(await prefs.containsKey(PREFS_USER))) {
_logger.info('autologin failed');
return false;
_logger.info('autologin failed, no saved user data');
state = AuthState.loggedOut;
return;
}
final extractedUserData = json.decode((await prefs.getString(PREFS_USER))!);
token = extractedUserData['token'];
serverUrl = extractedUserData['serverUrl'];
final userData = json.decode((await prefs.getString(PREFS_USER))!);
if (!userData.containsKey('token') || !userData.containsKey('serverUrl')) {
_logger.info('autologin failed, no token or serverUrl');
state = AuthState.loggedOut;
return;
}
token = userData['token'];
serverUrl = userData['serverUrl'];
if (token == null || serverUrl == null) {
_logger.info('autologin failed, token or serverUrl is null');
state = AuthState.loggedOut;
return;
}
// // Try to talk to a URL using the token, if this doesn't work, log out
final response = await client.head(
makeUri(serverUrl!, 'routine'),
headers: {
HttpHeaders.contentTypeHeader: 'application/json; charset=UTF-8',
HttpHeaders.userAgentHeader: getAppNameHeader(),
HttpHeaders.authorizationHeader: 'Token $token'
},
);
if (response.statusCode != 200) {
_logger.info('autologin failed, statusCode: ${response.statusCode}');
await logout();
return;
}
await initVersions(serverUrl!);
// If update is required don't log in user
if (await applicationUpdateRequired()) {
state = AuthState.updateRequired;
} else {
state = AuthState.loggedIn;
_logger.info('autologin successful');
}
_logger.info('autologin successful');
setApplicationVersion();
setServerVersion();
notifyListeners();
//_autoLogout();
return true;
}
Future<void> logout({bool shouldNotify = true}) async {
@@ -219,6 +255,7 @@ class AuthProvider with ChangeNotifier {
token = null;
serverUrl = null;
dataInit = false;
state = AuthState.loggedOut;
if (shouldNotify) {
notifyListeners();
@@ -236,7 +273,8 @@ class AuthProvider with ChangeNotifier {
if (applicationVersion != null) {
out = '/${applicationVersion!.version} '
'(${applicationVersion!.packageName}; '
'build: ${applicationVersion!.buildNumber})';
'build: ${applicationVersion!.buildNumber})'
' - https://github.com/wger-project';
}
return 'wger App$out';
}

View File

@@ -89,12 +89,6 @@ class AuthScreen extends StatelessWidget {
),
),
),
// Positioned(
// top: 0.4 * deviceSize.height,
// left: 15,
// right: 15,
// child: const ,
// ),
],
),
);
@@ -166,7 +160,6 @@ class _AuthCardState extends State<AuthCard> {
void _submit(BuildContext context) async {
if (!_formKey.currentState!.validate()) {
// Invalid!
return;
}
_formKey.currentState!.save();
@@ -176,7 +169,7 @@ class _AuthCardState extends State<AuthCard> {
try {
// Login existing user
late Map<String, LoginActions> res;
late LoginActions res;
if (_authMode == AuthMode.Login) {
res = await Provider.of<AuthProvider>(context, listen: false).login(
_authData['username']!,
@@ -196,13 +189,11 @@ class _AuthCardState extends State<AuthCard> {
}
// Check if update is required else continue normally
if (res.containsKey('action')) {
if (res['action'] == LoginActions.update && mounted) {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => const UpdateAppScreen()),
);
return;
}
if (res == LoginActions.update && mounted) {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => const UpdateAppScreen()),
);
return;
}
setState(() {

View File

@@ -146,27 +146,8 @@ class _HomeTabsScreenState extends State<HomeTabsScreen> with SingleTickerProvid
future: _initialData,
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Center(
child: SizedBox(
height: 70,
child: RiveAnimation.asset(
'assets/animations/wger_logo.riv',
animations: ['idle_loop2'],
),
),
),
Text(
AppLocalizations.of(context).loadingText,
style: Theme.of(context).textTheme.headlineSmall,
),
],
),
),
return const Scaffold(
body: LoadingWidget(),
);
} else {
return Scaffold(
@@ -204,3 +185,33 @@ class _HomeTabsScreenState extends State<HomeTabsScreen> with SingleTickerProvid
);
}
}
class LoadingWidget extends StatelessWidget {
const LoadingWidget({
super.key,
});
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Center(
child: SizedBox(
height: 70,
child: RiveAnimation.asset(
'assets/animations/wger_logo.riv',
animations: ['idle_loop2'],
),
),
),
Text(
AppLocalizations.of(context).loadingText,
style: Theme.of(context).textTheme.headlineSmall,
),
],
),
);
}
}

View File

@@ -30,10 +30,7 @@ class UpdateAppScreen extends StatelessWidget {
AppLocalizations.of(context).appUpdateTitle,
style: Theme.of(context).textTheme.headlineSmall,
),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [Text(AppLocalizations.of(context).appUpdateContent)],
),
content: Text(AppLocalizations.of(context).appUpdateContent),
actions: null,
),
);

View File

@@ -15,11 +15,9 @@ void main() {
path: 'api/v2/min-app-version/',
);
final testMetadata = {'wger.check_min_app_version': 'true'};
setUp(() {
mockClient = MockClient();
authProvider = AuthProvider(mockClient, false);
authProvider = AuthProvider(mockClient);
authProvider.serverUrl = 'http://localhost';
});
@@ -27,7 +25,7 @@ void main() {
test('app version higher than min version', () async {
// arrange
when(mockClient.get(tVersionUri)).thenAnswer((_) => Future(() => Response('"1.2.0"', 200)));
final updateNeeded = await authProvider.applicationUpdateRequired('1.3.0', testMetadata);
final updateNeeded = await authProvider.applicationUpdateRequired('1.3.0');
// assert
expect(updateNeeded, false);
@@ -36,7 +34,7 @@ void main() {
test('app version higher than min version - 1', () async {
// arrange
when(mockClient.get(tVersionUri)).thenAnswer((_) => Future(() => Response('"1.3"', 200)));
final updateNeeded = await authProvider.applicationUpdateRequired('1.1', testMetadata);
final updateNeeded = await authProvider.applicationUpdateRequired('1.1');
// assert
expect(updateNeeded, true);
@@ -45,7 +43,7 @@ void main() {
test('app version higher than min version - 2', () async {
// arrange
when(mockClient.get(tVersionUri)).thenAnswer((_) => Future(() => Response('"1.3.0"', 200)));
final updateNeeded = await authProvider.applicationUpdateRequired('1.1', testMetadata);
final updateNeeded = await authProvider.applicationUpdateRequired('1.1');
// assert
expect(updateNeeded, true);
@@ -54,7 +52,7 @@ void main() {
test('app version equal as min version', () async {
// arrange
when(mockClient.get(tVersionUri)).thenAnswer((_) => Future(() => Response('"1.3.0"', 200)));
final updateNeeded = await authProvider.applicationUpdateRequired('1.3.0', testMetadata);
final updateNeeded = await authProvider.applicationUpdateRequired('1.3.0');
// assert
expect(updateNeeded, false);

View File

@@ -78,7 +78,7 @@ void main() {
setUp(() {
mockClient = MockClient();
authProvider = AuthProvider(mockClient, false);
authProvider = AuthProvider(mockClient);
authProvider.serverUrl = 'https://wger.de';
SharedPreferences.setMockInitialValues({});

View File

@@ -202,6 +202,18 @@ class MockAuthProvider extends _i1.Mock implements _i2.AuthProvider {
returnValueForMissingStub: null,
);
@override
_i2.AuthState get state => (super.noSuchMethod(
Invocation.getter(#state),
returnValue: _i2.AuthState.updateRequired,
) as _i2.AuthState);
@override
set state(_i2.AuthState? _state) => super.noSuchMethod(
Invocation.setter(#state, _state),
returnValueForMissingStub: null,
);
@override
_i3.Client get client => (super.noSuchMethod(
Invocation.getter(#client),
@@ -253,17 +265,13 @@ class MockAuthProvider extends _i1.Mock implements _i2.AuthProvider {
) as _i5.Future<void>);
@override
_i5.Future<bool> applicationUpdateRequired([
String? version,
Map<String, String>? metadata,
]) =>
(super.noSuchMethod(
Invocation.method(#applicationUpdateRequired, [version, metadata]),
_i5.Future<bool> applicationUpdateRequired([String? version]) => (super.noSuchMethod(
Invocation.method(#applicationUpdateRequired, [version]),
returnValue: _i5.Future<bool>.value(false),
) as _i5.Future<bool>);
@override
_i5.Future<Map<String, _i2.LoginActions>> register({
_i5.Future<_i2.LoginActions> register({
required String? username,
required String? password,
required String? email,
@@ -278,23 +286,23 @@ class MockAuthProvider extends _i1.Mock implements _i2.AuthProvider {
#serverUrl: serverUrl,
#locale: locale,
}),
returnValue: _i5.Future<Map<String, _i2.LoginActions>>.value(
<String, _i2.LoginActions>{},
returnValue: _i5.Future<_i2.LoginActions>.value(
_i2.LoginActions.update,
),
) as _i5.Future<Map<String, _i2.LoginActions>>);
) as _i5.Future<_i2.LoginActions>);
@override
_i5.Future<Map<String, _i2.LoginActions>> login(
_i5.Future<_i2.LoginActions> login(
String? username,
String? password,
String? serverUrl,
) =>
(super.noSuchMethod(
Invocation.method(#login, [username, password, serverUrl]),
returnValue: _i5.Future<Map<String, _i2.LoginActions>>.value(
<String, _i2.LoginActions>{},
returnValue: _i5.Future<_i2.LoginActions>.value(
_i2.LoginActions.update,
),
) as _i5.Future<Map<String, _i2.LoginActions>>);
) as _i5.Future<_i2.LoginActions>);
@override
_i5.Future<String> getServerUrlFromPrefs() => (super.noSuchMethod(
@@ -308,10 +316,11 @@ class MockAuthProvider extends _i1.Mock implements _i2.AuthProvider {
) as _i5.Future<String>);
@override
_i5.Future<bool> tryAutoLogin() => (super.noSuchMethod(
_i5.Future<void> tryAutoLogin() => (super.noSuchMethod(
Invocation.method(#tryAutoLogin, []),
returnValue: _i5.Future<bool>.value(false),
) as _i5.Future<bool>);
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
@override
_i5.Future<void> logout({bool? shouldNotify = true}) => (super.noSuchMethod(

View File

@@ -94,6 +94,18 @@ class MockAuthProvider extends _i1.Mock implements _i3.AuthProvider {
returnValueForMissingStub: null,
);
@override
_i3.AuthState get state => (super.noSuchMethod(
Invocation.getter(#state),
returnValue: _i3.AuthState.updateRequired,
) as _i3.AuthState);
@override
set state(_i3.AuthState? _state) => super.noSuchMethod(
Invocation.setter(#state, _state),
returnValueForMissingStub: null,
);
@override
_i2.Client get client => (super.noSuchMethod(
Invocation.getter(#client),
@@ -145,17 +157,13 @@ class MockAuthProvider extends _i1.Mock implements _i3.AuthProvider {
) as _i5.Future<void>);
@override
_i5.Future<bool> applicationUpdateRequired([
String? version,
Map<String, String>? metadata,
]) =>
(super.noSuchMethod(
Invocation.method(#applicationUpdateRequired, [version, metadata]),
_i5.Future<bool> applicationUpdateRequired([String? version]) => (super.noSuchMethod(
Invocation.method(#applicationUpdateRequired, [version]),
returnValue: _i5.Future<bool>.value(false),
) as _i5.Future<bool>);
@override
_i5.Future<Map<String, _i3.LoginActions>> register({
_i5.Future<_i3.LoginActions> register({
required String? username,
required String? password,
required String? email,
@@ -170,23 +178,23 @@ class MockAuthProvider extends _i1.Mock implements _i3.AuthProvider {
#serverUrl: serverUrl,
#locale: locale,
}),
returnValue: _i5.Future<Map<String, _i3.LoginActions>>.value(
<String, _i3.LoginActions>{},
returnValue: _i5.Future<_i3.LoginActions>.value(
_i3.LoginActions.update,
),
) as _i5.Future<Map<String, _i3.LoginActions>>);
) as _i5.Future<_i3.LoginActions>);
@override
_i5.Future<Map<String, _i3.LoginActions>> login(
_i5.Future<_i3.LoginActions> login(
String? username,
String? password,
String? serverUrl,
) =>
(super.noSuchMethod(
Invocation.method(#login, [username, password, serverUrl]),
returnValue: _i5.Future<Map<String, _i3.LoginActions>>.value(
<String, _i3.LoginActions>{},
returnValue: _i5.Future<_i3.LoginActions>.value(
_i3.LoginActions.update,
),
) as _i5.Future<Map<String, _i3.LoginActions>>);
) as _i5.Future<_i3.LoginActions>);
@override
_i5.Future<String> getServerUrlFromPrefs() => (super.noSuchMethod(
@@ -200,10 +208,11 @@ class MockAuthProvider extends _i1.Mock implements _i3.AuthProvider {
) as _i5.Future<String>);
@override
_i5.Future<bool> tryAutoLogin() => (super.noSuchMethod(
_i5.Future<void> tryAutoLogin() => (super.noSuchMethod(
Invocation.method(#tryAutoLogin, []),
returnValue: _i5.Future<bool>.value(false),
) as _i5.Future<bool>);
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
@override
_i5.Future<void> logout({bool? shouldNotify = true}) => (super.noSuchMethod(

View File

@@ -23,7 +23,7 @@ import 'measurements/measurement_provider_test.mocks.dart';
import 'other/base_provider_test.mocks.dart';
// Test Auth provider
final AuthProvider testAuthProvider = AuthProvider(MockClient(), false)
final AuthProvider testAuthProvider = AuthProvider(MockClient())
..token = 'FooBar'
..serverUrl = 'https://localhost';