diff --git a/lib/leaderboard/bloc/leaderboard_bloc.dart b/lib/leaderboard/bloc/leaderboard_bloc.dart index 6542548d..49a35474 100644 --- a/lib/leaderboard/bloc/leaderboard_bloc.dart +++ b/lib/leaderboard/bloc/leaderboard_bloc.dart @@ -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 { try { final top10Leaderboard = await _leaderboardRepository.fetchTop10Leaderboard(); + + final leaderboardEntries = []; + top10Leaderboard.asMap().forEach( + (index, value) => leaderboardEntries.add(value.toEntry(index + 1)), + ); + emit( state.copyWith( status: LeaderboardStatus.success, - leaderboard: top10Leaderboard, + leaderboard: leaderboardEntries, ), ); } catch (error) { diff --git a/lib/leaderboard/bloc/leaderboard_event.dart b/lib/leaderboard/bloc/leaderboard_event.dart index 34152163..b9e6955a 100644 --- a/lib/leaderboard/bloc/leaderboard_event.dart +++ b/lib/leaderboard/bloc/leaderboard_event.dart @@ -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 get props => [entry]; diff --git a/lib/leaderboard/leaderboard.dart b/lib/leaderboard/leaderboard.dart index 13d71e40..156b7f78 100644 --- a/lib/leaderboard/leaderboard.dart +++ b/lib/leaderboard/leaderboard.dart @@ -1 +1,2 @@ export 'bloc/leaderboard_bloc.dart'; +export 'models/leader_board_entry.dart'; diff --git a/lib/leaderboard/models/leader_board_entry.dart b/lib/leaderboard/models/leader_board_entry.dart new file mode 100644 index 00000000..194f7cb6 --- /dev/null +++ b/lib/leaderboard/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; + } + } +} diff --git a/packages/leaderboard_repository/lib/src/leaderboard_repository.dart b/packages/leaderboard_repository/lib/src/leaderboard_repository.dart index 5a5fa42c..d75a88b3 100644 --- a/packages/leaderboard_repository/lib/src/leaderboard_repository.dart +++ b/packages/leaderboard_repository/lib/src/leaderboard_repository.dart @@ -83,9 +83,9 @@ class LeaderboardRepository { final FirebaseFirestore _firebaseFirestore; - /// Acquires top 10 [LeaderboardEntry]s. - Future> fetchTop10Leaderboard() async { - final leaderboardEntries = []; + /// Acquires top 10 [LeaderboardEntryData]s. + Future> fetchTop10Leaderboard() async { + final leaderboardEntries = []; late List documents; try { @@ -103,7 +103,7 @@ class LeaderboardRepository { final data = document.data() as Map?; 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 addLeaderboardEntry(LeaderboardEntry entry) async { + Future addLeaderboardEntry( + LeaderboardEntryData entry, + ) async { late DocumentReference entryReference; try { entryReference = await _firebaseFirestore diff --git a/packages/leaderboard_repository/lib/src/models/leaderboard_entry.dart b/packages/leaderboard_repository/lib/src/models/leaderboard_entry_data.dart similarity index 64% rename from packages/leaderboard_repository/lib/src/models/leaderboard_entry.dart rename to packages/leaderboard_repository/lib/src/models/leaderboard_entry_data.dart index 86cb2464..c8137520 100644 --- a/packages/leaderboard_repository/lib/src/models/leaderboard_entry.dart +++ b/packages/leaderboard_repository/lib/src/models/leaderboard_entry_data.dart @@ -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 json) { + /// Factory which converts a [Map] into a [LeaderboardEntryData]. + factory LeaderboardEntryData.fromJson(Map json) { return _$LeaderboardEntryFromJson(json); } - /// Converts the [LeaderboardEntry] to [Map]. + /// Converts the [LeaderboardEntryData] to [Map]. Map 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, diff --git a/packages/leaderboard_repository/lib/src/models/leaderboard_entry.g.dart b/packages/leaderboard_repository/lib/src/models/leaderboard_entry_data.g.dart similarity index 76% rename from packages/leaderboard_repository/lib/src/models/leaderboard_entry.g.dart rename to packages/leaderboard_repository/lib/src/models/leaderboard_entry_data.g.dart index fc685220..e57e43b8 100644 --- a/packages/leaderboard_repository/lib/src/models/leaderboard_entry.g.dart +++ b/packages/leaderboard_repository/lib/src/models/leaderboard_entry_data.g.dart @@ -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 json) => - LeaderboardEntry( +LeaderboardEntryData _$LeaderboardEntryFromJson(Map json) => + LeaderboardEntryData( playerInitials: json['playerInitials'] as String, score: json['score'] as int, character: $enumDecode(_$CharacterTypeEnumMap, json['character']), ); -Map _$LeaderboardEntryToJson(LeaderboardEntry instance) => +Map _$LeaderboardEntryToJson(LeaderboardEntryData instance) => { 'playerInitials': instance.playerInitials, 'score': instance.score, diff --git a/packages/leaderboard_repository/lib/src/models/leaderboard_ranking.dart b/packages/leaderboard_repository/lib/src/models/leaderboard_ranking.dart index 7ec90ef4..4a322e00 100644 --- a/packages/leaderboard_repository/lib/src/models/leaderboard_ranking.dart +++ b/packages/leaderboard_repository/lib/src/models/leaderboard_ranking.dart @@ -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 diff --git a/packages/leaderboard_repository/lib/src/models/models.dart b/packages/leaderboard_repository/lib/src/models/models.dart index 3dabe2bf..e10a743b 100644 --- a/packages/leaderboard_repository/lib/src/models/models.dart +++ b/packages/leaderboard_repository/lib/src/models/models.dart @@ -1,2 +1,2 @@ -export 'leaderboard_entry.dart'; +export 'leaderboard_entry_data.dart'; export 'leaderboard_ranking.dart'; diff --git a/packages/leaderboard_repository/test/src/leaderboard_repository_test.dart b/packages/leaderboard_repository/test/src/leaderboard_repository_test.dart index cd632638..592425ec 100644 --- a/packages/leaderboard_repository/test/src/leaderboard_repository_test.dart +++ b/packages/leaderboard_repository/test/src/leaderboard_repository_test.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, diff --git a/packages/leaderboard_repository/test/src/models/leaderboard_entry_test.dart b/packages/leaderboard_repository/test/src/models/leaderboard_entry_data_test.dart similarity index 72% rename from packages/leaderboard_repository/test/src/models/leaderboard_entry_test.dart rename to packages/leaderboard_repository/test/src/models/leaderboard_entry_data_test.dart index 21056529..f6e27e8a 100644 --- a/packages/leaderboard_repository/test/src/models/leaderboard_entry_test.dart +++ b/packages/leaderboard_repository/test/src/models/leaderboard_entry_data_test.dart @@ -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)); }); diff --git a/test/helpers/mocks.dart b/test/helpers/mocks.dart index 10541caa..a15953ff 100644 --- a/test/helpers/mocks.dart +++ b/test/helpers/mocks.dart @@ -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}) { diff --git a/test/leaderboard/bloc/leaderboard_bloc_test.dart b/test/leaderboard/bloc/leaderboard_bloc_test.dart index c44f7d3a..2b217704 100644 --- a/test/leaderboard/bloc/leaderboard_bloc_test.dart +++ b/test/leaderboard/bloc/leaderboard_bloc_test.dart @@ -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)); + }); + }); } diff --git a/test/leaderboard/bloc/leaderboard_event_test.dart b/test/leaderboard/bloc/leaderboard_event_test.dart index f74296af..33199ca1 100644 --- a/test/leaderboard/bloc/leaderboard_event_test.dart +++ b/test/leaderboard/bloc/leaderboard_event_test.dart @@ -20,7 +20,7 @@ void main() { }); group('LeaderboardEntryAdded', () { - const leaderboardEntry = LeaderboardEntry( + const leaderboardEntry = LeaderboardEntryData( playerInitials: 'ABC', score: 1500, character: CharacterType.dash, diff --git a/test/leaderboard/bloc/leaderboard_state_test.dart b/test/leaderboard/bloc/leaderboard_state_test.dart index 6ff5df13..a40a1cdb 100644 --- a/test/leaderboard/bloc/leaderboard_state_test.dart +++ b/test/leaderboard/bloc/leaderboard_state_test.dart @@ -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)));