mirror of
https://github.com/meditohq/medito-app.git
synced 2024-09-11 12:15:46 +03:00
chore: custom headers added, code refactoring (#624)
* chore: custom headers added, code refactoring * fix: request timeout fixed * chore: improved user initialization and retry mechanism * chore: declare const for static value * fix: navigation fixed, removed unnecessary requests * chore: fixed naming convention * chore: improved retry implementation * chore: change provider name --------- Co-authored-by: Michael Speed <michaelcspeed@users.noreply.github.com>
This commit is contained in:
parent
e2cc35c27a
commit
d744e08fb9
@ -1,4 +1,4 @@
|
||||
platform :ios, '11.0'
|
||||
platform :ios, '12.0'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
@ -236,6 +236,6 @@ SPEC CHECKSUMS:
|
||||
url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b
|
||||
vibration: 7d883d141656a1c1a6d8d238616b2042a51a1241
|
||||
|
||||
PODFILE CHECKSUM: 9be41085f3ebdad4d4aaa1df125b86534775346b
|
||||
PODFILE CHECKSUM: e02d150e307466fe01a4335b382fd16ee407a495
|
||||
|
||||
COCOAPODS: 1.12.1
|
||||
|
@ -480,6 +480,7 @@
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.healthcare-fitness";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@ -668,6 +669,7 @@
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.healthcare-fitness";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@ -698,6 +700,7 @@
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.healthcare-fitness";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
@ -13,7 +13,6 @@ class HTTPConstants {
|
||||
static const String PACKS = 'packs';
|
||||
static const String TRACKS = 'tracks';
|
||||
static const String BACKGROUND_SOUNDS = 'backgroundSounds';
|
||||
static const String HOME = 'home';
|
||||
static const String HEADER = 'main/header';
|
||||
static const String QUOTE = 'main/quote';
|
||||
static const String SHORTCUTS = 'main/shortcuts';
|
||||
|
@ -1,94 +0,0 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:Medito/constants/constants.dart';
|
||||
import 'package:Medito/models/models.dart';
|
||||
import 'package:Medito/providers/providers.dart';
|
||||
import 'package:Medito/services/network/dio_client_provider.dart';
|
||||
import 'package:Medito/widgets/snackbar_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
final retryCounterProvider = StateProvider<int>((ref) => 0);
|
||||
|
||||
final appInitializationProvider =
|
||||
FutureProvider.family<void, BuildContext>((ref, context) async {
|
||||
var _authProvider = ref.read(authProvider);
|
||||
try {
|
||||
var retryCount = ref.read(retryCounterProvider);
|
||||
if (retryCount < 2) {
|
||||
_checkInternetConnectivity(context, ref);
|
||||
}
|
||||
await _authProvider.initializeUser();
|
||||
var res = _authProvider.userRes;
|
||||
if (res.body != null) {
|
||||
var _user = res.body as UserTokenModel;
|
||||
_assignNewTokenToDio(ref, _user.token);
|
||||
var deviceInfo = await ref.read(deviceAndAppInfoProvider.future);
|
||||
var data = _setAppOpenedModelData(deviceInfo);
|
||||
|
||||
await ref.read(eventsProvider(event: data.toJson()).future);
|
||||
ref.read(retryCounterProvider.notifier).update((state) => 0);
|
||||
context.go(RouteConstants.homePath);
|
||||
} else {
|
||||
_handleRetry(context, ref, false);
|
||||
}
|
||||
} catch (err) {
|
||||
_handleRetry(context, ref, _authProvider.userRes.body != null);
|
||||
}
|
||||
});
|
||||
|
||||
void _handleRetry(BuildContext context, Ref ref, bool shouldNavigate) {
|
||||
var retryCount = ref.read(retryCounterProvider);
|
||||
if (retryCount < 2) {
|
||||
_recallProvider(ref, context);
|
||||
ref.read(retryCounterProvider.notifier).update((state) => ++state);
|
||||
} else {
|
||||
if (shouldNavigate) {
|
||||
ref.read(retryCounterProvider.notifier).update((state) => 0);
|
||||
context.go(RouteConstants.downloadsPath);
|
||||
} else {
|
||||
_recallProvider(ref, context);
|
||||
if (retryCount < 2) {
|
||||
showSnackBar(context, StringConstants.timeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _recallProvider(Ref<Object?> ref, BuildContext context) {
|
||||
Future.delayed(Duration(seconds: 2), () {
|
||||
ref.refresh(appInitializationProvider(context));
|
||||
});
|
||||
}
|
||||
|
||||
void _checkInternetConnectivity(BuildContext context, Ref ref) {
|
||||
var connectivityStatus = ref.read(connectivityStatusProvider);
|
||||
if (connectivityStatus == ConnectivityStatus.isDisonnected) {
|
||||
showSnackBar(context, StringConstants.connectivityError);
|
||||
}
|
||||
}
|
||||
|
||||
void _assignNewTokenToDio(Ref ref, String token) {
|
||||
ref
|
||||
.read(dioClientProvider)
|
||||
.dio
|
||||
.options
|
||||
.headers[HttpHeaders.authorizationHeader] = 'Bearer $token';
|
||||
}
|
||||
|
||||
EventsModel _setAppOpenedModelData(DeviceAndAppInfoModel val) {
|
||||
var appOpenedModel = AppOpenedModel(
|
||||
deviceOs: val.os,
|
||||
deviceLanguage: val.languageCode,
|
||||
deviceModel: val.model,
|
||||
buildNumber: val.buildNumber,
|
||||
appVersion: val.appVersion,
|
||||
);
|
||||
var event = EventsModel(
|
||||
name: EventTypes.appOpened,
|
||||
payload: appOpenedModel.toJson(),
|
||||
);
|
||||
|
||||
return event;
|
||||
}
|
@ -22,9 +22,9 @@ class AuthNotifier extends ChangeNotifier {
|
||||
|
||||
final AuthRepository authRepository;
|
||||
final Ref ref;
|
||||
ApiResponse userRes = ApiResponse.completed(null);
|
||||
ApiResponse sendOTPRes = ApiResponse.completed(null);
|
||||
ApiResponse verifyOTPRes = ApiResponse.completed(null);
|
||||
ApiResponse userResponse = ApiResponse.completed(null);
|
||||
ApiResponse sendOTPResponse = ApiResponse.completed(null);
|
||||
ApiResponse verifyOTPResponse = ApiResponse.completed(null);
|
||||
String? userEmail;
|
||||
bool counter = false;
|
||||
bool isAGuest = false;
|
||||
@ -49,45 +49,45 @@ class AuthNotifier extends ChangeNotifier {
|
||||
if (res == null) {
|
||||
await generateUserToken();
|
||||
} else {
|
||||
userRes = ApiResponse.completed(res);
|
||||
userResponse = ApiResponse.completed(res);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> generateUserToken() async {
|
||||
userRes = ApiResponse.loading();
|
||||
userResponse = ApiResponse.loading();
|
||||
notifyListeners();
|
||||
try {
|
||||
var res = await authRepository.generateUserToken();
|
||||
await saveUserInSharedPref(res);
|
||||
userRes = ApiResponse.completed(res);
|
||||
userResponse = ApiResponse.completed(res);
|
||||
} catch (e) {
|
||||
userRes = ApiResponse.error(e.toString());
|
||||
userResponse = ApiResponse.error(e.toString());
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> sendOTP(String email) async {
|
||||
sendOTPRes = ApiResponse.loading();
|
||||
sendOTPResponse = ApiResponse.loading();
|
||||
notifyListeners();
|
||||
try {
|
||||
var res = await authRepository.sendOTP(email);
|
||||
sendOTPRes = ApiResponse.completed(res);
|
||||
sendOTPResponse = ApiResponse.completed(res);
|
||||
} catch (e) {
|
||||
sendOTPRes = ApiResponse.error(e.toString());
|
||||
sendOTPResponse = ApiResponse.error(e.toString());
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> verifyOTP(String email, String OTP) async {
|
||||
verifyOTPRes = ApiResponse.loading();
|
||||
verifyOTPResponse = ApiResponse.loading();
|
||||
notifyListeners();
|
||||
try {
|
||||
var res = await authRepository.verifyOTP(email, OTP);
|
||||
await updateUserInSharedPref(email);
|
||||
verifyOTPRes = ApiResponse.completed(res);
|
||||
verifyOTPResponse = ApiResponse.completed(res);
|
||||
} catch (e) {
|
||||
verifyOTPRes = ApiResponse.error(e.toString());
|
||||
verifyOTPResponse = ApiResponse.error(e.toString());
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
@ -97,9 +97,9 @@ class AuthNotifier extends ChangeNotifier {
|
||||
}
|
||||
|
||||
Future<void> updateUserInSharedPref(String email) async {
|
||||
var _userTokenModel = userRes.body as UserTokenModel;
|
||||
var _userTokenModel = userResponse.body as UserTokenModel;
|
||||
var _user = _userTokenModel.copyWith(email: email);
|
||||
userRes = ApiResponse.completed(_user);
|
||||
userResponse = ApiResponse.completed(_user);
|
||||
await saveUserInSharedPref(_user);
|
||||
}
|
||||
|
||||
|
38
lib/providers/auth/initialize_user_provider.dart
Normal file
38
lib/providers/auth/initialize_user_provider.dart
Normal file
@ -0,0 +1,38 @@
|
||||
import 'package:Medito/providers/providers.dart';
|
||||
import 'package:Medito/services/network/dio_client_provider.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
var retryCounter = 0;
|
||||
final maxRetryCount = 2;
|
||||
|
||||
final userInitializationProvider =
|
||||
FutureProvider<UserInitializationStatus>((ref) async {
|
||||
try {
|
||||
var auth = ref.read(authProvider);
|
||||
await auth.initializeUser();
|
||||
var response = auth.userResponse;
|
||||
if (response.body != null) {
|
||||
await ref.read(assignDioHeadersProvider.future);
|
||||
await ref.read(appOpenedEventProvider.future);
|
||||
|
||||
return UserInitializationStatus.successful;
|
||||
}
|
||||
|
||||
return UserInitializationStatus.error;
|
||||
} catch (e) {
|
||||
if (retryCounter < maxRetryCount) {
|
||||
await Future.delayed(Duration(seconds: 2), () {
|
||||
_incrementCounter();
|
||||
});
|
||||
|
||||
return UserInitializationStatus.retry;
|
||||
}
|
||||
|
||||
return UserInitializationStatus.error;
|
||||
}
|
||||
});
|
||||
|
||||
void _incrementCounter() => retryCounter += 1;
|
||||
|
||||
//ignore: prefer-match-file-name
|
||||
enum UserInitializationStatus { retry, error, successful }
|
@ -1,15 +1,18 @@
|
||||
import 'package:connectivity/connectivity.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
final connectivityStatusProvider = StateNotifierProvider((ref) {
|
||||
return ConnectivityStatusNotifier();
|
||||
});
|
||||
final connectivityStatusProvider =
|
||||
StateNotifierProvider<ConnectivityStatusNotifier, ConnectivityStatus>(
|
||||
(ref) {
|
||||
return ConnectivityStatusNotifier();
|
||||
},
|
||||
);
|
||||
|
||||
//ignore: prefer-match-file-name
|
||||
enum ConnectivityStatus { NotDetermined, isConnected, isDisonnected }
|
||||
enum ConnectivityStatus { NotDetermined, isConnected, isDisconnected }
|
||||
|
||||
class ConnectivityStatusNotifier extends StateNotifier<ConnectivityStatus> {
|
||||
ConnectivityStatusNotifier() : super(ConnectivityStatus.isConnected) {
|
||||
ConnectivityStatusNotifier() : super(ConnectivityStatus.NotDetermined) {
|
||||
checkConnectivity();
|
||||
Connectivity().onConnectivityChanged.listen((ConnectivityResult result) {
|
||||
_setConnectivityStatus(result);
|
||||
@ -22,13 +25,14 @@ class ConnectivityStatusNotifier extends StateNotifier<ConnectivityStatus> {
|
||||
}
|
||||
|
||||
void _setConnectivityStatus(ConnectivityResult result) {
|
||||
//ignore: missing_enum_constant_in_switch
|
||||
switch (result) {
|
||||
case ConnectivityResult.mobile:
|
||||
case ConnectivityResult.wifi:
|
||||
state = ConnectivityStatus.isConnected;
|
||||
break;
|
||||
case ConnectivityResult.none:
|
||||
state = ConnectivityStatus.isDisonnected;
|
||||
state = ConnectivityStatus.isDisconnected;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
part 'device_and_app_info_provider.g.dart';
|
||||
|
||||
@riverpod
|
||||
Future<DeviceAndAppInfoModel> deviceAndAppInfo(ref) {
|
||||
Future<DeviceAndAppInfoModel> deviceAndAppInfo(DeviceAndAppInfoRef ref) {
|
||||
final info = ref.read(deviceAndAppInfoRepositoryProvider);
|
||||
ref.keepAlive();
|
||||
|
||||
|
27
lib/providers/events/app_opened_event_provider.dart
Normal file
27
lib/providers/events/app_opened_event_provider.dart
Normal file
@ -0,0 +1,27 @@
|
||||
import 'package:Medito/constants/constants.dart';
|
||||
import 'package:Medito/models/models.dart';
|
||||
import 'package:Medito/providers/providers.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
var appOpenedEventProvider = FutureProvider<void>((ref) async {
|
||||
var deviceInfo = await ref.read(deviceAndAppInfoProvider.future);
|
||||
var data = _createAppOpenedModelData(deviceInfo);
|
||||
|
||||
await ref.read(eventsProvider(event: data.toJson()).future);
|
||||
});
|
||||
|
||||
EventsModel _createAppOpenedModelData(DeviceAndAppInfoModel val) {
|
||||
var appOpenedModel = AppOpenedModel(
|
||||
deviceOs: val.os,
|
||||
deviceLanguage: val.languageCode,
|
||||
deviceModel: val.model,
|
||||
buildNumber: val.buildNumber,
|
||||
appVersion: val.appVersion,
|
||||
);
|
||||
var event = EventsModel(
|
||||
name: EventTypes.appOpened,
|
||||
payload: appOpenedModel.toJson(),
|
||||
);
|
||||
|
||||
return event;
|
||||
}
|
@ -1,17 +1,10 @@
|
||||
import 'package:Medito/models/models.dart';
|
||||
import 'package:Medito/providers/providers.dart';
|
||||
import 'package:Medito/repositories/repositories.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'home_provider.g.dart';
|
||||
|
||||
@riverpod
|
||||
Future<HomeModel> home(HomeRef ref) {
|
||||
final homeRepository = ref.watch(homeRepositoryProvider);
|
||||
ref.keepAlive();
|
||||
|
||||
return homeRepository.fetchHomeData();
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<HomeHeaderModel> fetchHomeHeader(FetchHomeHeaderRef ref) {
|
||||
final homeRepository = ref.watch(homeRepositoryProvider);
|
||||
@ -45,3 +38,15 @@ Future<QuoteModel> fetchQuote(FetchQuoteRef ref) {
|
||||
|
||||
return homeRepository.fetchQuote();
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<void> refreshHomeAPIs(RefreshHomeAPIsRef ref) async {
|
||||
ref.invalidate(fetchHomeHeaderProvider);
|
||||
await ref.read(fetchHomeHeaderProvider.future);
|
||||
ref.invalidate(fetchShortcutsProvider);
|
||||
await ref.read(fetchShortcutsProvider.future);
|
||||
ref.invalidate(fetchQuoteProvider);
|
||||
await ref.read(fetchQuoteProvider.future);
|
||||
ref.invalidate(remoteStatsProvider);
|
||||
await ref.read(remoteStatsProvider.future);
|
||||
}
|
||||
|
@ -1,21 +1,23 @@
|
||||
export 'auth/auth_provider.dart';
|
||||
export 'auth/initialize_user_provider.dart';
|
||||
export 'me/me_provider.dart';
|
||||
export 'home/home_provider.dart';
|
||||
export 'player/audio_player_provider.dart';
|
||||
export 'background_sounds/background_sounds_provider.dart';
|
||||
export 'connectivity/connectivity_provider.dart';
|
||||
export 'device_and_app_info/device_and_app_info_provider.dart';
|
||||
export 'events/events_provider.dart';
|
||||
export 'home/home_provider.dart';
|
||||
export 'me/me_provider.dart';
|
||||
export 'meditation/download_track_provider.dart';
|
||||
export 'meditation/track_provider.dart';
|
||||
export 'notification/notification_provider.dart';
|
||||
export 'pack/pack_provider.dart';
|
||||
export 'page_view/bottom_padding_provider.dart';
|
||||
export 'player/audio_play_pause_provider.dart';
|
||||
export 'player/audio_player_provider.dart';
|
||||
export 'player/audio_position_provider.dart';
|
||||
export 'player/audio_speed_provider.dart';
|
||||
export 'player/download/audio_downloader_provider.dart';
|
||||
export 'player/player_provider.dart';
|
||||
export 'search/search_provider.dart';
|
||||
export 'shared_preference/shared_preference_provider.dart';
|
||||
export 'events/app_opened_event_provider.dart';
|
||||
export 'stats/stats_provider.dart';
|
||||
|
@ -54,7 +54,8 @@ void _handleAudioCompletion(Ref ref, BuildContext context) {
|
||||
}
|
||||
|
||||
void _handleUserNotSignedIn(Ref ref, BuildContext context) {
|
||||
var _user = ref.read(authProvider.notifier).userRes.body as UserTokenModel;
|
||||
var _user =
|
||||
ref.read(authProvider.notifier).userResponse.body as UserTokenModel;
|
||||
if (_user.email == null) {
|
||||
var params = JoinRouteParamsModel(screen: Screen.track);
|
||||
context.push(
|
||||
|
@ -9,8 +9,6 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
part 'home_repository.g.dart';
|
||||
|
||||
abstract class HomeRepository {
|
||||
Future<HomeModel> fetchHomeData();
|
||||
|
||||
Future<HomeHeaderModel> fetchHomeHeader();
|
||||
|
||||
Future<QuoteModel> fetchQuote();
|
||||
@ -34,13 +32,6 @@ class HomeRepositoryImpl extends HomeRepository {
|
||||
|
||||
HomeRepositoryImpl({required this.ref, required this.client});
|
||||
|
||||
@override
|
||||
Future<HomeModel> fetchHomeData() async {
|
||||
var res = await client.getRequest(HTTPConstants.HOME);
|
||||
|
||||
return HomeModel.fromJson(res);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<HomeHeaderModel> fetchHomeHeader() async {
|
||||
try {
|
||||
|
@ -2,6 +2,8 @@ import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:Medito/constants/constants.dart';
|
||||
import 'package:Medito/models/models.dart';
|
||||
import 'package:Medito/providers/providers.dart';
|
||||
import 'package:Medito/services/network/dio_api_service.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
@ -62,3 +64,27 @@ Future<void> _captureException(
|
||||
stackTrace: err.stackTrace,
|
||||
);
|
||||
}
|
||||
|
||||
var assignDioHeadersProvider = FutureProvider<void>((ref) async {
|
||||
var auth = ref.read(authProvider);
|
||||
var deviceInfo = await ref.read(deviceAndAppInfoProvider.future);
|
||||
var headers = ref.read(dioClientProvider).dio.options.headers;
|
||||
|
||||
var user = auth.userResponse.body as UserTokenModel;
|
||||
|
||||
headers[HttpHeaders.authorizationHeader] = 'Bearer ${user.token}';
|
||||
var customHeaders = _createCustomHeaders(deviceInfo);
|
||||
for (var key in customHeaders.keys) {
|
||||
headers[key] = customHeaders[key];
|
||||
}
|
||||
});
|
||||
|
||||
Map<String, dynamic> _createCustomHeaders(DeviceAndAppInfoModel model) {
|
||||
return {
|
||||
'Device-Os': '${model.os}',
|
||||
'Device-Language': '${model.languageCode}',
|
||||
'Device-Model': '${model.model}',
|
||||
'App-Version': '${model.appVersion}',
|
||||
'Device-Time': '${DateTime.now()}',
|
||||
};
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ class _JoinEmailViewState extends ConsumerState<JoinEmailView> {
|
||||
var email = _emailController.text;
|
||||
await auth.sendOTP(email);
|
||||
auth.setCounter();
|
||||
var status = auth.sendOTPRes.status;
|
||||
var status = auth.sendOTPResponse.status;
|
||||
if (status == Status.COMPLETED) {
|
||||
var params =
|
||||
JoinRouteParamsModel(screen: widget.fromScreen, email: email);
|
||||
@ -54,7 +54,7 @@ class _JoinEmailViewState extends ConsumerState<JoinEmailView> {
|
||||
extra: params,
|
||||
);
|
||||
} else if (status == Status.ERROR) {
|
||||
showSnackBar(context, auth.sendOTPRes.message.toString());
|
||||
showSnackBar(context, auth.sendOTPResponse.message.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -63,7 +63,7 @@ class _JoinEmailViewState extends ConsumerState<JoinEmailView> {
|
||||
Widget build(BuildContext context) {
|
||||
var textTheme = Theme.of(context).textTheme;
|
||||
auth = ref.watch(authProvider);
|
||||
var isLoading = auth.sendOTPRes == ApiResponse.loading();
|
||||
var isLoading = auth.sendOTPResponse == ApiResponse.loading();
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: ColorConstants.ebony,
|
||||
|
@ -38,7 +38,7 @@ class _JoinVerifyOTPViewState extends ConsumerState<JoinVerifyOTPView> {
|
||||
void _handleVerify() async {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
await auth.verifyOTP(widget.email, _otpTextEditingController.text);
|
||||
var status = auth.verifyOTPRes.status;
|
||||
var status = auth.verifyOTPResponse.status;
|
||||
if (status == Status.COMPLETED) {
|
||||
await removeFirebaseToken();
|
||||
await requestGenerateFirebaseToken();
|
||||
@ -54,7 +54,7 @@ class _JoinVerifyOTPViewState extends ConsumerState<JoinVerifyOTPView> {
|
||||
extra: params,
|
||||
));
|
||||
} else if (status == Status.ERROR) {
|
||||
showSnackBar(context, auth.verifyOTPRes.message.toString());
|
||||
showSnackBar(context, auth.verifyOTPResponse.message.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -63,7 +63,7 @@ class _JoinVerifyOTPViewState extends ConsumerState<JoinVerifyOTPView> {
|
||||
Widget build(BuildContext context) {
|
||||
auth = ref.watch(authProvider);
|
||||
var textTheme = Theme.of(context).textTheme;
|
||||
var isLoading = auth.verifyOTPRes == ApiResponse.loading();
|
||||
var isLoading = auth.verifyOTPResponse == ApiResponse.loading();
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: ColorConstants.ebony,
|
||||
@ -194,16 +194,16 @@ class _ResendCodeWidget extends ConsumerWidget {
|
||||
void _handleResendOTP() async {
|
||||
await auth.sendOTP(email);
|
||||
auth.setCounter();
|
||||
var status = auth.sendOTPRes.status;
|
||||
var status = auth.sendOTPResponse.status;
|
||||
if (status == Status.COMPLETED) {
|
||||
showSnackBar(context, auth.sendOTPRes.body.toString());
|
||||
showSnackBar(context, auth.sendOTPResponse.body.toString());
|
||||
} else if (status == Status.ERROR) {
|
||||
showSnackBar(context, auth.sendOTPRes.message.toString());
|
||||
showSnackBar(context, auth.sendOTPResponse.message.toString());
|
||||
auth.setCounter();
|
||||
}
|
||||
}
|
||||
|
||||
if (auth.sendOTPRes.status == Status.LOADING) {
|
||||
if (auth.sendOTPResponse.status == Status.LOADING) {
|
||||
return SizedBox(
|
||||
height: 16,
|
||||
width: 16,
|
||||
|
@ -61,12 +61,10 @@ class _BackgroundSoundViewState extends ConsumerState<BackgroundSoundView> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var connectivityStatus =
|
||||
ref.watch(connectivityStatusProvider) as ConnectivityStatus;
|
||||
var connectivityStatus = ref.watch(connectivityStatusProvider);
|
||||
|
||||
ref.listen(connectivityStatusProvider, (prev, next) {
|
||||
var state = next as ConnectivityStatus;
|
||||
if (state == ConnectivityStatus.isDisonnected) {
|
||||
if (next == ConnectivityStatus.isDisconnected) {
|
||||
showSnackBar(context, StringConstants.connectivityError);
|
||||
}
|
||||
});
|
||||
@ -74,7 +72,7 @@ class _BackgroundSoundViewState extends ConsumerState<BackgroundSoundView> {
|
||||
ref.watch(fetchLocallySavedBackgroundSoundsProvider);
|
||||
var backgroundSounds = ref.watch(backgroundSoundsProvider);
|
||||
|
||||
if (connectivityStatus == ConnectivityStatus.isDisonnected) {
|
||||
if (connectivityStatus == ConnectivityStatus.isDisconnected) {
|
||||
return Scaffold(
|
||||
body: localBackgroundSounds.when(
|
||||
skipLoadingOnRefresh: true,
|
||||
@ -128,7 +126,7 @@ class _BackgroundSoundViewState extends ConsumerState<BackgroundSoundView> {
|
||||
) {
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
if (status == ConnectivityStatus.isDisonnected) {
|
||||
if (status == ConnectivityStatus.isDisconnected) {
|
||||
return;
|
||||
} else {
|
||||
ref.invalidate(backgroundSoundsProvider);
|
||||
|
@ -36,6 +36,7 @@ class _DownloadsViewState extends ConsumerState<DownloadsView>
|
||||
if (context.canPop()) {
|
||||
context.pop();
|
||||
} else {
|
||||
ref.read(refreshHomeAPIsProvider.future);
|
||||
context.go(RouteConstants.homePath);
|
||||
}
|
||||
},
|
||||
|
@ -24,12 +24,20 @@ class _HomeViewState extends ConsumerState<HomeView>
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
var connectivityStatus =
|
||||
ref.watch(connectivityStatusProvider) as ConnectivityStatus;
|
||||
if (connectivityStatus == ConnectivityStatus.isDisonnected) {
|
||||
var homeAPIsResponse = ref.watch(refreshHomeAPIsProvider);
|
||||
var connectivityStatus = ref.watch(connectivityStatusProvider);
|
||||
if (connectivityStatus == ConnectivityStatus.isDisconnected) {
|
||||
return ConnectivityErrorWidget();
|
||||
}
|
||||
|
||||
if (homeAPIsResponse.hasError) {
|
||||
return MeditoErrorWidget(
|
||||
message: homeAPIsResponse.error.toString(),
|
||||
onTap: () => _onRefresh(),
|
||||
isLoading: homeAPIsResponse.isLoading,
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
floatingActionButton: _buildFloatingButton(context),
|
||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
|
||||
@ -74,14 +82,8 @@ class _HomeViewState extends ConsumerState<HomeView>
|
||||
}
|
||||
|
||||
Future<void> _onRefresh() async {
|
||||
ref.invalidate(fetchHomeHeaderProvider);
|
||||
await ref.read(fetchHomeHeaderProvider.future);
|
||||
ref.invalidate(fetchShortcutsProvider);
|
||||
await ref.read(fetchShortcutsProvider.future);
|
||||
ref.invalidate(fetchQuoteProvider);
|
||||
await ref.read(fetchQuoteProvider.future);
|
||||
ref.invalidate(remoteStatsProvider);
|
||||
await ref.read(remoteStatsProvider.future);
|
||||
ref.invalidate(refreshHomeAPIsProvider);
|
||||
await ref.read(refreshHomeAPIsProvider.future);
|
||||
}
|
||||
|
||||
FloatingActionButton _buildFloatingButton(BuildContext context) {
|
||||
|
123
lib/views/home/widgets/shortcuts/shortcuts_items_widget.dart
Normal file
123
lib/views/home/widgets/shortcuts/shortcuts_items_widget.dart
Normal file
@ -0,0 +1,123 @@
|
||||
import 'package:Medito/constants/constants.dart';
|
||||
import 'package:Medito/models/models.dart';
|
||||
import 'package:Medito/providers/providers.dart';
|
||||
import 'package:Medito/routes/routes.dart';
|
||||
import 'package:Medito/utils/utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:reorderables/reorderables.dart';
|
||||
|
||||
class ShortcutsItemsWidget extends ConsumerStatefulWidget {
|
||||
const ShortcutsItemsWidget({super.key, required this.data});
|
||||
final ShortcutsModel data;
|
||||
@override
|
||||
ConsumerState<ShortcutsItemsWidget> createState() =>
|
||||
_ShortcutsItemsWidgetState();
|
||||
}
|
||||
|
||||
class _ShortcutsItemsWidgetState extends ConsumerState<ShortcutsItemsWidget> {
|
||||
late ShortcutsModel data;
|
||||
late List<Widget> shortcutsWidgetList = [];
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
data = widget.data;
|
||||
shortcutsWidgetList = _getShortcutsItemWidgetList();
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
void _handleChipPress(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
ShortcutsItemsModel element,
|
||||
) async {
|
||||
_handleTrackEvent(ref, element.id, element.title);
|
||||
await handleNavigation(
|
||||
context: context,
|
||||
element.type,
|
||||
[element.path.toString().getIdFromPath(), element.path],
|
||||
ref: ref,
|
||||
);
|
||||
}
|
||||
|
||||
void _handleTrackEvent(WidgetRef ref, String chipId, String chipTitle) {
|
||||
var chipViewedModel = ChipTappedModel(chipId: chipId, chipTitle: chipTitle);
|
||||
var event = EventsModel(
|
||||
name: EventTypes.chipTapped,
|
||||
payload: chipViewedModel.toJson(),
|
||||
);
|
||||
ref.read(eventsProvider(event: event.toJson()));
|
||||
}
|
||||
|
||||
void _onReorder(int oldIndex, int newIndex) {
|
||||
setState(() {
|
||||
_handleShortcutWidgetPlacement(newIndex, oldIndex);
|
||||
_handleShortcutItemPlacementInPreference(oldIndex, newIndex);
|
||||
});
|
||||
}
|
||||
|
||||
void _handleShortcutItemPlacementInPreference(int oldIndex, int newIndex) {
|
||||
var _data = [...data.shortcuts];
|
||||
final element = _data.removeAt(oldIndex);
|
||||
_data.insert(newIndex, element);
|
||||
data = data.copyWith(shortcuts: _data);
|
||||
var ids = _data.map((e) => e.id).toList();
|
||||
ref.read(updateShortcutsIdsInPreferenceProvider(ids: ids));
|
||||
}
|
||||
|
||||
void _handleShortcutWidgetPlacement(int newIndex, int oldIndex) {
|
||||
shortcutsWidgetList.insert(
|
||||
newIndex,
|
||||
shortcutsWidgetList.removeAt(oldIndex),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ReorderableWrap(
|
||||
spacing: 8.0,
|
||||
runSpacing: 8.0,
|
||||
padding: EdgeInsets.zero,
|
||||
maxMainAxisCount: 2,
|
||||
minMainAxisCount: 2,
|
||||
onReorder: _onReorder,
|
||||
children: shortcutsWidgetList,
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _getShortcutsItemWidgetList() {
|
||||
var size = MediaQuery.of(context).size;
|
||||
final containerHeight = 48.0;
|
||||
final containerWidth = (size.width / 2) - (padding20 + 2);
|
||||
|
||||
return data.shortcuts
|
||||
.map((e) => IntrinsicWidth(
|
||||
child: InkWell(
|
||||
key: ValueKey(e.id),
|
||||
onTap: () => _handleChipPress(context, ref, e),
|
||||
child: Container(
|
||||
height: containerHeight,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
color: ColorConstants.onyx,
|
||||
),
|
||||
padding: EdgeInsets.all(12),
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: containerWidth,
|
||||
minWidth: containerWidth,
|
||||
),
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
'${e.title}',
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
))
|
||||
.toList();
|
||||
}
|
||||
}
|
@ -1,12 +1,9 @@
|
||||
import 'package:Medito/constants/constants.dart';
|
||||
import 'package:Medito/models/models.dart';
|
||||
import 'package:Medito/providers/providers.dart';
|
||||
import 'package:Medito/routes/routes.dart';
|
||||
import 'package:Medito/utils/utils.dart';
|
||||
import 'package:Medito/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:reorderables/reorderables.dart';
|
||||
|
||||
import 'shortcuts_items_widget.dart';
|
||||
|
||||
class ShortcutsWidget extends ConsumerStatefulWidget {
|
||||
const ShortcutsWidget({super.key});
|
||||
@ -16,73 +13,21 @@ class ShortcutsWidget extends ConsumerStatefulWidget {
|
||||
}
|
||||
|
||||
class _ShortcutsWidgetState extends ConsumerState<ShortcutsWidget> {
|
||||
late ShortcutsModel data;
|
||||
late List<Widget> shortcutsWidgetList = [];
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void _handleChipPress(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
ShortcutsItemsModel element,
|
||||
) async {
|
||||
_handleTrackEvent(ref, element.id, element.title);
|
||||
await handleNavigation(
|
||||
context: context,
|
||||
element.type,
|
||||
[element.path.toString().getIdFromPath(), element.path],
|
||||
ref: ref,
|
||||
);
|
||||
}
|
||||
|
||||
void _handleTrackEvent(WidgetRef ref, String chipId, String chipTitle) {
|
||||
var chipViewedModel = ChipTappedModel(chipId: chipId, chipTitle: chipTitle);
|
||||
var event = EventsModel(
|
||||
name: EventTypes.chipTapped,
|
||||
payload: chipViewedModel.toJson(),
|
||||
);
|
||||
ref.read(eventsProvider(event: event.toJson()));
|
||||
}
|
||||
|
||||
void _onReorder(int oldIndex, int newIndex) {
|
||||
setState(() {
|
||||
_handleShortcutWidgetPlacement(newIndex, oldIndex);
|
||||
_handleShortcutItemPlacementInPreference(oldIndex, newIndex);
|
||||
});
|
||||
}
|
||||
|
||||
void _handleShortcutItemPlacementInPreference(int oldIndex, int newIndex) {
|
||||
var _data = [...data.shortcuts];
|
||||
final element = _data.removeAt(oldIndex);
|
||||
_data.insert(newIndex, element);
|
||||
data = data.copyWith(shortcuts: _data);
|
||||
var ids = _data.map((e) => e.id).toList();
|
||||
ref.read(updateShortcutsIdsInPreferenceProvider(ids: ids));
|
||||
}
|
||||
|
||||
void _handleShortcutWidgetPlacement(int newIndex, int oldIndex) {
|
||||
shortcutsWidgetList.insert(
|
||||
newIndex,
|
||||
shortcutsWidgetList.removeAt(oldIndex),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var response = ref.watch(fetchShortcutsProvider);
|
||||
ref.listen(fetchShortcutsProvider, (previous, next) {
|
||||
if (next.hasValue) {
|
||||
data = next.value!;
|
||||
shortcutsWidgetList = _getShortcutsItemWidgetList();
|
||||
}
|
||||
});
|
||||
|
||||
return response.when(
|
||||
skipLoadingOnRefresh: false,
|
||||
skipLoadingOnReload: true,
|
||||
data: (_) => _buildShortcuts(),
|
||||
data: (data) => ShortcutsItemsWidget(
|
||||
data: data,
|
||||
),
|
||||
error: (err, stack) => MeditoErrorWidget(
|
||||
message: err.toString(),
|
||||
onTap: () => ref.refresh(fetchShortcutsProvider),
|
||||
@ -92,52 +37,18 @@ class _ShortcutsWidgetState extends ConsumerState<ShortcutsWidget> {
|
||||
loading: () => const ShortcutsShimmerWidget(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ReorderableWrap _buildShortcuts() {
|
||||
return ReorderableWrap(
|
||||
spacing: 8.0,
|
||||
runSpacing: 8.0,
|
||||
padding: EdgeInsets.zero,
|
||||
maxMainAxisCount: 2,
|
||||
minMainAxisCount: 2,
|
||||
onReorder: _onReorder,
|
||||
children: shortcutsWidgetList,
|
||||
);
|
||||
}
|
||||
class MyWidget extends StatefulWidget {
|
||||
const MyWidget({super.key});
|
||||
|
||||
List<Widget> _getShortcutsItemWidgetList() {
|
||||
var size = MediaQuery.of(context).size;
|
||||
final containerHeight = 48.0;
|
||||
final containerWidth = (size.width / 2) - (padding20 + 2);
|
||||
@override
|
||||
State<MyWidget> createState() => _MyWidgetState();
|
||||
}
|
||||
|
||||
return data.shortcuts
|
||||
.map((e) => IntrinsicWidth(
|
||||
child: InkWell(
|
||||
key: ValueKey(e.id),
|
||||
onTap: () => _handleChipPress(context, ref, e),
|
||||
child: Container(
|
||||
height: containerHeight,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
color: ColorConstants.onyx,
|
||||
),
|
||||
padding: EdgeInsets.all(12),
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: containerWidth,
|
||||
minWidth: containerWidth,
|
||||
),
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
'${e.title}',
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
))
|
||||
.toList();
|
||||
class _MyWidgetState extends State<MyWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Placeholder();
|
||||
}
|
||||
}
|
||||
|
@ -18,13 +18,11 @@ class TilesWidget extends ConsumerWidget {
|
||||
return stats.when(
|
||||
skipLoadingOnRefresh: false,
|
||||
data: (data) => _buildTiles(ref, data.tiles),
|
||||
error: (err, stack) => Expanded(
|
||||
child: MeditoErrorWidget(
|
||||
message: err.toString(),
|
||||
onTap: () => ref.refresh(remoteStatsProvider),
|
||||
isLoading: stats.isLoading,
|
||||
isScaffold: false,
|
||||
),
|
||||
error: (err, stack) => MeditoErrorWidget(
|
||||
message: err.toString(),
|
||||
onTap: () => ref.refresh(remoteStatsProvider),
|
||||
isLoading: stats.isLoading,
|
||||
isScaffold: false,
|
||||
),
|
||||
loading: () => TilesShimmerWidget(),
|
||||
);
|
||||
|
@ -56,7 +56,7 @@ class _RootPageViewState extends ConsumerState<RootPageView> {
|
||||
children: [
|
||||
_renderChild(
|
||||
context,
|
||||
connectivityStatus as ConnectivityStatus,
|
||||
connectivityStatus,
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -72,7 +72,7 @@ class _RootPageViewState extends ConsumerState<RootPageView> {
|
||||
return widget.firstChild;
|
||||
} else if ((location != RouteConstants.downloadsPath &&
|
||||
location != RouteConstants.playerPath) &&
|
||||
status == ConnectivityStatus.isDisonnected) {
|
||||
status == ConnectivityStatus.isDisconnected) {
|
||||
return ConnectivityErrorWidget();
|
||||
} else {
|
||||
return widget.firstChild;
|
||||
|
@ -1,8 +1,10 @@
|
||||
import 'package:Medito/constants/constants.dart';
|
||||
import 'package:Medito/providers/auth/app_initialization_provider.dart';
|
||||
import 'package:Medito/providers/providers.dart';
|
||||
import 'package:Medito/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class SplashView extends ConsumerStatefulWidget {
|
||||
const SplashView({super.key});
|
||||
@ -14,14 +16,32 @@ class SplashView extends ConsumerStatefulWidget {
|
||||
class _SplashViewState extends ConsumerState<SplashView> {
|
||||
@override
|
||||
void initState() {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
ref.read(appInitializationProvider(context));
|
||||
});
|
||||
initializeUser();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void initializeUser() async {
|
||||
var response = await ref.read(userInitializationProvider.future);
|
||||
if (response == UserInitializationStatus.successful) {
|
||||
context.go(RouteConstants.homePath);
|
||||
} else if (response == UserInitializationStatus.error) {
|
||||
showSnackBar(context, StringConstants.timeout);
|
||||
context.go(RouteConstants.downloadsPath);
|
||||
} else if (response == UserInitializationStatus.retry) {
|
||||
ref.invalidate(userInitializationProvider);
|
||||
initializeUser();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
ref.listen(connectivityStatusProvider, (previous, next) {
|
||||
var isDisconnected = next == ConnectivityStatus.isDisconnected;
|
||||
if (isDisconnected) {
|
||||
showSnackBar(context, StringConstants.connectivityError);
|
||||
}
|
||||
});
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: ColorConstants.ebony,
|
||||
body: Center(
|
||||
|
@ -106,7 +106,7 @@ class _TrackViewState extends ConsumerState<TrackView>
|
||||
|
||||
ref.listen(trackOpenedFirstTimeProvider, (prev, next) {
|
||||
var _user =
|
||||
ref.read(authProvider.notifier).userRes.body as UserTokenModel;
|
||||
ref.read(authProvider.notifier).userResponse.body as UserTokenModel;
|
||||
if (_user.email == null && next.value != null && next.value!) {
|
||||
var params = JoinRouteParamsModel(screen: Screen.track);
|
||||
context.push(
|
||||
|
@ -28,7 +28,8 @@ class NetworkImageWidget extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
var userTokenModel = ref.watch(authProvider).userRes.body as UserTokenModel;
|
||||
var userTokenModel =
|
||||
ref.watch(authProvider).userResponse.body as UserTokenModel;
|
||||
if (url.contains('.svg')) {
|
||||
return SvgPicture.network(
|
||||
url,
|
||||
|
Loading…
Reference in New Issue
Block a user