diff --git a/README.md b/README.md
index d34d65db..1748e48a 100644
--- a/README.md
+++ b/README.md
@@ -21,9 +21,13 @@ Welcome to the official **Board Games Companion** Github repository - an open so
For more information about the application and its features please visit our [Wiki pages](https://github.com/Progrunning/BoardGamesCompanion/wiki).
+# Discussion
+
+If you have an idea about how the app could be improved, you ran into a problem or maybe you want to check what's currently in the works then join the [Discord](https://discord.gg/t9dTVXxnvC) server to chat with the development team and other BGC users.
+
# Contributing
-The mobile applications (iOS and Android) are written in [Dart language](https://dart.dev/) and using [Flutter cross-platform framework](https://flutter.dev/). This means that in order to contribute to the project one needs to have working Flutter environemnt on their machines.
+The iOS and Android mobile applications are written in [Dart language](https://dart.dev/), using a cross-platform framework [Flutter](https://flutter.dev/). In order to contribute to the project one needs to have working Flutter environemnt on their machines.
## Getting Started
diff --git a/board_games_companion/assets/discord-logo-blue.svg b/board_games_companion/assets/discord-logo-blue.svg
new file mode 100644
index 00000000..1f483fe8
--- /dev/null
+++ b/board_games_companion/assets/discord-logo-blue.svg
@@ -0,0 +1,23 @@
+
diff --git a/board_games_companion/lib/common/app_text.dart b/board_games_companion/lib/common/app_text.dart
index 5e7d2eb0..986d646f 100644
--- a/board_games_companion/lib/common/app_text.dart
+++ b/board_games_companion/lib/common/app_text.dart
@@ -19,6 +19,7 @@ class AppText {
static const aboutPageAuthorSectionTitle = 'Author';
static const aboutPageDesignAndArtSectionTitle = 'Design & Art';
static const aboutPageContentAndDataSectionTitle = 'Content & Data';
+ static const aboutPageCommunityTitle = 'Community';
static const aboutPagePluginsAndLibrariesSectionTitle = 'Plugins & Libraries';
static const aboutPageLicensesSectionTitle = 'Licenses';
static const aboutPageContentAndDataBggXmlApiTitle =
@@ -26,12 +27,16 @@ class AppText {
static const aboutPageContentAndDataBggXmlApiSubtitle = 'See below links for more details:';
static const aboutPagePluginsAndLibrariesSubtitle =
'The below is a list of the plugins and libraries that helped in building this app:';
+ static const aboutPageCommunitySubtitle =
+ "If you need help with the app, have an idea for a new feature or you want to see what we are currently working on then then feel free to join BGC's discord server to chat about it with developers and other app users.";
+ static const aboutPageCommunityJoinDiscord =
+ "Tap on the below logo to join the BGC's Discord server.";
static const playthroughsStatisticsPageLastWinnerSectionTitle = 'Last winner';
- static const playthroughsStatisticsPageTopFiveSectionTitle = 'Top 5';
- static const playthroughsStatisticsPagePlayerCountPercentageSectionTitle =
- 'Games played by player count';
- static const playthroughsStatisticsPagePlayerWinsPercentageSectionTitle = 'Games won by a player';
+ static const playthroughsStatisticsPageTopFiveSectionTitle = 'Top 5 scores';
+ static const playthroughsStatisticsPageGamesPlayedAndWonChartsSectionPrimaryTitle =
+ 'Games played';
+ static const playthroughsStatisticsPageGamesPlayedAndWonChartsSectionSecondaryTitle = 'Games won';
static const playthroughsStatisticsPageOverallStatsSectionTitle = 'Overall stats';
static const playthroughsStatisticsPageOverallStatsAvgPlaytime = 'Avg. playtime';
static const playthroughsStatisticsPageOverallStatsTotalPlaytime = 'Total playtime';
@@ -45,7 +50,8 @@ class AppText {
playthroughsStatisticsPageOverallStatsAvgScore;
static const playthroughsStatisticsPagePlayersStatsPlayedGames =
playthroughsStatisticsPageOverallStatsAvgPlayedGames;
- static const playthroughsStatisticsPagePlayerCountChartLegendFormat = '%s player%s';
+ static const playthroughsStatisticsPagePlayerCountChartLegendFormatSingular = '%i player';
+ static const playthroughsStatisticsPagePlayerCountChartLegendFormatPlural = '%i players';
static const playthroughsGameSettingsWinningConditionSectionTitle = 'Winning Condition';
static const playthroughsGameSettingsWinningConditionHighestScore = 'Highest score';
@@ -69,6 +75,7 @@ class AppText {
static const filterGamesPanelClearFiltersButtonText = 'Clear filters';
+ static const playthroughsLogGamePageHeader = 'Log a game';
static const playthroughsLogGamePagePlayerScoresStepTitle = 'Player scores';
static const importCollectionsSucceeded = 'Your collection has been imported from BGG!';
diff --git a/board_games_companion/lib/common/dimensions.dart b/board_games_companion/lib/common/dimensions.dart
index 4339ca57..7fce525e 100644
--- a/board_games_companion/lib/common/dimensions.dart
+++ b/board_games_companion/lib/common/dimensions.dart
@@ -55,6 +55,8 @@ class Dimensions {
static const double bottomTabTopHeight = 20;
+ static const double detailsItemHeight = 60;
+
static const EdgeInsets snackbarMargin = EdgeInsets.only(
left: Dimensions.standardSpacing,
right: Dimensions.standardSpacing,
diff --git a/board_games_companion/lib/models/board_game_statistics.dart b/board_games_companion/lib/models/board_game_statistics.dart
index 64de91c2..84f9df0d 100644
--- a/board_games_companion/lib/models/board_game_statistics.dart
+++ b/board_games_companion/lib/models/board_game_statistics.dart
@@ -1,9 +1,12 @@
+import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:tuple/tuple.dart';
import 'hive/player.dart';
import 'player_score.dart';
import 'player_statistics.dart';
+part 'board_game_statistics.freezed.dart';
+
class BoardGameStatistics {
DateTime? lastPlayed;
@@ -31,9 +34,27 @@ class BoardGameStatistics {
List? playersStatistics;
- Map? playerCountPercentage;
+ List? playerCountPercentage;
- Map? playerWinsPercentage;
+ List? playerWinsPercentage;
Map? playerWins;
}
+
+@freezed
+abstract class PlayerWinsStatistics with _$PlayerWinsStatistics {
+ const factory PlayerWinsStatistics({
+ required Player player,
+ required int numberOfWins,
+ required double winsPercentage,
+ }) = _PlayerWinsStatistics;
+}
+
+@freezed
+abstract class PlayerCountStatistics with _$PlayerCountStatistics {
+ const factory PlayerCountStatistics({
+ required int numberOfPlayers,
+ required int numberOfGamesPlayed,
+ required double gamesPlayedPercentage,
+ }) = _PlayerCountStatistics;
+}
diff --git a/board_games_companion/lib/models/board_game_statistics.freezed.dart b/board_games_companion/lib/models/board_game_statistics.freezed.dart
new file mode 100644
index 00000000..8ace6445
--- /dev/null
+++ b/board_games_companion/lib/models/board_game_statistics.freezed.dart
@@ -0,0 +1,341 @@
+// coverage:ignore-file
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// ignore_for_file: type=lint
+// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
+
+part of 'board_game_statistics.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+T _$identity(T value) => value;
+
+final _privateConstructorUsedError = UnsupportedError(
+ 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
+
+/// @nodoc
+mixin _$PlayerWinsStatistics {
+ Player get player => throw _privateConstructorUsedError;
+ int get numberOfWins => throw _privateConstructorUsedError;
+ double get winsPercentage => throw _privateConstructorUsedError;
+
+ @JsonKey(ignore: true)
+ $PlayerWinsStatisticsCopyWith get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $PlayerWinsStatisticsCopyWith<$Res> {
+ factory $PlayerWinsStatisticsCopyWith(PlayerWinsStatistics value,
+ $Res Function(PlayerWinsStatistics) then) =
+ _$PlayerWinsStatisticsCopyWithImpl<$Res>;
+ $Res call({Player player, int numberOfWins, double winsPercentage});
+}
+
+/// @nodoc
+class _$PlayerWinsStatisticsCopyWithImpl<$Res>
+ implements $PlayerWinsStatisticsCopyWith<$Res> {
+ _$PlayerWinsStatisticsCopyWithImpl(this._value, this._then);
+
+ final PlayerWinsStatistics _value;
+ // ignore: unused_field
+ final $Res Function(PlayerWinsStatistics) _then;
+
+ @override
+ $Res call({
+ Object? player = freezed,
+ Object? numberOfWins = freezed,
+ Object? winsPercentage = freezed,
+ }) {
+ return _then(_value.copyWith(
+ player: player == freezed
+ ? _value.player
+ : player // ignore: cast_nullable_to_non_nullable
+ as Player,
+ numberOfWins: numberOfWins == freezed
+ ? _value.numberOfWins
+ : numberOfWins // ignore: cast_nullable_to_non_nullable
+ as int,
+ winsPercentage: winsPercentage == freezed
+ ? _value.winsPercentage
+ : winsPercentage // ignore: cast_nullable_to_non_nullable
+ as double,
+ ));
+ }
+}
+
+/// @nodoc
+abstract class _$$_PlayerWinsStatisticsCopyWith<$Res>
+ implements $PlayerWinsStatisticsCopyWith<$Res> {
+ factory _$$_PlayerWinsStatisticsCopyWith(_$_PlayerWinsStatistics value,
+ $Res Function(_$_PlayerWinsStatistics) then) =
+ __$$_PlayerWinsStatisticsCopyWithImpl<$Res>;
+ @override
+ $Res call({Player player, int numberOfWins, double winsPercentage});
+}
+
+/// @nodoc
+class __$$_PlayerWinsStatisticsCopyWithImpl<$Res>
+ extends _$PlayerWinsStatisticsCopyWithImpl<$Res>
+ implements _$$_PlayerWinsStatisticsCopyWith<$Res> {
+ __$$_PlayerWinsStatisticsCopyWithImpl(_$_PlayerWinsStatistics _value,
+ $Res Function(_$_PlayerWinsStatistics) _then)
+ : super(_value, (v) => _then(v as _$_PlayerWinsStatistics));
+
+ @override
+ _$_PlayerWinsStatistics get _value => super._value as _$_PlayerWinsStatistics;
+
+ @override
+ $Res call({
+ Object? player = freezed,
+ Object? numberOfWins = freezed,
+ Object? winsPercentage = freezed,
+ }) {
+ return _then(_$_PlayerWinsStatistics(
+ player: player == freezed
+ ? _value.player
+ : player // ignore: cast_nullable_to_non_nullable
+ as Player,
+ numberOfWins: numberOfWins == freezed
+ ? _value.numberOfWins
+ : numberOfWins // ignore: cast_nullable_to_non_nullable
+ as int,
+ winsPercentage: winsPercentage == freezed
+ ? _value.winsPercentage
+ : winsPercentage // ignore: cast_nullable_to_non_nullable
+ as double,
+ ));
+ }
+}
+
+/// @nodoc
+
+class _$_PlayerWinsStatistics implements _PlayerWinsStatistics {
+ const _$_PlayerWinsStatistics(
+ {required this.player,
+ required this.numberOfWins,
+ required this.winsPercentage});
+
+ @override
+ final Player player;
+ @override
+ final int numberOfWins;
+ @override
+ final double winsPercentage;
+
+ @override
+ String toString() {
+ return 'PlayerWinsStatistics(player: $player, numberOfWins: $numberOfWins, winsPercentage: $winsPercentage)';
+ }
+
+ @override
+ bool operator ==(dynamic other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is _$_PlayerWinsStatistics &&
+ const DeepCollectionEquality().equals(other.player, player) &&
+ const DeepCollectionEquality()
+ .equals(other.numberOfWins, numberOfWins) &&
+ const DeepCollectionEquality()
+ .equals(other.winsPercentage, winsPercentage));
+ }
+
+ @override
+ int get hashCode => Object.hash(
+ runtimeType,
+ const DeepCollectionEquality().hash(player),
+ const DeepCollectionEquality().hash(numberOfWins),
+ const DeepCollectionEquality().hash(winsPercentage));
+
+ @JsonKey(ignore: true)
+ @override
+ _$$_PlayerWinsStatisticsCopyWith<_$_PlayerWinsStatistics> get copyWith =>
+ __$$_PlayerWinsStatisticsCopyWithImpl<_$_PlayerWinsStatistics>(
+ this, _$identity);
+}
+
+abstract class _PlayerWinsStatistics implements PlayerWinsStatistics {
+ const factory _PlayerWinsStatistics(
+ {required final Player player,
+ required final int numberOfWins,
+ required final double winsPercentage}) = _$_PlayerWinsStatistics;
+
+ @override
+ Player get player;
+ @override
+ int get numberOfWins;
+ @override
+ double get winsPercentage;
+ @override
+ @JsonKey(ignore: true)
+ _$$_PlayerWinsStatisticsCopyWith<_$_PlayerWinsStatistics> get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+mixin _$PlayerCountStatistics {
+ int get numberOfPlayers => throw _privateConstructorUsedError;
+ int get numberOfGamesPlayed => throw _privateConstructorUsedError;
+ double get gamesPlayedPercentage => throw _privateConstructorUsedError;
+
+ @JsonKey(ignore: true)
+ $PlayerCountStatisticsCopyWith get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $PlayerCountStatisticsCopyWith<$Res> {
+ factory $PlayerCountStatisticsCopyWith(PlayerCountStatistics value,
+ $Res Function(PlayerCountStatistics) then) =
+ _$PlayerCountStatisticsCopyWithImpl<$Res>;
+ $Res call(
+ {int numberOfPlayers,
+ int numberOfGamesPlayed,
+ double gamesPlayedPercentage});
+}
+
+/// @nodoc
+class _$PlayerCountStatisticsCopyWithImpl<$Res>
+ implements $PlayerCountStatisticsCopyWith<$Res> {
+ _$PlayerCountStatisticsCopyWithImpl(this._value, this._then);
+
+ final PlayerCountStatistics _value;
+ // ignore: unused_field
+ final $Res Function(PlayerCountStatistics) _then;
+
+ @override
+ $Res call({
+ Object? numberOfPlayers = freezed,
+ Object? numberOfGamesPlayed = freezed,
+ Object? gamesPlayedPercentage = freezed,
+ }) {
+ return _then(_value.copyWith(
+ numberOfPlayers: numberOfPlayers == freezed
+ ? _value.numberOfPlayers
+ : numberOfPlayers // ignore: cast_nullable_to_non_nullable
+ as int,
+ numberOfGamesPlayed: numberOfGamesPlayed == freezed
+ ? _value.numberOfGamesPlayed
+ : numberOfGamesPlayed // ignore: cast_nullable_to_non_nullable
+ as int,
+ gamesPlayedPercentage: gamesPlayedPercentage == freezed
+ ? _value.gamesPlayedPercentage
+ : gamesPlayedPercentage // ignore: cast_nullable_to_non_nullable
+ as double,
+ ));
+ }
+}
+
+/// @nodoc
+abstract class _$$_PlayerCountStatisticsCopyWith<$Res>
+ implements $PlayerCountStatisticsCopyWith<$Res> {
+ factory _$$_PlayerCountStatisticsCopyWith(_$_PlayerCountStatistics value,
+ $Res Function(_$_PlayerCountStatistics) then) =
+ __$$_PlayerCountStatisticsCopyWithImpl<$Res>;
+ @override
+ $Res call(
+ {int numberOfPlayers,
+ int numberOfGamesPlayed,
+ double gamesPlayedPercentage});
+}
+
+/// @nodoc
+class __$$_PlayerCountStatisticsCopyWithImpl<$Res>
+ extends _$PlayerCountStatisticsCopyWithImpl<$Res>
+ implements _$$_PlayerCountStatisticsCopyWith<$Res> {
+ __$$_PlayerCountStatisticsCopyWithImpl(_$_PlayerCountStatistics _value,
+ $Res Function(_$_PlayerCountStatistics) _then)
+ : super(_value, (v) => _then(v as _$_PlayerCountStatistics));
+
+ @override
+ _$_PlayerCountStatistics get _value =>
+ super._value as _$_PlayerCountStatistics;
+
+ @override
+ $Res call({
+ Object? numberOfPlayers = freezed,
+ Object? numberOfGamesPlayed = freezed,
+ Object? gamesPlayedPercentage = freezed,
+ }) {
+ return _then(_$_PlayerCountStatistics(
+ numberOfPlayers: numberOfPlayers == freezed
+ ? _value.numberOfPlayers
+ : numberOfPlayers // ignore: cast_nullable_to_non_nullable
+ as int,
+ numberOfGamesPlayed: numberOfGamesPlayed == freezed
+ ? _value.numberOfGamesPlayed
+ : numberOfGamesPlayed // ignore: cast_nullable_to_non_nullable
+ as int,
+ gamesPlayedPercentage: gamesPlayedPercentage == freezed
+ ? _value.gamesPlayedPercentage
+ : gamesPlayedPercentage // ignore: cast_nullable_to_non_nullable
+ as double,
+ ));
+ }
+}
+
+/// @nodoc
+
+class _$_PlayerCountStatistics implements _PlayerCountStatistics {
+ const _$_PlayerCountStatistics(
+ {required this.numberOfPlayers,
+ required this.numberOfGamesPlayed,
+ required this.gamesPlayedPercentage});
+
+ @override
+ final int numberOfPlayers;
+ @override
+ final int numberOfGamesPlayed;
+ @override
+ final double gamesPlayedPercentage;
+
+ @override
+ String toString() {
+ return 'PlayerCountStatistics(numberOfPlayers: $numberOfPlayers, numberOfGamesPlayed: $numberOfGamesPlayed, gamesPlayedPercentage: $gamesPlayedPercentage)';
+ }
+
+ @override
+ bool operator ==(dynamic other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is _$_PlayerCountStatistics &&
+ const DeepCollectionEquality()
+ .equals(other.numberOfPlayers, numberOfPlayers) &&
+ const DeepCollectionEquality()
+ .equals(other.numberOfGamesPlayed, numberOfGamesPlayed) &&
+ const DeepCollectionEquality()
+ .equals(other.gamesPlayedPercentage, gamesPlayedPercentage));
+ }
+
+ @override
+ int get hashCode => Object.hash(
+ runtimeType,
+ const DeepCollectionEquality().hash(numberOfPlayers),
+ const DeepCollectionEquality().hash(numberOfGamesPlayed),
+ const DeepCollectionEquality().hash(gamesPlayedPercentage));
+
+ @JsonKey(ignore: true)
+ @override
+ _$$_PlayerCountStatisticsCopyWith<_$_PlayerCountStatistics> get copyWith =>
+ __$$_PlayerCountStatisticsCopyWithImpl<_$_PlayerCountStatistics>(
+ this, _$identity);
+}
+
+abstract class _PlayerCountStatistics implements PlayerCountStatistics {
+ const factory _PlayerCountStatistics(
+ {required final int numberOfPlayers,
+ required final int numberOfGamesPlayed,
+ required final double gamesPlayedPercentage}) = _$_PlayerCountStatistics;
+
+ @override
+ int get numberOfPlayers;
+ @override
+ int get numberOfGamesPlayed;
+ @override
+ double get gamesPlayedPercentage;
+ @override
+ @JsonKey(ignore: true)
+ _$$_PlayerCountStatisticsCopyWith<_$_PlayerCountStatistics> get copyWith =>
+ throw _privateConstructorUsedError;
+}
diff --git a/board_games_companion/lib/pages/about/about_page.dart b/board_games_companion/lib/pages/about/about_page.dart
index 92553986..6cce6c38 100644
--- a/board_games_companion/lib/pages/about/about_page.dart
+++ b/board_games_companion/lib/pages/about/about_page.dart
@@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
+import 'package:flutter_svg/svg.dart';
import 'package:package_info/package_info.dart';
+import 'package:url_launcher/url_launcher.dart';
import '../../common/app_colors.dart';
import '../../common/app_text.dart';
@@ -45,7 +47,7 @@ class AboutPageState extends BasePageState {
slivers: [
SliverPersistentHeader(
delegate: BgcSliverHeaderDelegate(
- title: AppText.aboutPageAuthorSectionTitle,
+ primaryTitle: AppText.aboutPageAuthorSectionTitle,
),
),
const SliverToBoxAdapter(
@@ -53,12 +55,12 @@ class AboutPageState extends BasePageState {
title: 'Mikolaj Kieres',
subtitle: Constants.feedbackEmailAddress,
uri: 'mailto:${Constants.feedbackEmailAddress}?subject=BGC%20Feedback',
- iconUri: 'assets/mikolaj_profile_picture.jpg',
+ assetIconUri: 'assets/mikolaj_profile_picture.jpg',
),
),
SliverPersistentHeader(
delegate: BgcSliverHeaderDelegate(
- title: AppText.aboutPageDesignAndArtSectionTitle,
+ primaryTitle: AppText.aboutPageDesignAndArtSectionTitle,
),
),
const SliverToBoxAdapter(
@@ -66,141 +68,34 @@ class AboutPageState extends BasePageState {
title: 'Alicja Adamkiewicz',
subtitle: 'instagram.com/adamkiewicz_art',
uri: 'https://www.instagram.com/adamkiewicz_art',
- iconUri: 'assets/adamkiewiczart_logo.png',
+ assetIconUri: 'assets/adamkiewiczart_logo.png',
),
),
SliverPersistentHeader(
delegate: BgcSliverHeaderDelegate(
- title: AppText.aboutPageContentAndDataSectionTitle,
+ primaryTitle: AppText.aboutPageCommunityTitle,
+ ),
+ ),
+ const _CommunitySection(),
+ SliverPersistentHeader(
+ delegate: BgcSliverHeaderDelegate(
+ primaryTitle: AppText.aboutPageContentAndDataSectionTitle,
),
),
const _ContentAndDataSection(),
SliverPersistentHeader(
delegate: BgcSliverHeaderDelegate(
- title: AppText.aboutPagePluginsAndLibrariesSectionTitle,
+ primaryTitle: AppText.aboutPagePluginsAndLibrariesSectionTitle,
),
),
const _PluginsAndLibrariesSection(),
SliverPersistentHeader(
delegate: BgcSliverHeaderDelegate(
- title: AppText.aboutPageLicensesSectionTitle,
+ primaryTitle: AppText.aboutPageLicensesSectionTitle,
),
),
const _LicensePageDetailsItem(),
],
- // Column(
- // mainAxisSize: MainAxisSize.min,
- // crossAxisAlignment: CrossAxisAlignment.stretch,
- // children: const [
- // SectionTitle(title: 'Author'),
- // DetailsItem(
- // title: 'Mikolaj Kieres',
- // subtitle: Constants.feedbackEmailAddress,
- // uri: 'mailto:${Constants.feedbackEmailAddress}?subject=BGC%20Feedback',
- // iconUri: 'assets/mikolaj_profile_picture.jpg',
- // ),
- // Divider(color: AppColors.accentColor),
- // SectionTitle(title: 'Design & Art'),
- // DetailsItem(
- // title: 'Alicja Adamkiewicz',
- // subtitle: 'instagram.com/adamkiewicz_art',
- // uri: 'https://www.instagram.com/adamkiewicz_art',
- // iconUri: 'assets/adamkiewiczart_logo.png',
- // ),
- // Divider(color: AppColors.accentColor),
- // SectionTitle(title: 'Content & Data'),
- // SectionText(
- // text:
- // "The board games data shown in the app is a courtesy of the publicly available BoardGameGeek's XML API.",
- // ),
- // SectionText(text: 'See below links for more details:'),
- // DetailsItem(
- // title: 'BGG',
- // subtitle: 'boardgamegeek.com',
- // uri: Constants.boardGameGeekBaseApiUrl),
- // DetailsItem(
- // title: 'XML API',
- // subtitle: 'boardgamegeek.com/wiki/page/BGG_XML_API2',
- // uri: 'https://boardgamegeek.com/wiki/page/BGG_XML_API2'),
- // DetailsItem(
- // title: 'Terms of Service',
- // subtitle: 'boardgamegeek.com/terms',
- // uri: 'https://www.boardgamegeek.com/terms'),
- // Divider(color: AppColors.accentColor),
- // SectionTitle(title: 'Plugins & Libraries'),
- // SectionText(
- // text:
- // 'The below is a list of the plugins and libraries that helped in building this app:',
- // ),
- // DetailsItem(
- // title: 'Lato Font',
- // subtitle: 'Powered by Google Fonts',
- // uri: 'https://pub.dev/packages/google_fonts'),
- // DetailsItem(
- // title: 'Logging',
- // subtitle: 'Handles in app logs',
- // uri: 'https://pub.dev/packages/logging'),
- // DetailsItem(
- // title: 'Dio Http Cache',
- // subtitle: 'SQLite like cache of http responses',
- // uri: 'https://pub.dev/packages/dio_http_cache'),
- // DetailsItem(
- // title: 'Dio Http2 Adapter',
- // subtitle: 'Provides the ability to create custom http adapters',
- // uri: 'https://pub.dev/packages/dio_http2_adapter'),
- // DetailsItem(
- // title: 'Cached Network Image',
- // subtitle: 'Caching network images',
- // uri: 'https://pub.dev/packages/cached_network_image'),
- // DetailsItem(
- // title: 'Firebase Crashlytics',
- // subtitle: 'Captures app crash analytics',
- // uri: 'https://pub.dev/packages/firebase_crashlytics'),
- // DetailsItem(
- // title: 'Hive',
- // subtitle: 'NoSQL Database',
- // uri: 'https://pub.dev/packages/hive'),
- // DetailsItem(
- // title: 'Path Provider',
- // subtitle: 'Helps with filesystem paths',
- // uri: 'https://pub.dev/packages/path_provider'),
- // DetailsItem(
- // title: 'Polygon Clipper',
- // subtitle: 'Draws polygon shapes',
- // uri: 'https://pub.dev/packages/polygon_clipper'),
- // DetailsItem(
- // title: 'Carousel Slider',
- // subtitle: 'Helps with carousels',
- // uri: 'https://pub.dev/packages/carousel_slider'),
- // DetailsItem(
- // title: 'Image Picker',
- // subtitle: 'Picking images and taking photos with camera',
- // uri: 'https://pub.dev/packages/image_picker'),
- // DetailsItem(
- // title: 'Image Picker',
- // subtitle: 'Picking images and taking photos with camera',
- // uri: 'https://pub.dev/packages/image_picker'),
- // DetailsItem(
- // title: 'XML',
- // subtitle: 'Parsing XML API responses',
- // uri: 'https://pub.dev/packages/xml'),
- // DetailsItem(
- // title: 'Provider',
- // subtitle: 'DI & state management',
- // uri: 'https://pub.dev/packages/provider'),
- // DetailsItem(
- // title: 'Animations',
- // subtitle: 'Navigation animations',
- // uri: 'https://pub.dev/packages/animations'),
- // DetailsItem(
- // title: 'Url Launcher',
- // subtitle: "Launching Uri's",
- // uri: 'https://pub.dev/packages/url_launcher'),
- // Divider(color: AppColors.accentColor),
- // SectionTitle(title: 'Licenses'),
- // _LicensePageDetailsItem(),
- // ],
- // ),
),
),
const _PrivacyPolicyFooter(),
@@ -213,6 +108,45 @@ class AboutPageState extends BasePageState {
}
}
+class _CommunitySection extends StatelessWidget {
+ const _CommunitySection({
+ Key? key,
+ }) : super(key: key);
+
+ static const String discordInviteUrl = 'https://discord.gg/t9dTVXxnvC';
+ static const String discordLogoUri = 'assets/discord-logo-blue.svg';
+
+ @override
+ Widget build(BuildContext context) {
+ return SliverList(
+ delegate: SliverChildListDelegate.fixed(
+ [
+ const SectionText(text: AppText.aboutPageCommunitySubtitle),
+ const SectionText(text: AppText.aboutPageCommunityJoinDiscord),
+ Material(
+ color: Colors.transparent,
+ child: InkWell(
+ onTap: () => LauncherHelper.launchUri(
+ context,
+ discordInviteUrl,
+ launchMode: LaunchMode.externalApplication,
+ ),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: Dimensions.standardSpacing),
+ child: SvgPicture.asset(
+ discordLogoUri,
+ height: Dimensions.detailsItemHeight,
+ alignment: Alignment.topLeft,
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
+
class _ContentAndDataSection extends StatelessWidget {
const _ContentAndDataSection({
Key? key,
@@ -337,11 +271,21 @@ class _PluginsAndLibrariesSection extends StatelessWidget {
subtitle: 'Draws SVG files',
uri: 'https://pub.dev/packages/flutter_svg',
),
+ DetailsItem(
+ title: 'Font Awesome',
+ subtitle: 'Icons pack',
+ uri: 'https://pub.dev/packages/font_awesome_flutter',
+ ),
DetailsItem(
title: 'Freezed',
subtitle: 'Data models code generator',
uri: 'https://pub.dev/packages/freezed',
),
+ DetailsItem(
+ title: 'Google Fonts - Lato Font',
+ subtitle: 'Font used across the entire app',
+ uri: 'https://pub.dev/packages/google_fonts',
+ ),
DetailsItem(
title: 'Get it',
subtitle: 'Dependency injection',
@@ -352,6 +296,11 @@ class _PluginsAndLibrariesSection extends StatelessWidget {
subtitle: 'NoSQL Database',
uri: 'https://pub.dev/packages/hive',
),
+ DetailsItem(
+ title: 'Html Unespace',
+ subtitle: 'Helps unescaping HTML-encoded strings',
+ uri: 'https://pub.dev/packages/html_unescape',
+ ),
DetailsItem(
title: 'Path Provider',
subtitle: 'Helps with filesystem paths',
@@ -372,16 +321,6 @@ class _PluginsAndLibrariesSection extends StatelessWidget {
subtitle: "Launching Uri's",
uri: 'https://pub.dev/packages/url_launcher',
),
- DetailsItem(
- title: 'Lato Font',
- subtitle: 'Font used across the entire app',
- uri: 'https://pub.dev/packages/google_fonts',
- ),
- DetailsItem(
- title: 'Font Awesome',
- subtitle: 'Icons pack',
- uri: 'https://pub.dev/packages/font_awesome_flutter',
- ),
],
),
);
diff --git a/board_games_companion/lib/pages/board_game_details/board_game_details_page.dart b/board_games_companion/lib/pages/board_game_details/board_game_details_page.dart
index f7096f5b..ab7c3017 100644
--- a/board_games_companion/lib/pages/board_game_details/board_game_details_page.dart
+++ b/board_games_companion/lib/pages/board_game_details/board_game_details_page.dart
@@ -4,6 +4,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
import 'package:provider/provider.dart';
+import 'package:url_launcher/url_launcher.dart';
import '../../common/app_colors.dart';
import '../../common/app_styles.dart';
@@ -307,6 +308,7 @@ class _Links extends StatelessWidget {
await LauncherHelper.launchUri(
context,
_boardGameDetailsStore.boardGame.bggOverviewUrl,
+ launchMode: LaunchMode.externalApplication,
);
}),
const SizedBox(
@@ -320,6 +322,7 @@ class _Links extends StatelessWidget {
await LauncherHelper.launchUri(
context,
_boardGameDetailsStore.boardGame.bggHotVideosUrl,
+ launchMode: LaunchMode.externalApplication,
);
},
),
@@ -334,6 +337,7 @@ class _Links extends StatelessWidget {
await LauncherHelper.launchUri(
context,
_boardGameDetailsStore.boardGame.bggHotForumUrl,
+ launchMode: LaunchMode.externalApplication,
);
},
),
diff --git a/board_games_companion/lib/pages/edit_playthrough/edit_playthrough_page.dart b/board_games_companion/lib/pages/edit_playthrough/edit_playthrough_page.dart
index d688335a..ee3365f2 100644
--- a/board_games_companion/lib/pages/edit_playthrough/edit_playthrough_page.dart
+++ b/board_games_companion/lib/pages/edit_playthrough/edit_playthrough_page.dart
@@ -61,14 +61,14 @@ class EditPlaythroughPageState extends State with EnterScor
slivers: [
SliverPersistentHeader(
delegate: BgcSliverHeaderDelegate(
- title: AppText.editPlaythroughDateAndDurationHeaderTitle,
+ primaryTitle: AppText.editPlaythroughDateAndDurationHeaderTitle,
),
),
_PlayDateTimeSection(viewModel: widget.viewModel),
SliverPersistentHeader(
pinned: true,
- delegate:
- BgcSliverHeaderDelegate(title: AppText.editPlaythroughScoresHeaderTitle),
+ delegate: BgcSliverHeaderDelegate(
+ primaryTitle: AppText.editPlaythroughScoresHeaderTitle),
),
_ScoresSection(
viewModel: widget.viewModel,
@@ -78,8 +78,8 @@ class EditPlaythroughPageState extends State with EnterScor
if (widget.viewModel.hasNotes) ...[
SliverPersistentHeader(
pinned: true,
- delegate:
- BgcSliverHeaderDelegate(title: AppText.editPlaythroughNotesHeaderTitle),
+ delegate: BgcSliverHeaderDelegate(
+ primaryTitle: AppText.editPlaythroughNotesHeaderTitle),
),
_NotesSection(
notes: widget.viewModel.notes!,
diff --git a/board_games_companion/lib/pages/games/games_page.dart b/board_games_companion/lib/pages/games/games_page.dart
index b176bd3f..0af07351 100644
--- a/board_games_companion/lib/pages/games/games_page.dart
+++ b/board_games_companion/lib/pages/games/games_page.dart
@@ -188,7 +188,7 @@ class _Collection extends StatelessWidget {
if (hasMainGames) ...[
SliverPersistentHeader(
delegate: BgcSliverHeaderDelegate(
- title: sprintf(
+ primaryTitle: sprintf(
AppText.gamesPageMainGamesSliverSectionTitleFormat,
[totalMainGames],
),
@@ -200,7 +200,7 @@ class _Collection extends StatelessWidget {
for (var expansionsMapEntry in expansionsMap.entries) ...[
SliverPersistentHeader(
delegate: BgcSliverHeaderDelegate(
- title: sprintf(
+ primaryTitle: sprintf(
AppText.gamesPageExpansionsSliverSectionTitleFormat,
[expansionsMapEntry.key.item2, expansionsMapEntry.value.length],
),
diff --git a/board_games_companion/lib/pages/playthroughs/playthrough_statistics_view_model.dart b/board_games_companion/lib/pages/playthroughs/playthrough_statistics_view_model.dart
index 15ce99c7..e730a61f 100644
--- a/board_games_companion/lib/pages/playthroughs/playthrough_statistics_view_model.dart
+++ b/board_games_companion/lib/pages/playthroughs/playthrough_statistics_view_model.dart
@@ -191,14 +191,21 @@ abstract class _PlaythroughStatisticsViewModel with Store {
List finishedPlaythroughs,
BoardGameStatistics boardGameStatistics,
) {
- boardGameStatistics.playerCountPercentage = groupBy(
- finishedPlaythroughs
- .map((Playthrough playthrough) => playthrough.playerIds.length)
- .toList()
- ..sort((int numberOfPlayers, int otherNumberOfPlayers) =>
- numberOfPlayers.compareTo(otherNumberOfPlayers)),
- (int numberOfPlayers) => numberOfPlayers)
- .map((key, value) => MapEntry(key, value.length / finishedPlaythroughs.length));
+ boardGameStatistics.playerCountPercentage = [];
+ final numberOfPlayersInPlaythroughs = finishedPlaythroughs
+ .map((Playthrough playthrough) => playthrough.playerIds.length)
+ .toList()
+ ..sort((int numberOfPlayers, int otherNumberOfPlayers) =>
+ numberOfPlayers.compareTo(otherNumberOfPlayers));
+ groupBy(numberOfPlayersInPlaythroughs, (int numberOfPlayers) => numberOfPlayers).forEach(
+ (numberOfPlayers, playthroughs) => boardGameStatistics.playerCountPercentage!.add(
+ PlayerCountStatistics(
+ numberOfPlayers: numberOfPlayers,
+ numberOfGamesPlayed: playthroughs.length,
+ gamesPlayedPercentage: playthroughs.length / finishedPlaythroughs.length,
+ ),
+ ),
+ );
}
void _updatePlayerWinsPercentage(
@@ -228,10 +235,13 @@ abstract class _PlaythroughStatisticsViewModel with Store {
}
}
- boardGameStatistics.playerWinsPercentage = {};
+ boardGameStatistics.playerWinsPercentage = [];
for (final MapEntry playerWin in playerWins.entries) {
- boardGameStatistics.playerWinsPercentage![playerWin.key] =
- playerWin.value / finishedPlaythroughs.length;
+ boardGameStatistics.playerWinsPercentage!.add(PlayerWinsStatistics(
+ player: playerWin.key,
+ numberOfWins: playerWin.value,
+ winsPercentage: playerWin.value / finishedPlaythroughs.length,
+ ));
}
}
}
diff --git a/board_games_companion/lib/pages/playthroughs/playthroughs_game_settings_page.dart b/board_games_companion/lib/pages/playthroughs/playthroughs_game_settings_page.dart
index bfb6189a..aac834ba 100644
--- a/board_games_companion/lib/pages/playthroughs/playthroughs_game_settings_page.dart
+++ b/board_games_companion/lib/pages/playthroughs/playthroughs_game_settings_page.dart
@@ -2,12 +2,11 @@ import 'package:board_games_companion/common/app_text.dart';
import 'package:board_games_companion/common/app_theme.dart';
import 'package:board_games_companion/common/enums/game_winning_condition.dart';
import 'package:board_games_companion/pages/playthroughs/playthroughs_game_settings_view_model.dart';
-import 'package:board_games_companion/widgets/about/section_title.dart';
import 'package:flutter/material.dart';
import '../../common/app_colors.dart';
-import '../../common/dimensions.dart';
import '../../injectable.dart';
+import '../../widgets/common/slivers/bgc_sliver_header_delegate.dart';
class PlaythroughsGameSettingsPage extends StatefulWidget {
const PlaythroughsGameSettingsPage({Key? key}) : super(key: key);
@@ -27,29 +26,25 @@ class _PlaythroughsGameSettingsPageState extends State CustomScrollView(
+ slivers: [
+ SliverPersistentHeader(
+ delegate: BgcSliverHeaderDelegate(
+ primaryTitle: AppText.playthroughsGameSettingsWinningConditionSectionTitle,
+ ),
+ ),
+ _WinningConditionSection(
+ winningCondition: viewModel.winningCondition,
+ onChange: (GameWinningCondition? winningCondition) async {
+ if (winningCondition == null) {
+ return;
+ }
- await viewModel.updateWinningCondition(winningCondition);
- },
- )
- ],
- ),
- ),
- );
- }
+ await viewModel.updateWinningCondition(winningCondition);
+ },
+ ),
+ ],
+ );
}
class _WinningConditionSection extends StatefulWidget {
@@ -71,50 +66,51 @@ class __WinningConditionSectionState extends State<_WinningConditionSection> {
@override
Widget build(BuildContext context) {
- return Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [
- const SectionTitle(title: AppText.playthroughsGameSettingsWinningConditionSectionTitle),
- RadioListTile(
- activeColor: AppColors.accentColor,
- title: const Text(
- AppText.playthroughsGameSettingsWinningConditionHighestScore,
- style: AppTheme.defaultTextFieldStyle,
+ return SliverToBoxAdapter(
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ RadioListTile(
+ activeColor: AppColors.accentColor,
+ title: const Text(
+ AppText.playthroughsGameSettingsWinningConditionHighestScore,
+ style: AppTheme.defaultTextFieldStyle,
+ ),
+ secondary: Icon(Icons.arrow_upward,
+ color: _gameWinningCondition == GameWinningCondition.HighestScore
+ ? AppColors.activeWinningConditionIcon
+ : AppColors.inactiveBottomTabColor),
+ value: GameWinningCondition.HighestScore,
+ groupValue: _gameWinningCondition,
+ onChanged: (GameWinningCondition? value) {
+ setState(() {
+ _gameWinningCondition = value;
+ widget.onChange(_gameWinningCondition);
+ });
+ },
),
- secondary: Icon(Icons.arrow_upward,
- color: _gameWinningCondition == GameWinningCondition.HighestScore
- ? AppColors.activeWinningConditionIcon
- : AppColors.inactiveBottomTabColor),
- value: GameWinningCondition.HighestScore,
- groupValue: _gameWinningCondition,
- onChanged: (GameWinningCondition? value) {
- setState(() {
- _gameWinningCondition = value;
- widget.onChange(_gameWinningCondition);
- });
- },
- ),
- RadioListTile(
- activeColor: AppColors.accentColor,
- title: const Text(
- AppText.playthroughsGameSettingsWinningConditionLowestScore,
- style: AppTheme.defaultTextFieldStyle,
+ RadioListTile(
+ activeColor: AppColors.accentColor,
+ title: const Text(
+ AppText.playthroughsGameSettingsWinningConditionLowestScore,
+ style: AppTheme.defaultTextFieldStyle,
+ ),
+ secondary: Icon(Icons.arrow_downward,
+ color: _gameWinningCondition == GameWinningCondition.LowestScore
+ ? AppColors.activeWinningConditionIcon
+ : AppColors.inactiveBottomTabColor),
+ value: GameWinningCondition.LowestScore,
+ groupValue: _gameWinningCondition,
+ onChanged: (GameWinningCondition? value) {
+ setState(() {
+ _gameWinningCondition = value;
+ widget.onChange(_gameWinningCondition);
+ });
+ },
),
- secondary: Icon(Icons.arrow_downward,
- color: _gameWinningCondition == GameWinningCondition.LowestScore
- ? AppColors.activeWinningConditionIcon
- : AppColors.inactiveBottomTabColor),
- value: GameWinningCondition.LowestScore,
- groupValue: _gameWinningCondition,
- onChanged: (GameWinningCondition? value) {
- setState(() {
- _gameWinningCondition = value;
- widget.onChange(_gameWinningCondition);
- });
- },
- ),
- ],
+ ],
+ ),
);
}
}
diff --git a/board_games_companion/lib/pages/playthroughs/playthroughs_log_game_page.dart b/board_games_companion/lib/pages/playthroughs/playthroughs_log_game_page.dart
index 23307db4..bdb0b6e1 100644
--- a/board_games_companion/lib/pages/playthroughs/playthroughs_log_game_page.dart
+++ b/board_games_companion/lib/pages/playthroughs/playthroughs_log_game_page.dart
@@ -21,6 +21,7 @@ import '../../models/player_score.dart';
import '../../models/playthrough_player.dart';
import '../../widgets/common/default_icon.dart';
import '../../widgets/common/elevated_icon_button.dart';
+import '../../widgets/common/slivers/bgc_sliver_header_delegate.dart';
import '../../widgets/common/text/item_property_value_widget.dart';
import '../../widgets/player/player_avatar.dart';
import '../../widgets/playthrough/calendar_card.dart';
@@ -88,14 +89,14 @@ class _LogPlaythroughStepperState extends State<_LogPlaythroughStepper> {
@override
Widget build(BuildContext context) {
- return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Padding(
- padding: const EdgeInsets.all(Dimensions.standardSpacing),
- child: Text('Log a game', style: AppTheme.theme.textTheme.headline2),
+ return CustomScrollView(
+ slivers: [
+ SliverPersistentHeader(
+ delegate: BgcSliverHeaderDelegate(
+ primaryTitle: AppText.playthroughsLogGamePageHeader,
+ ),
),
- Expanded(
+ SliverFillRemaining(
child: Theme(
data: AppTheme.theme.copyWith(
colorScheme: AppTheme.theme.colorScheme.copyWith(primary: AppColors.accentColor),
diff --git a/board_games_companion/lib/pages/playthroughs/playthroughs_page.dart b/board_games_companion/lib/pages/playthroughs/playthroughs_page.dart
index 14ea7ba5..c913839f 100644
--- a/board_games_companion/lib/pages/playthroughs/playthroughs_page.dart
+++ b/board_games_companion/lib/pages/playthroughs/playthroughs_page.dart
@@ -1,14 +1,15 @@
+import 'package:board_games_companion/utilities/launcher_helper.dart';
import 'package:convex_bottom_bar/convex_bottom_bar.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
+import 'package:url_launcher/url_launcher.dart';
import '../../common/app_colors.dart';
import '../../common/app_text.dart';
import '../../common/app_theme.dart';
import '../../common/dimensions.dart';
import '../../models/bgg/bgg_plays_import_raport.dart';
-import '../../models/hive/board_game_details.dart';
import '../../models/navigation/board_game_details_page_arguments.dart';
import '../../widgets/bottom_tab_icon.dart';
import '../../widgets/common/loading_overlay.dart';
@@ -62,6 +63,10 @@ class PlaythroughsPageState extends BasePageState
appBar: AppBar(
title: Text(widget.viewModel.boardGame.name, style: AppTheme.titleTextStyle),
actions: [
+ IconButton(
+ icon: const Icon(Icons.music_note, color: AppColors.accentColor),
+ onPressed: () async => _openGamesMusicPlaylist(context),
+ ),
Observer(
builder: (_) {
if (!widget.viewModel.hasUser) {
@@ -70,14 +75,13 @@ class PlaythroughsPageState extends BasePageState
return IconButton(
icon: const Icon(Icons.download, color: AppColors.accentColor),
- onPressed: () =>
- _importBggPlays(widget.viewModel.userName!, widget.viewModel.boardGame.id),
+ onPressed: () => _importBggPlays(),
);
},
),
IconButton(
icon: const Icon(Icons.info, color: AppColors.accentColor),
- onPressed: () async => _navigateToBoardGameDetails(context, widget.viewModel.boardGame),
+ onPressed: () => _navigateToBoardGameDetails(context),
),
],
),
@@ -133,25 +137,32 @@ class PlaythroughsPageState extends BasePageState
return scaffold;
}
- Future _navigateToBoardGameDetails(
- BuildContext context, BoardGameDetails boardGameDetails) async {
+ Future _navigateToBoardGameDetails(BuildContext context) async {
await Navigator.pushNamed(
context,
BoardGamesDetailsPage.pageRoute,
arguments: BoardGameDetailsPageArguments(
- boardGameDetails.id,
- boardGameDetails.name,
+ widget.viewModel.boardGame.id,
+ widget.viewModel.boardGame.name,
PlaythroughsPage,
),
);
}
- Future _importBggPlays(String username, String boardGameId) async {
+ Future _openGamesMusicPlaylist(BuildContext context) async {
+ await LauncherHelper.launchUri(
+ context,
+ widget.viewModel.gamePlaylistUrl,
+ launchMode: LaunchMode.externalApplication,
+ );
+ }
+
+ Future _importBggPlays() async {
try {
setState(() {
_showImportGamesLoadingIndicator = true;
});
- await widget.viewModel.importPlays(username, boardGameId);
+ await widget.viewModel.importPlays(widget.viewModel.userName!, widget.viewModel.boardGame.id);
if (widget.viewModel.bggPlaysImportRaport!.playsToImportTotal > 0) {
if (!mounted) {
return;
@@ -159,8 +170,8 @@ class PlaythroughsPageState extends BasePageState
await _showImportPlaysReportDialog(
context,
- username,
- boardGameId,
+ widget.viewModel.userName!,
+ widget.viewModel.boardGame.id,
widget.viewModel.bggPlaysImportRaport!,
);
} else {
diff --git a/board_games_companion/lib/pages/playthroughs/playthroughs_statistics_page.dart b/board_games_companion/lib/pages/playthroughs/playthroughs_statistics_page.dart
index 110eff9b..dbb6424d 100644
--- a/board_games_companion/lib/pages/playthroughs/playthroughs_statistics_page.dart
+++ b/board_games_companion/lib/pages/playthroughs/playthroughs_statistics_page.dart
@@ -1,5 +1,6 @@
import 'dart:math';
+import 'package:board_games_companion/common/app_theme.dart';
import 'package:board_games_companion/injectable.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
@@ -18,6 +19,7 @@ import '../../models/board_game_statistics.dart';
import '../../models/hive/player.dart';
import '../../models/player_statistics.dart';
import '../../widgets/board_games/board_game_image.dart';
+import '../../widgets/common/slivers/bgc_sliver_header_delegate.dart';
import '../../widgets/common/text/item_property_title_widget.dart';
import '../../widgets/player/player_avatar.dart';
import '../../widgets/playthrough/calendar_card.dart';
@@ -62,40 +64,50 @@ class PlaythroughStatistcsPageState extends State {
),
),
if (viewModel.futureLoadBoardGamesStatistics?.status == FutureStatus.fulfilled) ...[
+ SliverPersistentHeader(
+ delegate: BgcSliverHeaderDelegate(
+ primaryTitle: AppText.playthroughsStatisticsPageLastWinnerSectionTitle,
+ ),
+ ),
_SliverSectionWrapper(
- child: _LastWinnerSection(boardGameStatistics: viewModel.boardGameStatistics)),
+ child: _LastWinnerSection(boardGameStatistics: viewModel.boardGameStatistics),
+ ),
+ SliverPersistentHeader(
+ delegate: BgcSliverHeaderDelegate(
+ primaryTitle: AppText.playthroughsStatisticsPageOverallStatsSectionTitle,
+ ),
+ ),
_SliverSectionWrapper(
child: _OverallStatsSection(boardGameStatistics: viewModel.boardGameStatistics),
),
- if (viewModel.boardGameStatistics.topScoreres?.isNotEmpty ?? false)
- _SliverSectionWrapper(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- const ItemPropertyTitle(
- AppText.playthroughsStatisticsPageTopFiveSectionTitle),
- const SizedBox(height: Dimensions.halfStandardSpacing),
- _TopScores(boardGameStatistics: viewModel.boardGameStatistics),
- ],
+ if (viewModel.boardGameStatistics.topScoreres?.isNotEmpty ?? false) ...[
+ SliverPersistentHeader(
+ delegate: BgcSliverHeaderDelegate(
+ primaryTitle: AppText.playthroughsStatisticsPageTopFiveSectionTitle,
),
),
+ _SliverSectionWrapper(
+ child: _TopScores(boardGameStatistics: viewModel.boardGameStatistics),
+ ),
+ ],
if ((viewModel.boardGameStatistics.playerCountPercentage?.isNotEmpty ?? false) &&
- (viewModel.boardGameStatistics.playerWinsPercentage?.isNotEmpty ?? false))
+ (viewModel.boardGameStatistics.playerWinsPercentage?.isNotEmpty ?? false)) ...[
+ SliverPersistentHeader(
+ delegate: BgcSliverHeaderDelegate(
+ primaryTitle: AppText
+ .playthroughsStatisticsPageGamesPlayedAndWonChartsSectionPrimaryTitle,
+ secondaryTitle: AppText
+ .playthroughsStatisticsPageGamesPlayedAndWonChartsSectionSecondaryTitle,
+ ),
+ ),
_SliverSectionWrapper(
child: _PlayerCharts(boardGameStatistics: viewModel.boardGameStatistics),
),
- if (viewModel.boardGameStatistics.playersStatistics?.isNotEmpty ?? false) ...[
- SliverToBoxAdapter(
- child: Padding(
- padding: const EdgeInsets.only(left: Dimensions.standardSpacing),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: const [
- ItemPropertyTitle(
- AppText.playthroughsStatisticsPagePlayersStatsSectionTitle),
- SizedBox(height: Dimensions.halfStandardSpacing),
- ],
- ),
+ ],
+ if (viewModel.boardGameStatistics.playersStatistics?.isNotEmpty ?? false) ...[
+ SliverPersistentHeader(
+ delegate: BgcSliverHeaderDelegate(
+ primaryTitle: AppText.playthroughsStatisticsPagePlayersStatsSectionTitle,
),
),
_PlayersStatisticsSection(boardGameStatistics: viewModel.boardGameStatistics),
@@ -124,7 +136,11 @@ class _PlayersStatisticsSection extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SliverPadding(
- padding: const EdgeInsets.symmetric(horizontal: Dimensions.standardSpacing),
+ padding: const EdgeInsets.only(
+ top: Dimensions.standardSpacing,
+ left: Dimensions.standardSpacing,
+ right: Dimensions.standardSpacing,
+ ),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(_, index) {
@@ -223,8 +239,6 @@ class _LastWinnerSection extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
- const ItemPropertyTitle(AppText.playthroughsStatisticsPageLastWinnerSectionTitle),
- const SizedBox(height: Dimensions.halfStandardSpacing),
SingleChildScrollView(
clipBehavior: Clip.none,
scrollDirection: Axis.horizontal,
@@ -273,28 +287,21 @@ class _PlayerChartsState extends State<_PlayerCharts> {
playerCountChartColors = {};
playerWinsChartColors = {};
int i = 0;
- for (final MapEntry playeCountPercentage
- in widget.boardGameStatistics.playerCountPercentage!.entries) {
- playerCountChartColors[playeCountPercentage.key] =
+ for (final PlayerCountStatistics playeCountStatistics
+ in widget.boardGameStatistics.playerCountPercentage!) {
+ playerCountChartColors[playeCountStatistics.numberOfPlayers] =
AppColors.chartColorPallete[i++ % AppColors.chartColorPallete.length];
}
i = 0;
- for (final MapEntry playerWinsPercentage
- in widget.boardGameStatistics.playerWinsPercentage!.entries) {
- playerWinsChartColors[playerWinsPercentage.key] =
+ for (final PlayerWinsStatistics playerWinsStatistics
+ in widget.boardGameStatistics.playerWinsPercentage!) {
+ playerWinsChartColors[playerWinsStatistics.player] =
AppColors.chartColorPallete[i++ % AppColors.chartColorPallete.length];
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- Row(
- children: const [
- ItemPropertyTitle(AppText.playthroughsStatisticsPagePlayerCountPercentageSectionTitle),
- Expanded(child: SizedBox.shrink()),
- ItemPropertyTitle(AppText.playthroughsStatisticsPagePlayerWinsPercentageSectionTitle),
- ],
- ),
const SizedBox(height: Dimensions.halfStandardSpacing),
Row(
children: [
@@ -304,12 +311,12 @@ class _PlayerChartsState extends State<_PlayerCharts> {
child: PieChart(
PieChartData(
sections: [
- for (final MapEntry playeCountPercentage
- in widget.boardGameStatistics.playerCountPercentage!.entries)
+ for (final PlayerCountStatistics playeCountStatistics
+ in widget.boardGameStatistics.playerCountPercentage!)
PieChartSectionData(
- value: playeCountPercentage.value,
- title: '${(playeCountPercentage.value * 100).toStringAsFixed(0)}%',
- color: playerCountChartColors[playeCountPercentage.key],
+ value: playeCountStatistics.gamesPlayedPercentage,
+ title: '${playeCountStatistics.numberOfGamesPlayed}',
+ color: playerCountChartColors[playeCountStatistics.numberOfPlayers],
),
],
),
@@ -322,12 +329,12 @@ class _PlayerChartsState extends State<_PlayerCharts> {
child: PieChart(
PieChartData(
sections: [
- for (final MapEntry playeWinsPercentage
- in widget.boardGameStatistics.playerWinsPercentage!.entries)
+ for (final PlayerWinsStatistics playeWinsStatistics
+ in widget.boardGameStatistics.playerWinsPercentage!)
PieChartSectionData(
- value: playeWinsPercentage.value,
- title: '${(playeWinsPercentage.value * 100).toStringAsFixed(0)}%',
- color: playerWinsChartColors[playeWinsPercentage.key],
+ value: playeWinsStatistics.winsPercentage,
+ title: '${playeWinsStatistics.numberOfWins}',
+ color: playerWinsChartColors[playeWinsStatistics.player],
),
],
),
@@ -344,20 +351,35 @@ class _PlayerChartsState extends State<_PlayerCharts> {
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- for (final MapEntry playeCountPercentage
- in widget.boardGameStatistics.playerCountPercentage!.entries)
+ for (final PlayerCountStatistics playeCountStatistics
+ in widget.boardGameStatistics.playerCountPercentage!)
Padding(
padding: const EdgeInsets.only(bottom: Dimensions.standardSpacing),
child: Row(
children: [
- _ChartLegendBox(color: playerCountChartColors[playeCountPercentage.key]!),
+ _ChartLegendBox(
+ color: playerCountChartColors[playeCountStatistics.numberOfPlayers]!),
const SizedBox(width: Dimensions.halfStandardSpacing),
- Text(
- sprintf(
- AppText.playthroughsStatisticsPagePlayerCountChartLegendFormat,
- [
- playeCountPercentage.key,
- if (playeCountPercentage.key > 1) 's' else ''
+ RichText(
+ text: TextSpan(
+ children: [
+ TextSpan(
+ text: sprintf(
+ playeCountStatistics.numberOfPlayers > 1
+ ? AppText
+ .playthroughsStatisticsPagePlayerCountChartLegendFormatPlural
+ : AppText
+ .playthroughsStatisticsPagePlayerCountChartLegendFormatSingular,
+ [
+ playeCountStatistics.numberOfPlayers,
+ ],
+ ),
+ ),
+ TextSpan(
+ text:
+ ' [${(playeCountStatistics.gamesPlayedPercentage * 100).toStringAsFixed(0)}%]',
+ style: AppTheme.theme.textTheme.subtitle1,
+ ),
],
),
),
@@ -372,15 +394,26 @@ class _PlayerChartsState extends State<_PlayerCharts> {
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
- for (final MapEntry playerWinsPercentage
- in widget.boardGameStatistics.playerWinsPercentage!.entries)
+ for (final PlayerWinsStatistics playerWinsStatistics
+ in widget.boardGameStatistics.playerWinsPercentage!)
Padding(
padding: const EdgeInsets.only(bottom: Dimensions.standardSpacing),
child: Row(
children: [
- Text('${playerWinsPercentage.key.name}'),
+ RichText(
+ text: TextSpan(
+ children: [
+ TextSpan(text: '${playerWinsStatistics.player.name} '),
+ TextSpan(
+ text:
+ '[${(playerWinsStatistics.winsPercentage * 100).toStringAsFixed(0)}%]',
+ style: AppTheme.theme.textTheme.subtitle1,
+ ),
+ ],
+ ),
+ ),
const SizedBox(width: Dimensions.halfStandardSpacing),
- _ChartLegendBox(color: playerWinsChartColors[playerWinsPercentage.key]!),
+ _ChartLegendBox(color: playerWinsChartColors[playerWinsStatistics.player]!),
],
),
),
@@ -512,8 +545,6 @@ class _OverallStatsSection extends StatelessWidget {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- const ItemPropertyTitle(AppText.playthroughsStatisticsPageOverallStatsSectionTitle),
- const SizedBox(height: Dimensions.halfStandardSpacing),
Row(
children: [
Column(
@@ -667,7 +698,7 @@ class _SliverSectionWrapper extends StatelessWidget {
return SliverPadding(
padding: const EdgeInsets.only(
left: Dimensions.standardSpacing,
- top: Dimensions.halfStandardSpacing,
+ top: Dimensions.standardSpacing,
right: Dimensions.standardSpacing,
bottom: Dimensions.doubleStandardSpacing,
),
diff --git a/board_games_companion/lib/pages/playthroughs/playthroughs_view_model.dart b/board_games_companion/lib/pages/playthroughs/playthroughs_view_model.dart
index 7cf9432c..c604085f 100644
--- a/board_games_companion/lib/pages/playthroughs/playthroughs_view_model.dart
+++ b/board_games_companion/lib/pages/playthroughs/playthroughs_view_model.dart
@@ -35,6 +35,9 @@ abstract class _PlaythroughsViewModel with Store {
this._userStore,
);
+ static const baseMelodiceUrl = 'https://melodice.org';
+ static const melodicePlaylistUrl = '$baseMelodiceUrl/playlist';
+
final PlayersStore _playersStore;
final PlaythroughsStore _playthroughsStore;
final AnalyticsService _analyticsService;
@@ -55,6 +58,9 @@ abstract class _PlaythroughsViewModel with Store {
@computed
String? get userName => _userStore.userName;
+ @computed
+ String get gamePlaylistUrl => '$melodicePlaylistUrl/${boardGame.id}';
+
@action
void setBoardGame(BoardGameDetails boardGame) {
_playthroughsStore.setBoardGame(boardGame);
diff --git a/board_games_companion/lib/pages/playthroughs/playthroughs_view_model.g.dart b/board_games_companion/lib/pages/playthroughs/playthroughs_view_model.g.dart
index 1cfa3035..44c5d56a 100644
--- a/board_games_companion/lib/pages/playthroughs/playthroughs_view_model.g.dart
+++ b/board_games_companion/lib/pages/playthroughs/playthroughs_view_model.g.dart
@@ -29,6 +29,13 @@ mixin _$PlaythroughsViewModel on _PlaythroughsViewModel, Store {
(_$userNameComputed ??= Computed(() => super.userName,
name: '_PlaythroughsViewModel.userName'))
.value;
+ Computed? _$gamePlaylistUrlComputed;
+
+ @override
+ String get gamePlaylistUrl => (_$gamePlaylistUrlComputed ??= Computed(
+ () => super.gamePlaylistUrl,
+ name: '_PlaythroughsViewModel.gamePlaylistUrl'))
+ .value;
late final _$_PlaythroughsViewModelActionController =
ActionController(name: '_PlaythroughsViewModel', context: context);
@@ -49,7 +56,8 @@ mixin _$PlaythroughsViewModel on _PlaythroughsViewModel, Store {
return '''
boardGame: ${boardGame},
hasUser: ${hasUser},
-userName: ${userName}
+userName: ${userName},
+gamePlaylistUrl: ${gamePlaylistUrl}
''';
}
}
diff --git a/board_games_companion/lib/pages/search_board_games/search_board_games_page.dart b/board_games_companion/lib/pages/search_board_games/search_board_games_page.dart
index 228beb3a..414426a9 100644
--- a/board_games_companion/lib/pages/search_board_games/search_board_games_page.dart
+++ b/board_games_companion/lib/pages/search_board_games/search_board_games_page.dart
@@ -66,7 +66,8 @@ class SearchBoardGamesPageState extends State {
),
SliverPersistentHeader(
pinned: true,
- delegate: BgcSliverHeaderDelegate(title: AppText.hotBoardGamesSliverSectionTitle),
+ delegate:
+ BgcSliverHeaderDelegate(primaryTitle: AppText.hotBoardGamesSliverSectionTitle),
),
_HotBoardGames(
viewModel: widget.viewModel,
diff --git a/board_games_companion/lib/utilities/launcher_helper.dart b/board_games_companion/lib/utilities/launcher_helper.dart
index a7b34d6c..77cba07c 100644
--- a/board_games_companion/lib/utilities/launcher_helper.dart
+++ b/board_games_companion/lib/utilities/launcher_helper.dart
@@ -3,7 +3,11 @@ import 'package:url_launcher/url_launcher.dart';
// ignore: avoid_classes_with_only_static_members
class LauncherHelper {
- static Future launchUri(BuildContext context, String uri) async {
+ static Future launchUri(
+ BuildContext context,
+ String uri, {
+ LaunchMode launchMode = LaunchMode.platformDefault,
+ }) async {
if (context == null || uri.isEmpty) {
return;
}
@@ -14,7 +18,7 @@ class LauncherHelper {
}
if (await canLaunchUrl(parsedUri)) {
- await launchUrl(parsedUri);
+ await launchUrl(parsedUri, mode: launchMode);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
diff --git a/board_games_companion/lib/widgets/about/detail_item.dart b/board_games_companion/lib/widgets/about/detail_item.dart
index 268dfc39..f6339b7d 100644
--- a/board_games_companion/lib/widgets/about/detail_item.dart
+++ b/board_games_companion/lib/widgets/about/detail_item.dart
@@ -1,5 +1,7 @@
+import 'package:basics/basics.dart';
import 'package:board_games_companion/widgets/elevated_container.dart';
import 'package:flutter/material.dart';
+import 'package:flutter_svg/svg.dart';
import '../../common/app_colors.dart';
import '../../common/app_styles.dart';
@@ -11,19 +13,19 @@ class DetailsItem extends StatelessWidget {
const DetailsItem({
required this.title,
required this.subtitle,
- this.iconUri,
+ this.assetIconUri,
this.onTap,
this.uri,
+ this.isSvg = false,
Key? key,
}) : super(key: key);
final String title;
final String subtitle;
- final String? iconUri;
+ final String? assetIconUri;
final VoidCallback? onTap;
final String? uri;
-
- static const double _size = 60;
+ final bool isSvg;
@override
Widget build(BuildContext context) {
@@ -37,27 +39,31 @@ class DetailsItem extends StatelessWidget {
horizontal: Dimensions.standardSpacing,
),
child: SizedBox(
- height: (iconUri?.isNotEmpty ?? false) ? _size : null,
+ height: (assetIconUri.isNotNullOrBlank) ? Dimensions.detailsItemHeight : null,
child: Row(
mainAxisSize: MainAxisSize.max,
children: [
- if (iconUri?.isNotEmpty ?? false)
+ if (assetIconUri.isNotNullOrBlank)
ElevatedContainer(
elevation: AppStyles.defaultElevation,
child: ClipRRect(
borderRadius: BorderRadius.circular(AppStyles.defaultCornerRadius),
- child: Image(
- height: _size,
- width: _size,
- image: AssetImage(iconUri!),
- fit: BoxFit.cover,
- ),
+ child: isSvg
+ ? SvgPicture.asset(
+ assetIconUri!,
+ height: Dimensions.detailsItemHeight,
+ width: Dimensions.detailsItemHeight,
+ )
+ : Image(
+ height: Dimensions.detailsItemHeight,
+ width: Dimensions.detailsItemHeight,
+ image: AssetImage(assetIconUri!),
+ fit: BoxFit.cover,
+ ),
),
),
- if (iconUri?.isNotEmpty ?? false)
- const SizedBox(
- width: Dimensions.standardSpacing,
- ),
+ if (assetIconUri?.isNotEmpty ?? false)
+ const SizedBox(width: Dimensions.standardSpacing),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.max,
@@ -69,13 +75,8 @@ class DetailsItem extends StatelessWidget {
fontWeight: FontWeight.normal,
),
),
- const SizedBox(
- height: Dimensions.halfStandardSpacing,
- ),
- Text(
- subtitle,
- style: AppTheme.theme.textTheme.subtitle1,
- ),
+ const SizedBox(height: Dimensions.halfStandardSpacing),
+ Text(subtitle, style: AppTheme.theme.textTheme.subtitle1),
],
),
),
@@ -92,10 +93,7 @@ class DetailsItem extends StatelessWidget {
return;
}
- await LauncherHelper.launchUri(
- context,
- uri!,
- );
+ await LauncherHelper.launchUri(context, uri!);
},
),
);
diff --git a/board_games_companion/lib/widgets/about/section_text.dart b/board_games_companion/lib/widgets/about/section_text.dart
index 1461d2df..13b4f080 100644
--- a/board_games_companion/lib/widgets/about/section_text.dart
+++ b/board_games_companion/lib/widgets/about/section_text.dart
@@ -4,20 +4,17 @@ import '../../common/app_theme.dart';
import '../../common/dimensions.dart';
class SectionText extends StatelessWidget {
-
const SectionText({
required this.text,
Key? key,
}) : super(key: key);
-
+
final String text;
@override
Widget build(BuildContext context) {
return Padding(
- padding: const EdgeInsets.all(
- Dimensions.standardSpacing,
- ),
+ padding: const EdgeInsets.all(Dimensions.standardSpacing),
child: Text(
text,
style: AppTheme.theme.textTheme.headline4,
diff --git a/board_games_companion/lib/widgets/common/bgg_community_member_text_widget.dart b/board_games_companion/lib/widgets/common/bgg_community_member_text_widget.dart
index 3882618d..bfc0f995 100644
--- a/board_games_companion/lib/widgets/common/bgg_community_member_text_widget.dart
+++ b/board_games_companion/lib/widgets/common/bgg_community_member_text_widget.dart
@@ -24,10 +24,7 @@ class BggCommunityMemberText extends StatelessWidget {
style: const TextStyle(decoration: TextDecoration.underline),
recognizer: TapGestureRecognizer()
..onTap = () async {
- await LauncherHelper.launchUri(
- context,
- Constants.boardGameGeekBaseApiUrl,
- );
+ await LauncherHelper.launchUri(context, Constants.boardGameGeekBaseApiUrl);
},
),
const TextSpan(text: ' community, then you can enter your '),
diff --git a/board_games_companion/lib/widgets/common/slivers/bgc_sliver_header_delegate.dart b/board_games_companion/lib/widgets/common/slivers/bgc_sliver_header_delegate.dart
index 5e4ce791..e2a415da 100644
--- a/board_games_companion/lib/widgets/common/slivers/bgc_sliver_header_delegate.dart
+++ b/board_games_companion/lib/widgets/common/slivers/bgc_sliver_header_delegate.dart
@@ -1,3 +1,4 @@
+import 'package:basics/basics.dart';
import 'package:flutter/material.dart';
import '../../../common/app_colors.dart';
@@ -6,31 +7,48 @@ import '../../../common/dimensions.dart';
class BgcSliverHeaderDelegate extends SliverPersistentHeaderDelegate {
BgcSliverHeaderDelegate({
- required this.title,
+ required this.primaryTitle,
+ this.secondaryTitle,
}) : super();
- String title;
+ String primaryTitle;
+ String? secondaryTitle;
+
+ static const double _size = 50;
@override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
return Material(
- elevation: 4,
- child: Container(
- color: AppColors.primaryColor,
- padding: const EdgeInsets.all(Dimensions.standardSpacing),
- child: Align(
- alignment: Alignment.centerLeft,
- child: Text(title, style: AppTheme.titleTextStyle, overflow: TextOverflow.ellipsis),
+ elevation: Dimensions.defaultElevation,
+ color: AppColors.primaryColor,
+ child: SizedBox(
+ height: _size,
+ child: Padding(
+ padding: const EdgeInsets.all(Dimensions.standardSpacing),
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Text(primaryTitle, style: AppTheme.titleTextStyle, overflow: TextOverflow.ellipsis),
+ if (secondaryTitle.isNotNullOrBlank) ...[
+ const Expanded(child: SizedBox.shrink()),
+ Text(
+ secondaryTitle!,
+ style: AppTheme.titleTextStyle,
+ overflow: TextOverflow.ellipsis,
+ ),
+ ]
+ ],
+ ),
),
),
);
}
@override
- double get maxExtent => 50;
+ double get maxExtent => _size;
@override
- double get minExtent => 50;
+ double get minExtent => _size;
@override
bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) {
diff --git a/board_games_companion/pubspec.lock b/board_games_companion/pubspec.lock
index dcb88586..7de96ca8 100644
--- a/board_games_companion/pubspec.lock
+++ b/board_games_companion/pubspec.lock
@@ -364,7 +364,7 @@ packages:
name: fl_chart
url: "https://pub.dartlang.org"
source: hosted
- version: "0.55.0"
+ version: "0.55.2"
flutter:
dependency: "direct main"
description: flutter
@@ -1098,7 +1098,7 @@ packages:
name: url_launcher
url: "https://pub.dartlang.org"
source: hosted
- version: "6.1.5"
+ version: "6.1.6"
url_launcher_android:
dependency: transitive
description:
diff --git a/board_games_companion/pubspec.yaml b/board_games_companion/pubspec.yaml
index 25dc7f37..746088ca 100644
--- a/board_games_companion/pubspec.yaml
+++ b/board_games_companion/pubspec.yaml
@@ -33,7 +33,7 @@ dependencies:
firebase_core: ^1.7.0
firebase_crashlytics: ^2.2.2
firebase_analytics: ^8.3.3
- fl_chart: ^0.55.0
+ fl_chart: ^0.55.2
flutter_mobx: ^2.0.6+1
flutter_polygon: ^0.2.0
flutter_slidable: ^2.0.0
@@ -62,7 +62,7 @@ dependencies:
share_plus: ^4.0.10+1
sprintf: ^6.0.0
tuple: ^2.0.0
- url_launcher: ^6.0.12
+ url_launcher: ^6.1.6
uuid: ^3.0.4
xml: ^5.3.0
flutter: