refactor: leaderboard model (#78)

* refactor: changed name of LeaderboardEntry at LeaderboardRepository to LeaderboardEntryData

* chore: doc and analysis errors

* refactor: removed findNested extensions (#77)

* Update lib/leaderboard/bloc/leaderboard_bloc.dart

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* Update lib/leaderboard/bloc/leaderboard_bloc.dart

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* fix: leaderboard gen file

* refactor: moved leaderboard models to separate path

* test: fixed tests with leaderboard model

* chore: doc

Co-authored-by: Alejandro Santiago <dev@alestiago.com>
Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>
pull/88/head
Rui Miguel Alonso 3 years ago committed by GitHub
parent 92404fbdba
commit 573e16d0d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -3,6 +3,7 @@ import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball/leaderboard/leaderboard.dart';
part 'leaderboard_event.dart';
part 'leaderboard_state.dart';
@ -30,10 +31,16 @@ class LeaderboardBloc extends Bloc<LeaderboardEvent, LeaderboardState> {
try {
final top10Leaderboard =
await _leaderboardRepository.fetchTop10Leaderboard();
final leaderboardEntries = <LeaderboardEntry>[];
top10Leaderboard.asMap().forEach(
(index, value) => leaderboardEntries.add(value.toEntry(index + 1)),
);
emit(
state.copyWith(
status: LeaderboardStatus.success,
leaderboard: top10Leaderboard,
leaderboard: leaderboardEntries,
),
);
} catch (error) {

@ -9,7 +9,7 @@ abstract class LeaderboardEvent extends Equatable {
}
/// {@template top_10_fetched}
/// Request the top 10 [LeaderboardEntry]s.
/// Request the top 10 [LeaderboardEntryData]s.
/// {endtemplate}
class Top10Fetched extends LeaderboardEvent {
/// {@macro top_10_fetched}
@ -20,7 +20,7 @@ class Top10Fetched extends LeaderboardEvent {
}
/// {@template leaderboard_entry_added}
/// Writes a new [LeaderboardEntry].
/// Writes a new [LeaderboardEntryData].
///
/// Should be added when a player finishes a game.
/// {endtemplate}
@ -28,8 +28,8 @@ class LeaderboardEntryAdded extends LeaderboardEvent {
/// {@macro leaderboard_entry_added}
const LeaderboardEntryAdded({required this.entry});
/// [LeaderboardEntry] to be written to the remote storage.
final LeaderboardEntry entry;
/// [LeaderboardEntryData] to be written to the remote storage.
final LeaderboardEntryData entry;
@override
List<Object?> get props => [entry];

@ -1 +1,2 @@
export 'bloc/leaderboard_bloc.dart';
export 'models/leader_board_entry.dart';

@ -0,0 +1,80 @@
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball_theme/pinball_theme.dart';
/// {@template leaderboard_entry}
/// A model representing a leaderboard entry containing the ranking position,
/// player's initials, score, and chosen character.
///
/// {@endtemplate}
class LeaderboardEntry {
/// {@macro leaderboard_entry}
LeaderboardEntry({
required this.rank,
required this.playerInitials,
required this.score,
required this.character,
});
/// Ranking position for [LeaderboardEntry].
final String rank;
/// Player's chosen initials for [LeaderboardEntry].
final String playerInitials;
/// Score for [LeaderboardEntry].
final int score;
/// [CharacterTheme] for [LeaderboardEntry].
final AssetGenImage character;
}
/// Converts [LeaderboardEntryData] from repository to [LeaderboardEntry].
extension LeaderboardEntryDataX on LeaderboardEntryData {
/// Conversion method to [LeaderboardEntry]
LeaderboardEntry toEntry(int position) {
return LeaderboardEntry(
rank: position.toString(),
playerInitials: playerInitials,
score: score,
character: character.toTheme.characterAsset,
);
}
}
/// Converts [CharacterType] to [CharacterTheme] to show on UI character theme
/// from repository.
extension CharacterTypeX on CharacterType {
/// Conversion method to [CharacterTheme]
CharacterTheme get toTheme {
switch (this) {
case CharacterType.dash:
return const DashTheme();
case CharacterType.sparky:
return const SparkyTheme();
case CharacterType.android:
return const AndroidTheme();
case CharacterType.dino:
return const DinoTheme();
}
}
}
/// Converts [CharacterTheme] to [CharacterType] to persist at repository the
/// character theme from UI.
extension CharacterThemeX on CharacterTheme {
/// Conversion method to [CharacterType]
CharacterType get toType {
switch (runtimeType) {
case DashTheme:
return CharacterType.dash;
case SparkyTheme:
return CharacterType.sparky;
case AndroidTheme:
return CharacterType.android;
case DinoTheme:
return CharacterType.dino;
default:
return CharacterType.dash;
}
}
}

@ -83,9 +83,9 @@ class LeaderboardRepository {
final FirebaseFirestore _firebaseFirestore;
/// Acquires top 10 [LeaderboardEntry]s.
Future<List<LeaderboardEntry>> fetchTop10Leaderboard() async {
final leaderboardEntries = <LeaderboardEntry>[];
/// Acquires top 10 [LeaderboardEntryData]s.
Future<List<LeaderboardEntryData>> fetchTop10Leaderboard() async {
final leaderboardEntries = <LeaderboardEntryData>[];
late List<QueryDocumentSnapshot> documents;
try {
@ -103,7 +103,7 @@ class LeaderboardRepository {
final data = document.data() as Map<String, dynamic>?;
if (data != null) {
try {
leaderboardEntries.add(LeaderboardEntry.fromJson(data));
leaderboardEntries.add(LeaderboardEntryData.fromJson(data));
} catch (error, stackTrace) {
throw LeaderboardDeserializationException(error, stackTrace);
}
@ -115,7 +115,9 @@ class LeaderboardRepository {
/// Adds player's score entry to the leaderboard and gets their
/// [LeaderboardRanking].
Future<LeaderboardRanking> addLeaderboardEntry(LeaderboardEntry entry) async {
Future<LeaderboardRanking> addLeaderboardEntry(
LeaderboardEntryData entry,
) async {
late DocumentReference entryReference;
try {
entryReference = await _firebaseFirestore

@ -1,9 +1,9 @@
import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';
part 'leaderboard_entry.g.dart';
part 'leaderboard_entry_data.g.dart';
/// Google character type associated with a [LeaderboardEntry].
/// Google character type associated with a [LeaderboardEntryData].
enum CharacterType {
/// Dash character.
dash,
@ -18,7 +18,7 @@ enum CharacterType {
dino,
}
/// {@template leaderboard_entry}
/// {@template leaderboard_entry_data}
/// A model representing a leaderboard entry containing the player's initials,
/// score, and chosen character.
///
@ -34,42 +34,42 @@ enum CharacterType {
/// ```
/// {@endtemplate}
@JsonSerializable()
class LeaderboardEntry extends Equatable {
/// {@macro leaderboard_entry}
const LeaderboardEntry({
class LeaderboardEntryData extends Equatable {
/// {@macro leaderboard_entry_data}
const LeaderboardEntryData({
required this.playerInitials,
required this.score,
required this.character,
});
/// Factory which converts a [Map] into a [LeaderboardEntry].
factory LeaderboardEntry.fromJson(Map<String, dynamic> json) {
/// Factory which converts a [Map] into a [LeaderboardEntryData].
factory LeaderboardEntryData.fromJson(Map<String, dynamic> json) {
return _$LeaderboardEntryFromJson(json);
}
/// Converts the [LeaderboardEntry] to [Map].
/// Converts the [LeaderboardEntryData] to [Map].
Map<String, dynamic> toJson() => _$LeaderboardEntryToJson(this);
/// Player's chosen initials for [LeaderboardEntry].
/// Player's chosen initials for [LeaderboardEntryData].
///
/// Example: 'ABC'.
@JsonKey(name: 'playerInitials')
final String playerInitials;
/// Score for [LeaderboardEntry].
/// Score for [LeaderboardEntryData].
///
/// Example: 1500.
@JsonKey(name: 'score')
final int score;
/// [CharacterType] for [LeaderboardEntry].
/// [CharacterType] for [LeaderboardEntryData].
///
/// Example: [CharacterType.dash].
@JsonKey(name: 'character')
final CharacterType character;
/// An empty [LeaderboardEntry] object.
static const empty = LeaderboardEntry(
/// An empty [LeaderboardEntryData] object.
static const empty = LeaderboardEntryData(
playerInitials: '',
score: 0,
character: CharacterType.dash,

@ -1,19 +1,19 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'leaderboard_entry.dart';
part of 'leaderboard_entry_data.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
LeaderboardEntry _$LeaderboardEntryFromJson(Map<String, dynamic> json) =>
LeaderboardEntry(
LeaderboardEntryData _$LeaderboardEntryFromJson(Map<String, dynamic> json) =>
LeaderboardEntryData(
playerInitials: json['playerInitials'] as String,
score: json['score'] as int,
character: $enumDecode(_$CharacterTypeEnumMap, json['character']),
);
Map<String, dynamic> _$LeaderboardEntryToJson(LeaderboardEntry instance) =>
Map<String, dynamic> _$LeaderboardEntryToJson(LeaderboardEntryData instance) =>
<String, dynamic>{
'playerInitials': instance.playerInitials,
'score': instance.score,

@ -2,17 +2,17 @@ import 'package:equatable/equatable.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
/// {@template leaderboard_ranking}
/// Contains [ranking] for a single [LeaderboardEntry] and the number of players
/// the [ranking] is [outOf].
/// Contains [ranking] for a single [LeaderboardEntryData] and the number of
/// players the [ranking] is [outOf].
/// {@endtemplate}
class LeaderboardRanking extends Equatable {
/// {@macro leaderboard_ranking}
const LeaderboardRanking({required this.ranking, required this.outOf});
/// Place ranking by score for a [LeaderboardEntry].
/// Place ranking by score for a [LeaderboardEntryData].
final int ranking;
/// Number of [LeaderboardEntry]s at the time of score entry.
/// Number of [LeaderboardEntryData]s at the time of score entry.
final int outOf;
@override

@ -1,2 +1,2 @@
export 'leaderboard_entry.dart';
export 'leaderboard_entry_data.dart';
export 'leaderboard_ranking.dart';

@ -57,7 +57,7 @@ void main() {
final top10Leaderboard = top10Scores
.map(
(score) => LeaderboardEntry(
(score) => LeaderboardEntryData(
playerInitials: 'user$score',
score: score,
character: CharacterType.dash,
@ -144,7 +144,7 @@ void main() {
entryScore,
1000,
];
final leaderboardEntry = LeaderboardEntry(
final leaderboardEntry = LeaderboardEntryData(
playerInitials: 'ABC',
score: entryScore,
character: CharacterType.dash,

@ -9,21 +9,21 @@ void main() {
'character': 'dash',
};
const leaderboardEntry = LeaderboardEntry(
const leaderboardEntry = LeaderboardEntryData(
playerInitials: 'ABC',
score: 1500,
character: CharacterType.dash,
);
test('can be instantiated', () {
const leaderboardEntry = LeaderboardEntry.empty;
const leaderboardEntry = LeaderboardEntryData.empty;
expect(leaderboardEntry, isNotNull);
});
test('supports value equality.', () {
const leaderboardEntry = LeaderboardEntry.empty;
const leaderboardEntry2 = LeaderboardEntry.empty;
const leaderboardEntry = LeaderboardEntryData.empty;
const leaderboardEntry2 = LeaderboardEntryData.empty;
expect(leaderboardEntry, equals(leaderboardEntry2));
});
@ -33,7 +33,7 @@ void main() {
});
test('can be obtained from json', () {
final leaderboardEntryFrom = LeaderboardEntry.fromJson(data);
final leaderboardEntryFrom = LeaderboardEntryData.fromJson(data);
expect(leaderboardEntry, equals(leaderboardEntryFrom));
});

@ -2,6 +2,7 @@ import 'package:flame/input.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/theme/theme.dart';
@ -32,6 +33,8 @@ class MockGameState extends Mock implements GameState {}
class MockThemeCubit extends Mock implements ThemeCubit {}
class MockLeaderboardRepository extends Mock implements LeaderboardRepository {}
class MockRawKeyDownEvent extends Mock implements RawKeyDownEvent {
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {

@ -5,8 +5,9 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/leaderboard/leaderboard.dart';
import 'package:pinball_theme/pinball_theme.dart';
class MockLeaderboardRepository extends Mock implements LeaderboardRepository {}
import '../../helpers/helpers.dart';
void main() {
group('LeaderboardBloc', () {
@ -42,7 +43,7 @@ void main() {
final top10Leaderboard = top10Scores
.map(
(score) => LeaderboardEntry(
(score) => LeaderboardEntryData(
playerInitials: 'user$score',
score: score,
character: CharacterType.dash,
@ -101,7 +102,7 @@ void main() {
});
group('LeaderboardEntryAdded', () {
final leaderboardEntry = LeaderboardEntry(
final leaderboardEntry = LeaderboardEntryData(
playerInitials: 'ABC',
score: 1500,
character: CharacterType.dash,
@ -163,4 +164,40 @@ void main() {
);
});
});
group('CharacterTypeX', () {
test('converts CharacterType.android to AndroidTheme', () {
expect(CharacterType.android.toTheme, equals(AndroidTheme()));
});
test('converts CharacterType.dash to DashTheme', () {
expect(CharacterType.dash.toTheme, equals(DashTheme()));
});
test('converts CharacterType.dino to DinoTheme', () {
expect(CharacterType.dino.toTheme, equals(DinoTheme()));
});
test('converts CharacterType.sparky to SparkyTheme', () {
expect(CharacterType.sparky.toTheme, equals(SparkyTheme()));
});
});
group('CharacterThemeX', () {
test('converts AndroidTheme to CharacterType.android', () {
expect(AndroidTheme().toType, equals(CharacterType.android));
});
test('converts DashTheme to CharacterType.dash', () {
expect(DashTheme().toType, equals(CharacterType.dash));
});
test('converts DinoTheme to CharacterType.dino', () {
expect(DinoTheme().toType, equals(CharacterType.dino));
});
test('converts SparkyTheme to CharacterType.sparky', () {
expect(SparkyTheme().toType, equals(CharacterType.sparky));
});
});
}

@ -20,7 +20,7 @@ void main() {
});
group('LeaderboardEntryAdded', () {
const leaderboardEntry = LeaderboardEntry(
const leaderboardEntry = LeaderboardEntryData(
playerInitials: 'ABC',
score: 1500,
character: CharacterType.dash,

@ -3,6 +3,7 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball/leaderboard/leaderboard.dart';
import 'package:pinball_theme/pinball_theme.dart';
void main() {
group('LeaderboardState', () {
@ -25,10 +26,11 @@ void main() {
});
group('copyWith', () {
const leaderboardEntry = LeaderboardEntry(
final leaderboardEntry = LeaderboardEntry(
rank: '1',
playerInitials: 'ABC',
score: 1500,
character: CharacterType.dash,
character: DashTheme().characterAsset,
);
test(
@ -51,7 +53,7 @@ void main() {
final otherLeaderboardState = LeaderboardState(
status: LeaderboardStatus.success,
ranking: LeaderboardRanking(ranking: 0, outOf: 0),
leaderboard: const [leaderboardEntry],
leaderboard: [leaderboardEntry],
);
expect(leaderboardState, isNot(equals(otherLeaderboardState)));

Loading…
Cancel
Save