Skip to content

Commit

Permalink
Feature: My Account access block (#6)
Browse files Browse the repository at this point in the history
The purpose of this branch is to block access to MyAccountPage if the connected network does not contain information about default token or bech32 address prefix.

List of changes:
- created global_nav_controller.dart to enable navigation without passing an instance of BuildContext
- added "leaveProtectedPage()" method from global_nav_controller.dart to "signOut()" method auth_cubit.dart to link those 2 actions
- added methods in network_module_bloc.dart with reference to auth_cubit.dart to automatically log out and leave MyAccountPage if user changes to unhealthy network that does not contain TokenDefaultDenomModel
- modified sign_in_drawer_page.dart to block connecting the wallet if network does not contain TokenDefaultDenomModel
and created sign_in_drawer_warning_section.dart to display "Change network" button and refresh network timer in this case
- added NetworkModuleState.refreshing() to provide information about network refreshing to SignInDrawerPage
- added "lastRefreshDateTime" to all Network Models (except NetworkEmptyModel) to save info about time of last refresh of each network. This enabled creating a timer in SignInDrawerPage and optimizing network refreshing in network_module_bloc.dart and network_custom_section.cubit.dart.
- renamed "timed_refresh_button_cubit.dart" -> "time_counter_cubit.dart" since it was used in sign_in_drawer_warning_section.dart, so it became a generic cubit instead of specific widget centered
- removed redundant method from network_module_service.dart and changed nullable TokenDefaultDenomModel to bool variable "valuesFromNetworkExistBool" inside this model
- unrelated with the branch's specific domain: added conditions in network_module_bloc.dart and network_custom_section_cubit.dart to prevent still refreshing networks from being refreshed again
- unrelated with the branch's specific domain: added TODO about removing IdentityRegistrarCubit initialization in main.dart
  • Loading branch information
nemoforte authored Mar 19, 2024
1 parent a46d915 commit d89552a
Show file tree
Hide file tree
Showing 52 changed files with 854 additions and 275 deletions.
4 changes: 4 additions & 0 deletions lib/blocs/generic/auth/auth_cubit.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import 'dart:async';

import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:miro/blocs/generic/identity_registrar/identity_registrar_cubit.dart';
import 'package:miro/config/locator.dart';
import 'package:miro/shared/controllers/global_nav/global_nav_controller.dart';
import 'package:miro/shared/models/wallet/wallet.dart';

class AuthCubit extends Cubit<Wallet?> {
Expand All @@ -16,6 +19,7 @@ class AuthCubit extends Cubit<Wallet?> {
Future<void> signOut() async {
emit(null);
await identityRegistrarCubit.setWalletAddress(null);
globalLocator<GlobalNavController>().leaveProtectedPage();
}

bool get isSignedIn => state != null;
Expand Down
53 changes: 30 additions & 23 deletions lib/blocs/generic/network_module/network_module_bloc.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:async';

import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:miro/blocs/generic/auth/auth_cubit.dart';
import 'package:miro/blocs/generic/network_module/a_network_module_event.dart';
import 'package:miro/blocs/generic/network_module/events/network_module_auto_connect_event.dart';
import 'package:miro/blocs/generic/network_module/events/network_module_connect_event.dart';
Expand All @@ -14,6 +15,7 @@ import 'package:miro/config/app_config.dart';
import 'package:miro/config/locator.dart';
import 'package:miro/infra/services/network_module_service.dart';
import 'package:miro/shared/controllers/browser/rpc_browser_url_controller.dart';
import 'package:miro/shared/models/network/data/connection_status_type.dart';
import 'package:miro/shared/models/network/status/a_network_status_model.dart';
import 'package:miro/shared/models/network/status/network_empty_model.dart';
import 'package:miro/shared/models/network/status/network_unknown_model.dart';
Expand All @@ -31,8 +33,7 @@ class NetworkModuleBloc extends Bloc<ANetworkModuleEvent, NetworkModuleState> {
final RpcBrowserUrlController _rpcBrowserUrlController = RpcBrowserUrlController();

late Timer _timer;
bool _refreshingBool = false;
TokenDefaultDenomModel? tokenDefaultDenomModel;
TokenDefaultDenomModel tokenDefaultDenomModel = TokenDefaultDenomModel.empty();

NetworkModuleBloc() : super(NetworkModuleState.disconnected()) {
on<NetworkModuleInitEvent>(_mapInitEventToState);
Expand Down Expand Up @@ -63,14 +64,10 @@ class NetworkModuleBloc extends Bloc<ANetworkModuleEvent, NetworkModuleState> {
}

Future<void> _mapRefreshEventToState(NetworkModuleRefreshEvent networkModuleRefreshEvent, Emitter<NetworkModuleState> emit) async {
if (_refreshingBool) {
return;
}
_refreshingBool = true;

if (state.networkStatusModel is NetworkEmptyModel) {
if (state.networkStatusModel is NetworkEmptyModel || state.isRefreshing) {
_updateNetworkStatusModelList();
} else {
emit(NetworkModuleState.refreshing(state.networkStatusModel));
NetworkUnknownModel networkUnknownModel = NetworkUnknownModel.fromNetworkStatusModel(state.networkStatusModel);
ANetworkStatusModel networkStatusModel = await _networkModuleService.getNetworkStatusModel(networkUnknownModel);

Expand All @@ -82,12 +79,11 @@ class NetworkModuleBloc extends Bloc<ANetworkModuleEvent, NetworkModuleState> {
await _networkCustomSectionCubit.updateNetworks(networkStatusModel);
emit(NetworkModuleState.connected(networkStatusModel));
_refreshTokenDefaultDenomModel(networkStatusModel);
await _checkIfSignOutNeeded();
}
}

await _networkCustomSectionCubit.refreshNetworks();

_refreshingBool = false;
}

Future<void> _mapAutoConnectEventToState(NetworkModuleAutoConnectEvent networkModuleAutoConnectEvent, Emitter<NetworkModuleState> emit) async {
Expand Down Expand Up @@ -118,6 +114,7 @@ class NetworkModuleBloc extends Bloc<ANetworkModuleEvent, NetworkModuleState> {
await _networkCustomSectionCubit.updateNetworks(networkOnlineModel);
emit(NetworkModuleState.connected(networkOnlineModel));
_switchTokenDefaultDenomModel(networkOnlineModel);
await _checkIfSignOutNeeded();
}

Future<void> _mapDisconnectEventToState(
Expand All @@ -129,11 +126,33 @@ class NetworkModuleBloc extends Bloc<ANetworkModuleEvent, NetworkModuleState> {
emit(NetworkModuleState.disconnected());
}

Future<void> _checkIfSignOutNeeded() async {
AuthCubit authCubit = globalLocator<AuthCubit>();
bool signedInBool = authCubit.state != null;
if (tokenDefaultDenomModel.valuesFromNetworkExistBool == false && signedInBool) {
await authCubit.signOut();
}
}

void _refreshTokenDefaultDenomModel(ANetworkStatusModel networkStatusModel) {
bool networkOnlineBool = networkStatusModel is ANetworkOnlineModel;
bool emptyTokenDefaultDenomModelBool = tokenDefaultDenomModel.valuesFromNetworkExistBool == false;
if (networkOnlineBool && emptyTokenDefaultDenomModelBool) {
tokenDefaultDenomModel = tokenDefaultDenomModel.copyWith(networkStatusModel.tokenDefaultDenomModel);
}
}

void _switchTokenDefaultDenomModel(ANetworkStatusModel networkStatusModel) {
if (networkStatusModel is ANetworkOnlineModel) {
tokenDefaultDenomModel = tokenDefaultDenomModel.copyWith(networkStatusModel.tokenDefaultDenomModel);
}
}

void _updateNetworkStatusModelList({NetworkUnknownModel? ignoreNetworkUnknownModel}) {
List<ANetworkStatusModel> networkStatusModelList = _networkListCubit.networkStatusModelList;
for (ANetworkStatusModel networkStatusModel in networkStatusModelList) {
bool networkNotIgnoredBool = networkStatusModel.uri != ignoreNetworkUnknownModel?.uri;
if (networkNotIgnoredBool) {
if (networkNotIgnoredBool && networkStatusModel.connectionStatusType != ConnectionStatusType.refreshing) {
_updateNetworkStatusModel(networkUnknownModel: NetworkUnknownModel.fromNetworkStatusModel(networkStatusModel));
}
}
Expand All @@ -143,16 +162,4 @@ class NetworkModuleBloc extends Bloc<ANetworkModuleEvent, NetworkModuleState> {
ANetworkStatusModel networkStatusModel = await _networkModuleService.getNetworkStatusModel(networkUnknownModel);
_networkListCubit.setNetworkStatusModel(networkStatusModel: networkStatusModel);
}

void _refreshTokenDefaultDenomModel(ANetworkStatusModel networkStatusModel) {
if (networkStatusModel is ANetworkOnlineModel) {
tokenDefaultDenomModel ??= networkStatusModel.tokenDefaultDenomModel;
}
}

void _switchTokenDefaultDenomModel(ANetworkStatusModel networkStatusModel) {
if (networkStatusModel is ANetworkOnlineModel) {
tokenDefaultDenomModel = networkStatusModel.tokenDefaultDenomModel;
}
}
}
8 changes: 7 additions & 1 deletion lib/blocs/generic/network_module/network_module_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,18 @@ class NetworkModuleState extends Equatable {

NetworkModuleState.disconnected() : networkStatusModel = NetworkEmptyModel(connectionStatusType: ConnectionStatusType.disconnected);

NetworkModuleState.refreshing(ANetworkStatusModel networkStatusModel)
: networkStatusModel = networkStatusModel.copyWith(connectionStatusType: ConnectionStatusType.refreshing, lastRefreshDateTime: DateTime.now());

bool get isConnecting => networkStatusModel.connectionStatusType == ConnectionStatusType.connecting;

bool get isConnected => networkStatusModel.connectionStatusType == ConnectionStatusType.connected;
bool get isConnected =>
networkStatusModel.connectionStatusType == ConnectionStatusType.connected || networkStatusModel.connectionStatusType == ConnectionStatusType.refreshing;

bool get isDisconnected => networkStatusModel.connectionStatusType == ConnectionStatusType.disconnected;

bool get isRefreshing => networkStatusModel.connectionStatusType == ConnectionStatusType.refreshing;

Uri get networkUri => networkStatusModel.uri;

@override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import 'dart:async';

import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:miro/blocs/generic/network_module/events/network_module_refresh_event.dart';
import 'package:miro/blocs/generic/network_module/network_module_bloc.dart';
import 'package:miro/blocs/generic/network_module/network_module_state.dart';
import 'package:miro/blocs/pages/drawer/sign_in_drawer_page/sign_in_drawer_page_state.dart';
import 'package:miro/config/app_config.dart';
import 'package:miro/config/locator.dart';
import 'package:miro/shared/models/tokens/token_default_denom_model.dart';

class SignInDrawerPageCubit extends Cubit<SignInDrawerPageState> {
final int refreshIntervalSeconds = globalLocator<AppConfig>().defaultRefreshIntervalSeconds;
final NetworkModuleBloc _networkModuleBloc = globalLocator<NetworkModuleBloc>();
late final StreamSubscription<NetworkModuleState> _networkModuleStateSubscription;

SignInDrawerPageCubit() : super(const SignInDrawerPageState(disabledBool: true)) {
_refreshDrawer();
_networkModuleStateSubscription = _networkModuleBloc.stream.listen(_refreshDrawer);
}

void refreshNetwork() {
_networkModuleBloc.add(NetworkModuleRefreshEvent());
emit(state.copyWith(refreshUnlockingDateTime: DateTime.now().add(Duration(seconds: refreshIntervalSeconds))));
}

@override
Future<void> close() {
_networkModuleStateSubscription.cancel();
return super.close();
}

void _refreshDrawer([NetworkModuleState? networkModuleState]) {
TokenDefaultDenomModel tokenDefaultDenomModel = _networkModuleBloc.tokenDefaultDenomModel;
bool disabledBool = tokenDefaultDenomModel.valuesFromNetworkExistBool == false;
DateTime expirationDateTime = _calculateExpirationDateTime();
if (_networkModuleBloc.state.isRefreshing) {
emit(state.copyWith(disabledBool: disabledBool, refreshingBool: true, refreshUnlockingDateTime: expirationDateTime));
} else {
emit(state.copyWith(disabledBool: disabledBool, refreshingBool: false, refreshUnlockingDateTime: expirationDateTime));
}
}

DateTime _calculateExpirationDateTime() {
DateTime calculationStartDateTime = DateTime.now();

// Last refresh of currently connected network from now, which refreshes only during NetworkModuleRefreshEvent
// Can take bigger values than 60s because of long response time
Duration lastRefreshFromNow = calculationStartDateTime.difference(_networkModuleBloc.state.networkStatusModel.lastRefreshDateTime!);

// Last occurrence of NetworkModuleRefreshEvent from now
// If lastRefreshFromNow is more than 60s (e.g. 78s), lastAutoRefreshEventFromNow will equal the remainder of dividing it by 60s (e.g. 18s)
// Milliseconds were used to avoid inaccurate rounding by the Duration class (e.g. 59.9s -> 59.0s)
Duration lastAutoRefreshEventFromNow =
lastRefreshFromNow.inSeconds > 60 ? Duration(milliseconds: lastRefreshFromNow.inMilliseconds % 60000) : lastRefreshFromNow;

Duration timeUntilNextRefresh = Duration(seconds: refreshIntervalSeconds) - lastAutoRefreshEventFromNow;
return calculationStartDateTime.add(timeUntilNextRefresh);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import 'package:equatable/equatable.dart';

class SignInDrawerPageState extends Equatable {
final bool disabledBool;
final bool refreshingBool;
final DateTime? refreshUnlockingDateTime;

const SignInDrawerPageState({
required this.disabledBool,
this.refreshingBool = false,
this.refreshUnlockingDateTime,
});

SignInDrawerPageState copyWith({
bool? disabledBool,
bool? refreshingBool,
DateTime? refreshUnlockingDateTime,
}) {
return SignInDrawerPageState(
disabledBool: disabledBool ?? this.disabledBool,
refreshingBool: refreshingBool ?? this.refreshingBool,
refreshUnlockingDateTime: refreshUnlockingDateTime ?? this.refreshUnlockingDateTime,
);
}

@override
List<Object?> get props => <Object?>[disabledBool, refreshingBool, refreshUnlockingDateTime];
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import 'dart:async';

import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:miro/blocs/widgets/buttons/timed_refresh_button/timed_refresh_button_state.dart';
import 'package:miro/blocs/widgets/buttons/time_counter/time_counter_state.dart';

class TimedRefreshButtonCubit extends Cubit<TimedRefreshButtonState> {
class TimeCounterCubit extends Cubit<TimeCounterState> {
Timer? timer;

TimedRefreshButtonCubit() : super(const TimedRefreshButtonState(remainingUnlockTime: Duration.zero));
TimeCounterCubit() : super(const TimeCounterState(remainingUnlockTime: Duration.zero));

@override
Future<void> close() {
Expand All @@ -17,7 +17,7 @@ class TimedRefreshButtonCubit extends Cubit<TimedRefreshButtonState> {
void startCounting(DateTime expirationTime) {
timer?.cancel();
Duration remainingUnlockTime = _calculateRemainingUnlockTime(expirationTime);
emit(TimedRefreshButtonState(remainingUnlockTime: remainingUnlockTime));
emit(TimeCounterState(remainingUnlockTime: remainingUnlockTime));
timer = Timer.periodic(const Duration(seconds: 1), _handleTimerTick);
}

Expand All @@ -28,10 +28,10 @@ class TimedRefreshButtonCubit extends Cubit<TimedRefreshButtonState> {

void _handleTimerTick(Timer timer) {
if (state.remainingUnlockTime.inSeconds > 0) {
emit(TimedRefreshButtonState(remainingUnlockTime: state.remainingUnlockTime - const Duration(seconds: 1)));
emit(TimeCounterState(remainingUnlockTime: state.remainingUnlockTime - const Duration(seconds: 1)));
} else {
timer.cancel();
emit(const TimedRefreshButtonState(remainingUnlockTime: Duration.zero));
emit(const TimeCounterState(remainingUnlockTime: Duration.zero));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import 'package:equatable/equatable.dart';

class TimedRefreshButtonState extends Equatable {
class TimeCounterState extends Equatable {
final Duration remainingUnlockTime;

const TimedRefreshButtonState({
const TimeCounterState({
required this.remainingUnlockTime,
});

Expand Down
18 changes: 9 additions & 9 deletions lib/blocs/widgets/kira/kira_list/abstract_list/a_list_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -249,15 +249,15 @@ abstract class AListBloc<T extends AListItem> extends Bloc<AListEvent, AListStat
});
_setupPagesCacheFromList(allListItems);
} catch (_) {
AppLogger().log(message: 'Cannot fetch all list data for ${listController.runtimeType}');
pageReloadController.hasErrors = true;
showLoadingOverlay.value = false;
emit(ListErrorState());
return;
}

if (isClosed == false) {
showLoadingOverlay.value = false;
if (isClosed == false) {
AppLogger().log(message: 'Cannot fetch all list data for ${listController.runtimeType}');
pageReloadController.hasErrors = true;
emit(ListErrorState());
}
} finally {
if (isClosed == false) {
showLoadingOverlay.value = false;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ class NetworkCustomSectionCubit extends Cubit<NetworkCustomSectionState> {
NetworkCustomSectionCubit() : super(NetworkCustomSectionState());

Future<void> checkConnection(Uri uri) async {
NetworkUnknownModel networkUnknownModel = NetworkUnknownModel(uri: uri, connectionStatusType: ConnectionStatusType.disconnected);
NetworkUnknownModel networkUnknownModel =
NetworkUnknownModel(uri: uri, connectionStatusType: ConnectionStatusType.disconnected, lastRefreshDateTime: DateTime.now());
bool networkCustomBool = _isNetworkCustom(networkUnknownModel);

if (networkCustomBool == false || state.containsUriWithEqualUrn(uri)) {
Expand All @@ -36,8 +37,16 @@ class NetworkCustomSectionCubit extends Cubit<NetworkCustomSectionState> {
String stateId = StringUtils.generateUuid();
_activeStateId = stateId;

ANetworkStatusModel? newCheckedNetworkStatusModel = await _refreshCustomNetwork(state.checkedNetworkStatusModel);
ANetworkStatusModel? newLastConnectedNetworkStatusModel = await _refreshCustomNetwork(state.lastConnectedNetworkStatusModel);
ANetworkStatusModel? newCheckedNetworkStatusModel;
ANetworkStatusModel? newLastConnectedNetworkStatusModel;

if (state.checkedNetworkStatusModel?.connectionStatusType != ConnectionStatusType.refreshing) {
newCheckedNetworkStatusModel = await _refreshCustomNetwork(state.checkedNetworkStatusModel);
}

if (state.lastConnectedNetworkStatusModel?.connectionStatusType != ConnectionStatusType.refreshing) {
newLastConnectedNetworkStatusModel = await _refreshCustomNetwork(state.lastConnectedNetworkStatusModel);
}

bool stateIdEqualBool = stateId == _activeStateId;
if (stateIdEqualBool) {
Expand Down
Loading

0 comments on commit d89552a

Please sign in to comment.