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

feat: add hint feature flag #360

Merged
merged 5 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 17 additions & 0 deletions lib/hint/bloc/hint_bloc.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:api_client/api_client.dart';
import 'package:bloc/bloc.dart';
import 'package:board_info_repository/board_info_repository.dart';
import 'package:equatable/equatable.dart';
import 'package:game_domain/game_domain.dart';

Expand All @@ -9,15 +10,31 @@ part 'hint_state.dart';
class HintBloc extends Bloc<HintEvent, HintState> {
HintBloc({
required HintResource hintResource,
required BoardInfoRepository boardInfoRepository,
}) : _hintResource = hintResource,
_boardInfoRepository = boardInfoRepository,
super(const HintState()) {
on<HintEnabledRequested>(_onHintEnabledRequested);
on<HintModeEntered>(_onHintModeEntered);
on<HintModeExited>(_onHintModeExited);
on<HintRequested>(_onHintRequested);
on<PreviousHintsRequested>(_onPreviousHintsRequested);
}

final HintResource _hintResource;
final BoardInfoRepository _boardInfoRepository;

Future<void> _onHintEnabledRequested(
HintEnabledRequested event,
Emitter<HintState> emit,
) async {
await emit.forEach(
_boardInfoRepository.isHintsEnabled(),
onData: (isHintsEnabled) {
return state.copyWith(isHintsEnabled: isHintsEnabled);
},
);
}

void _onHintModeEntered(
HintModeEntered event,
Expand Down
7 changes: 7 additions & 0 deletions lib/hint/bloc/hint_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ sealed class HintEvent extends Equatable {
const HintEvent();
}

class HintEnabledRequested extends HintEvent {
const HintEnabledRequested();

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

class HintModeEntered extends HintEvent {
const HintModeEntered();

Expand Down
6 changes: 5 additions & 1 deletion lib/hint/bloc/hint_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,25 @@ enum HintStatus {

class HintState extends Equatable {
const HintState({
this.isHintsEnabled = false,
this.status = HintStatus.initial,
this.hints = const [],
this.maxHints = 10,
});

final bool isHintsEnabled;
final HintStatus status;
final List<Hint> hints;
final int maxHints;

HintState copyWith({
bool? isHintsEnabled,
HintStatus? status,
List<Hint>? hints,
int? maxHints,
}) {
return HintState(
isHintsEnabled: isHintsEnabled ?? this.isHintsEnabled,
status: status ?? this.status,
hints: hints ?? this.hints,
maxHints: maxHints ?? this.maxHints,
Expand All @@ -50,5 +54,5 @@ class HintState extends Equatable {
int get hintsLeft => maxHints - hints.length;

@override
List<Object> get props => [status, hints, maxHints];
List<Object> get props => [isHintsEnabled, status, hints, maxHints];
}
37 changes: 22 additions & 15 deletions lib/hint/widgets/hints_section.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,23 +39,30 @@ class HintsSection extends StatelessWidget {
final isThinking = context
.select((HintBloc bloc) => bloc.state.status == HintStatus.thinking);
final allHints = context.select((HintBloc bloc) => bloc.state.hints);

return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
...allHints.mapIndexed(
(i, hint) => HintQuestionResponse(
index: i,
hint: hint,
final isHintsEnabled =
context.select((HintBloc bloc) => bloc.state.isHintsEnabled);

if (!isHintsEnabled) return const SizedBox.shrink();

return SingleChildScrollView(
reverse: true,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
...allHints.mapIndexed(
(i, hint) => HintQuestionResponse(
index: i,
hint: hint,
),
),
),
if (isThinking) ...[
const SizedBox(height: 24),
const Center(child: HintLoadingIndicator()),
const SizedBox(height: 8),
if (isThinking) ...[
const SizedBox(height: 24),
const Center(child: HintLoadingIndicator()),
const SizedBox(height: 8),
],
],
],
),
);
}
}
Expand Down
6 changes: 5 additions & 1 deletion lib/word_selection/view/word_selection_page.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:api_client/api_client.dart';
import 'package:board_info_repository/board_info_repository.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:io_crossword/hint/hint.dart';
Expand All @@ -20,7 +21,10 @@ class WordSelectionPage extends StatelessWidget {
key: Key(wordId),
create: (context) => HintBloc(
hintResource: context.read<HintResource>(),
)..add(PreviousHintsRequested(wordId)),
boardInfoRepository: context.read<BoardInfoRepository>(),
)
..add(const HintEnabledRequested())
..add(PreviousHintsRequested(wordId)),
child: const WordSelectionView(),
);
}
Expand Down
40 changes: 23 additions & 17 deletions lib/word_selection/view/word_solving_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class WordSolvingLargeView extends StatelessWidget {
final selectedWord =
context.select((WordSelectionBloc bloc) => bloc.state.word);
if (selectedWord == null) return const SizedBox.shrink();
final isHintsEnabled =
context.select((HintBloc bloc) => bloc.state.isHintsEnabled);

return Column(
children: [
Expand All @@ -45,8 +47,10 @@ class WordSolvingLargeView extends StatelessWidget {
textAlign: TextAlign.center,
),
const SizedBox(height: 32),
const HintsTitle(),
const SizedBox(height: 32),
if (isHintsEnabled) ...[
const HintsTitle(),
const SizedBox(height: 32),
],
Flexible(
child: BlocSelector<WordSelectionBloc, WordSelectionState,
WordSelectionStatus>(
Expand All @@ -56,10 +60,7 @@ class WordSolvingLargeView extends StatelessWidget {
return const CircularProgressIndicator();
}

return const SingleChildScrollView(
reverse: true,
child: HintsSection(),
);
return const HintsSection();
},
),
),
Expand Down Expand Up @@ -96,6 +97,8 @@ class _WordSolvingSmallViewState extends State<WordSolvingSmallView> {
final selectedWord =
context.select((WordSelectionBloc bloc) => bloc.state.word);
if (selectedWord == null) return const SizedBox.shrink();
final isHintsEnabled =
context.select((HintBloc bloc) => bloc.state.isHintsEnabled);

return Column(
children: [
Expand All @@ -112,8 +115,10 @@ class _WordSolvingSmallViewState extends State<WordSolvingSmallView> {
textAlign: TextAlign.center,
),
const SizedBox(height: 32),
const HintsTitle(),
const SizedBox(height: 32),
if (isHintsEnabled) ...[
const HintsTitle(),
const SizedBox(height: 32),
],
Expanded(
child: BlocSelector<WordSelectionBloc, WordSelectionState,
WordSelectionStatus>(
Expand All @@ -123,10 +128,7 @@ class _WordSolvingSmallViewState extends State<WordSolvingSmallView> {
return const Center(child: CircularProgressIndicator());
}

return const SingleChildScrollView(
reverse: true,
child: HintsSection(),
);
return const HintsSection();
},
),
),
Expand All @@ -148,8 +150,10 @@ class BottomPanel extends StatelessWidget {
Widget build(BuildContext context) {
final isHintModeActive =
context.select((HintBloc bloc) => bloc.state.isHintModeActive);
final isHintsEnabled =
context.select((HintBloc bloc) => bloc.state.isHintsEnabled);

if (isHintModeActive) {
if (isHintModeActive && isHintsEnabled) {
return const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expand All @@ -166,10 +170,12 @@ class BottomPanel extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Flexible(
child: GeminiHintButton(),
),
const SizedBox(width: 8),
if (isHintsEnabled) ...[
const Flexible(
child: GeminiHintButton(),
),
const SizedBox(width: 8),
],
Flexible(
child: SubmitButton(controller: controller),
),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:rxdart/rxdart.dart';

/// {@template board_info_exception}
/// An exception to throw when there is an error fetching the board info.
Expand Down Expand Up @@ -34,6 +35,8 @@ class BoardInfoRepository {
/// The [CollectionReference] for the config.
late final CollectionReference<Map<String, dynamic>> boardInfoCollection;

BehaviorSubject<bool>? _hintsEnabled;

/// Returns the total words count available in the crossword.
Stream<int> getTotalWordsCount() {
try {
Expand Down Expand Up @@ -85,4 +88,27 @@ class BoardInfoRepository {
throw BoardInfoException(error, stackStrace);
}
}

/// Returns the hints enabled status.
Stream<bool> isHintsEnabled() {
if (_hintsEnabled != null) return _hintsEnabled!.stream;

_hintsEnabled = BehaviorSubject<bool>();

boardInfoCollection
.where('type', isEqualTo: 'is_hints_enabled')
.snapshots()
.map((snapshot) {
final docs = snapshot.docs;
// If the flag is not found, we assume it is disabled.
if (docs.isEmpty) return false;

final isHintsEnabled = docs.first.data()['value'] as bool;
return isHintsEnabled;
})
.listen(_hintsEnabled!.add)
.onError(_hintsEnabled!.addError);

return _hintsEnabled!.stream;
}
}
1 change: 1 addition & 0 deletions packages/board_info_repository/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ dependencies:
cloud_firestore: ^4.16.1
flutter:
sdk: flutter
rxdart: ^0.27.7

dev_dependencies:
flutter_test:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,22 @@ void main() {
);
});
});

group('isHintsEnabled', () {
test('returns hints enabled status from firebase', () {
final doc = _MockQueryDocumentSnapshot<Map<String, dynamic>>();
final query = _MockQuerySnapshot<Map<String, dynamic>>();
when(
() => collection.where('type', isEqualTo: 'is_hints_enabled'),
).thenReturn(collection);
when(collection.snapshots).thenAnswer((_) => Stream.value(query));
when(() => query.docs).thenReturn([doc]);
when(doc.data).thenReturn({'value': true});

final result = boardInfoRepository.isHintsEnabled();
expect(result, emits(true));
});
});
});

group('BoardInfoException', () {
Expand Down
Loading
Loading