Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PE-4994: Prompt Users to Create a Snapshot #1517

Merged
merged 35 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
72192f6
feat(prompt to snapshot): WIP PE-4994
matibat Dec 11, 2023
def0599
feat(prompt to snapshot): dialog PE-4994
matibat Dec 12, 2023
68efc81
feat(prompt to snapshot): removes hard-coded value PE-4994
matibat Dec 12, 2023
53339b3
feat(prompt to snapshot): removes unnecessary logs PE-4994
matibat Dec 12, 2023
7a824ef
feat(prompt to snapshot): makes the bloc testable PE-4994
matibat Dec 12, 2023
f1d8ce4
test(prompt to snapshot): unit tests for the bloc PE-4994
matibat Dec 12, 2023
e13fcf8
feat(prompt to snapshot): dont remind me again modal PE-4994
matibat Dec 12, 2023
21b6980
test(prompt to snapshot): adds test case PE-4994
matibat Dec 12, 2023
37a9646
feat(prompt to snapshot): wordings PE-4994
matibat Dec 12, 2023
0cf2625
feat(prompt to snapshot): cleans the dontremindmeagain flag on logout…
matibat Dec 12, 2023
99c1c53
chore(prompt to snapshot): removes unnecessary comment Pe4994
matibat Dec 13, 2023
bf3500f
feat(prompt to snapshot): do not prompt while sync is running PE-4994
matibat Dec 14, 2023
9665ecf
chore(drive detail page): removes unnecessary comment PE-4994
matibat Dec 14, 2023
23e2a71
feat(prompt to snapshot): logging PE-4994
matibat Dec 14, 2023
e8735b7
Merge remote-tracking branch 'origin/dev' into PE-4994
matibat Dec 14, 2023
211622e
Merge remote-tracking branch 'origin/PE-4994' into PE-4994
matibat Dec 14, 2023
c17b849
feat(prompt to snapshot bloc): uses a map for holding the TXs count P…
matibat Jan 8, 2024
6aa4a5e
Merge remote-tracking branch 'origin/dev' into PE-4994
matibat Jan 9, 2024
2f6bd6f
feat(prompt to snapshot): corrects wrong import PE-4994
matibat Jan 9, 2024
d5f058e
feat(prompt to snapshot): makes dontRemindMeAgain be in a per-wallet …
matibat Jan 10, 2024
17d651b
test(prompt to snapshot): updates unit tests PE-4994
matibat Jan 10, 2024
41fb9ca
chore(drive detail page): removes uused variable PE-4994
matibat Jan 10, 2024
78164ba
Merge remote-tracking branch 'origin/dev' into PE-4994
matibat Jan 10, 2024
9708365
test(debouncer): implements unit tests PE-4994
matibat Jan 10, 2024
8569554
chore(prompt to snapshot dialog): reverts unnecessary change PE-4994
matibat Jan 10, 2024
cf3fd77
feat(prompt to snapshot bloc): makes it check permissions by itself i…
matibat Jan 11, 2024
497bc0f
test(prompt to snapshot): updates unit test PE-5254
matibat Jan 11, 2024
06e68c1
feat(prompt to snapshot): don-t prompt while snapshotting PE-4994
matibat Jan 11, 2024
f49bd81
test(prompt to snapshot): unit tests snapshotting state PE-4994
matibat Jan 11, 2024
ccbef66
chore(cleanup): nit PE-4994
matibat Jan 11, 2024
bb67b10
fix(prompt snapshot)
thiagocarvalhodev Jan 19, 2024
be4d8e8
Merge pull request #1565 from ardriveapp/PE-5457-modal-doesnt-reappea…
thiagocarvalhodev Jan 19, 2024
a55382e
Merge branch 'dev' into PE-4994
karlprieb Jan 22, 2024
290e008
Merge branch 'dev' into PE-4994
thiagocarvalhodev Jan 23, 2024
e643868
Update main.dart
thiagocarvalhodev Jan 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions lib/blocs/drives/drives_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import 'dart:async';

import 'package:ardrive/authentication/ardrive_auth.dart';
import 'package:ardrive/blocs/blocs.dart';
import 'package:ardrive/blocs/prompt_to_snapshot/prompt_to_snapshot_bloc.dart';
import 'package:ardrive/blocs/prompt_to_snapshot/prompt_to_snapshot_event.dart';
import 'package:ardrive/core/activity_tracker.dart';
import 'package:ardrive/models/models.dart';
import 'package:ardrive/utils/user_utils.dart';
Expand All @@ -17,6 +19,7 @@ part 'drives_state.dart';
/// It works even if the user profile is unavailable.
class DrivesCubit extends Cubit<DrivesState> {
final ProfileCubit _profileCubit;
final PromptToSnapshotBloc _promptToSnapshotBloc;
final DriveDao _driveDao;
final ArDriveAuth _auth;

Expand All @@ -26,9 +29,11 @@ class DrivesCubit extends Cubit<DrivesState> {
required ArDriveAuth auth,
this.initialSelectedDriveId,
required ProfileCubit profileCubit,
required PromptToSnapshotBloc promptToSnapshotBloc,
required DriveDao driveDao,
required ActivityTracker activityTracker,
}) : _profileCubit = profileCubit,
_promptToSnapshotBloc = promptToSnapshotBloc,
_driveDao = driveDao,
_auth = auth,
super(DrivesLoadInProgress()) {
Expand Down Expand Up @@ -71,6 +76,8 @@ class DrivesCubit extends Cubit<DrivesState> {
final sharedDrives =
drives.where((d) => !isDriveOwner(auth, d.ownerAddress)).toList();

_promptToSnapshotBloc.add(SelectedDrive(driveId: selectedDriveId));

emit(
DrivesLoadSuccess(
selectedDriveId: selectedDriveId,
Expand All @@ -90,15 +97,26 @@ class DrivesCubit extends Cubit<DrivesState> {

void selectDrive(String driveId) {
final canCreateNewDrive = _profileCubit.state is ProfileLoggedIn;
final state = this.state is DrivesLoadSuccess
? (this.state as DrivesLoadSuccess).copyWith(selectedDriveId: driveId)
: DrivesLoadedWithNoDrivesFound(canCreateNewDrive: canCreateNewDrive);
final DrivesState state;
if (this.state is DrivesLoadSuccess) {
state = (this.state as DrivesLoadSuccess).copyWith(
selectedDriveId: driveId,
);
_promptToSnapshotBloc.add(SelectedDrive(driveId: driveId));
} else {
state = DrivesLoadedWithNoDrivesFound(
canCreateNewDrive: canCreateNewDrive,
);
_promptToSnapshotBloc.add(const SelectedDrive(driveId: null));
}
emit(state);
}

void cleanDrives() {
initialSelectedDriveId = null;

_promptToSnapshotBloc.add(const SelectedDrive(driveId: null));

final state = DrivesLoadSuccess(
selectedDriveId: null,
userDrives: const [],
Expand All @@ -121,10 +139,13 @@ class DrivesCubit extends Cubit<DrivesState> {
? state.sharedDrives.first.id
: null;
if (firstOrNullDrive != null) {
_promptToSnapshotBloc.add(SelectedDrive(driveId: firstOrNullDrive));
emit(state.copyWith(selectedDriveId: firstOrNullDrive));
return;
}
}

_promptToSnapshotBloc.add(const SelectedDrive(driveId: null));
emit(DrivesLoadedWithNoDrivesFound(canCreateNewDrive: canCreateNewDrive));
}

Expand Down
1 change: 0 additions & 1 deletion lib/blocs/feedback_survey/feedback_survey_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ class FeedbackSurveyCubit extends Cubit<FeedbackSurveyState> {

FeedbackSurveyCubit(
FeedbackSurveyState initialState, {

/// takes a KeyValueStore for testing purposes
KeyValueStore? store,
}) : super(initialState) {
Expand Down
169 changes: 169 additions & 0 deletions lib/blocs/prompt_to_snapshot/prompt_to_snapshot_bloc.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import 'package:ardrive/blocs/prompt_to_snapshot/prompt_to_snapshot_event.dart';
import 'package:ardrive/blocs/prompt_to_snapshot/prompt_to_snapshot_state.dart';
import 'package:ardrive/utils/debouncer.dart';
import 'package:ardrive/utils/key_value_store.dart';
import 'package:ardrive/utils/local_key_value_store.dart';
import 'package:ardrive/utils/logger/logger.dart';
import 'package:ardrive_utils/ardrive_utils.dart';
import 'package:collection/collection.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

const int secondsBeforePrompting = 20;
const int numberOfTxsBeforeSnapshot = 1000;

class PromptToSnapshotBloc
extends Bloc<PromptToSnapshotEvent, PromptToSnapshotState> {
final Debouncer _debouncer = Debouncer(
delay: const Duration(seconds: secondsBeforePrompting),
);
static KeyValueStore? _maybeStore;
// Should be per-drive?
static const storeKey = 'dont-ask-to-snapshot-again';
matibat marked this conversation as resolved.
Show resolved Hide resolved

PromptToSnapshotBloc({
/// takes a KeyValueStore for testing purposes
KeyValueStore? store,
matibat marked this conversation as resolved.
Show resolved Hide resolved
}) : super(const PromptToSnapshotIdle(driveId: null)) {
on<CountSyncedTxs>(_onCountSyncedTxs);
on<SelectedDrive>(_onDriveSelected);
on<DriveSnapshotting>(_onDriveSnapshotting);
on<DriveSnapshotted>(_onDriveSnapshotted);
on<DismissDontAskAgain>(_onDismissDontAskAgain);

_maybeStore ??= store;
}

Future<KeyValueStore> get _store async {
/// lazily initialize KeyValueStore
_maybeStore ??= await LocalKeyValueStore.getInstance();
return _maybeStore!;
}

Future<void> _onCountSyncedTxs(
CountSyncedTxs event,
Emitter<PromptToSnapshotState> emit,
) async {
CountOfTxsSyncedWithGql.countForDrive(
event.driveId,
event.txsSyncedWithGqlCount,
);
}

Future<void> _onDriveSelected(
SelectedDrive event,
Emitter<PromptToSnapshotState> emit,
) async {
logger.d('Selected drive: ${event.driveId}');

if (event.driveId == null) {
if (state is PromptToSnapshotIdle) {
emit(const PromptToSnapshotIdle(driveId: null));
}
return;
}

final wouldDriveBenefitFromSnapshot =
CountOfTxsSyncedWithGql.wouldDriveBenefitFromSnapshot(event.driveId!);

logger.d(
'Debouncing prompt to snapshot (${event.driveId}): '
'should prompt: $wouldDriveBenefitFromSnapshot, is closed: $isClosed',
);

matibat marked this conversation as resolved.
Show resolved Hide resolved
await _debouncer.run(() async {
final shouldAskAgain = await _shouldAskToSnapshotAgain();
if (shouldAskAgain &&
wouldDriveBenefitFromSnapshot &&
!isClosed &&
state is PromptToSnapshotIdle) {
logger.d('Prompting to snapshot for ${event.driveId}');
emit(PromptToSnapshotPrompting(driveId: event.driveId!));
}
}).catchError((e) {
logger.d('Debouncer cancelled: $e');
});
matibat marked this conversation as resolved.
Show resolved Hide resolved
}

Future<void> _onDriveSnapshotting(
DriveSnapshotting event,
Emitter<PromptToSnapshotState> emit,
) async {
emit(PromptToSnapshotPrompting(driveId: event.driveId));
}

Future<void> _onDriveSnapshotted(
DriveSnapshotted event,
Emitter<PromptToSnapshotState> emit,
) async {
CountOfTxsSyncedWithGql.resetForDrive(event.driveId);
emit(PromptToSnapshotIdle(driveId: event.driveId));
}

Future<void> _onDismissDontAskAgain(
DismissDontAskAgain event,
Emitter<PromptToSnapshotState> emit,
) async {
await _dontAskToSnapshotAgain();
emit(PromptToSnapshotIdle(driveId: event.driveId));
}

Future<void> _dontAskToSnapshotAgain() async {
await (await _store).putBool(storeKey, true);
}

Future<bool> _shouldAskToSnapshotAgain() async {
final store = await _store;
final value = await store.getBool(storeKey);
return value != true;
}
}

abstract class CountOfTxsSyncedWithGql {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, @thiagocarvalhodev, this is the class I mentioned earlier.

  • We can extend this class and/or compose it with other similar ones to track stats as you suggested.
  • And we can add the persistence here.

static final List<CountOfTxsSyncedWithGqlOfDrive>
karlprieb marked this conversation as resolved.
Show resolved Hide resolved
_countOfTxsSynceWithGqlOfDrive = [];

static int _getForDrive(DriveID driveId) {
return _countOfTxsSynceWithGqlOfDrive
.firstWhereOrNull((e) => e.driveId == driveId)
?.count ??
0;
}

static void countForDrive(DriveID driveId, int count) {
final currentCount = _getForDrive(driveId);
_countOfTxsSynceWithGqlOfDrive.removeWhere((e) => e.driveId == driveId);
karlprieb marked this conversation as resolved.
Show resolved Hide resolved
_countOfTxsSynceWithGqlOfDrive.add(CountOfTxsSyncedWithGqlOfDrive(
count: currentCount + count,
driveId: driveId,
));
logger.d(
'Count of txs synced with gql for $driveId: $count,'
' total: ${currentCount + count}}',
);
matibat marked this conversation as resolved.
Show resolved Hide resolved
}

static void resetForDrive(DriveID driveId) {
_countOfTxsSynceWithGqlOfDrive.removeWhere((e) => e.driveId == driveId);
logger.d('Reset count of txs synced with gql for $driveId');
matibat marked this conversation as resolved.
Show resolved Hide resolved
}

static bool wouldDriveBenefitFromSnapshot(DriveID driveId) {
final count = _getForDrive(driveId);
final wouldBenefit = count >= numberOfTxsBeforeSnapshot;
logger.d(
'Would drive $driveId benefit from snapshot? $wouldBenefit,'
' count: $count',
);
matibat marked this conversation as resolved.
Show resolved Hide resolved
return wouldBenefit;
}
}

class CountOfTxsSyncedWithGqlOfDrive {
final int count;
final DriveID driveId;

const CountOfTxsSyncedWithGqlOfDrive({
required this.count,
required this.driveId,
});
}
57 changes: 57 additions & 0 deletions lib/blocs/prompt_to_snapshot/prompt_to_snapshot_event.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import 'package:ardrive_utils/ardrive_utils.dart';
import 'package:equatable/equatable.dart';

abstract class PromptToSnapshotEvent extends Equatable {
final DriveID? driveId;
const PromptToSnapshotEvent({required this.driveId});

@override
List<Object> get props => [];
}

class CountSyncedTxs extends PromptToSnapshotEvent {
final int txsSyncedWithGqlCount;
final bool wasDeepSync;

@override
String get driveId => super.driveId!;

const CountSyncedTxs({
required DriveID driveId,
required this.txsSyncedWithGqlCount,
required this.wasDeepSync,
}) : super(driveId: driveId);

@override
List<Object> get props => [driveId, txsSyncedWithGqlCount, wasDeepSync];
}

class SelectedDrive extends PromptToSnapshotEvent {
const SelectedDrive({required super.driveId});
}

class DriveSnapshotting extends PromptToSnapshotEvent {
@override
String get driveId => super.driveId!;

const DriveSnapshotting({required DriveID driveId}) : super(driveId: driveId);
}

class DriveSnapshotted extends PromptToSnapshotEvent {
final int txsSyncedWithGqlCount;

@override
String get driveId => super.driveId!;

const DriveSnapshotted({
required DriveID driveId,
this.txsSyncedWithGqlCount = 0,
}) : super(driveId: driveId);

@override
List<Object> get props => [driveId, txsSyncedWithGqlCount];
}

class DismissDontAskAgain extends PromptToSnapshotEvent {
const DismissDontAskAgain() : super(driveId: null);
}
44 changes: 44 additions & 0 deletions lib/blocs/prompt_to_snapshot/prompt_to_snapshot_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import 'package:ardrive_utils/ardrive_utils.dart';
import 'package:equatable/equatable.dart';

abstract class PromptToSnapshotState extends Equatable {
final DriveID? driveId;

const PromptToSnapshotState({
required this.driveId,
});

@override
List<Object> get props => [driveId ?? ''];
}

class PromptToSnapshotIdle extends PromptToSnapshotState {
const PromptToSnapshotIdle({
required super.driveId,
});

PromptToSnapshotIdle copyWith({
DriveID? driveId,
}) {
return PromptToSnapshotIdle(
driveId: driveId ?? this.driveId,
);
}
}

class PromptToSnapshotPrompting extends PromptToSnapshotState {
@override
String get driveId => super.driveId!;

const PromptToSnapshotPrompting({
required DriveID driveId,
}) : super(driveId: driveId);

PromptToSnapshotPrompting copyWith({
String? driveId,
}) {
return PromptToSnapshotPrompting(
driveId: driveId ?? this.driveId,
);
}
}
Loading
Loading