Skip to content

Commit

Permalink
Integrate games cookbook recipes with the code excerpts machinery (#9950
Browse files Browse the repository at this point in the history
)

Makes the code featured in the new game-related cookbooks CI-tested.

_Issues fixed by this PR (if any):_

Fixes #9822.

## Presubmit checklist

- [x] This PR doesn’t contain automatically generated corrections
(Grammarly or similar).
- [x] This PR follows the [Google Developer Documentation Style
Guidelines](https://developers.google.com/style) — for example, it
doesn’t use _i.e._ or _e.g._, and it avoids _I_ and _we_ (first person).
- [x] This PR uses [semantic line
breaks](https://github.com/dart-lang/site-shared/blob/main/doc/writing-for-dart-and-flutter-websites.md#semantic-line-breaks)
of 80 characters or fewer.
  • Loading branch information
filiph authored Dec 21, 2023
1 parent 534aa3f commit a78d7d2
Show file tree
Hide file tree
Showing 21 changed files with 885 additions and 108 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Take our settings from the example_utils analysis_options.yaml file.
# If necessary for a particular example, this file can also include
# overrides for individual lints.

include: package:example_utils/analysis.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import 'dart:async';

import 'package:games_services/games_services.dart';
import 'package:logging/logging.dart';

/// Allows awarding achievements and leaderboard scores,
/// and also showing the platforms' UI overlays for achievements
/// and leaderboards.
///
/// A facade of `package:games_services`.
class GamesServicesController {
static final Logger _log = Logger('GamesServicesController');

final Completer<bool> _signedInCompleter = Completer();

Future<bool> get signedIn => _signedInCompleter.future;

/// Unlocks an achievement on Game Center / Play Games.
///
/// You must provide the achievement ids via the [iOS] and [android]
/// parameters.
///
/// Does nothing when the game isn't signed into the underlying
/// games service.
Future<void> awardAchievement(
{required String iOS, required String android}) async {
if (!await signedIn) {
_log.warning('Trying to award achievement when not logged in.');
return;
}

try {
await GamesServices.unlock(
achievement: Achievement(
androidID: android,
iOSID: iOS,
),
);
} catch (e) {
_log.severe('Cannot award achievement: $e');
}
}

/// Signs into the underlying games service.
Future<void> initialize() async {
try {
await GamesServices.signIn();
// The API is unclear so we're checking to be sure. The above call
// returns a String, not a boolean, and there's no documentation
// as to whether every non-error result means we're safely signed in.
final signedIn = await GamesServices.isSignedIn;
_signedInCompleter.complete(signedIn);
} catch (e) {
_log.severe('Cannot log into GamesServices: $e');
_signedInCompleter.complete(false);
}
}

/// Launches the platform's UI overlay with achievements.
Future<void> showAchievements() async {
if (!await signedIn) {
_log.severe('Trying to show achievements when not logged in.');
return;
}

try {
await GamesServices.showAchievements();
} catch (e) {
_log.severe('Cannot show achievements: $e');
}
}

/// Launches the platform's UI overlay with leaderboard(s).
Future<void> showLeaderboard() async {
if (!await signedIn) {
_log.severe('Trying to show leaderboard when not logged in.');
return;
}

try {
await GamesServices.showLeaderboards(
// TODO: When ready, change both these leaderboard IDs.
iOSLeaderboardID: 'some_id_from_app_store',
androidLeaderboardID: 'sOmE_iD_fRoM_gPlAy',
);
} catch (e) {
_log.severe('Cannot show leaderboard: $e');
}
}

/// Submits [score] to the leaderboard.
Future<void> submitLeaderboardScore(int score) async {
if (!await signedIn) {
_log.warning('Trying to submit leaderboard when not logged in.');
return;
}

_log.info('Submitting $score to leaderboard.');

try {
await GamesServices.submitScore(
score: Score(
// TODO: When ready, change these leaderboard IDs.
iOSLeaderboardID: 'some_id_from_app_store',
androidLeaderboardID: 'sOmE_iD_fRoM_gPlAy',
value: score,
),
);
} catch (e) {
_log.severe('Cannot submit leaderboard score: $e');
}
}
}
44 changes: 44 additions & 0 deletions examples/cookbook/games/achievements_leaderboards/lib/various.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// ignore_for_file: unused_catch_clause

import 'package:flutter/services.dart';
import 'package:games_services/games_services.dart';

void main() async {
// #docregion signIn
try {
await GamesServices.signIn();
} on PlatformException catch (e) {
// ... deal with failures ...
}
// #enddocregion signIn

// #docregion unlock
await GamesServices.unlock(
achievement: Achievement(
androidID: 'your android id',
iOSID: 'your ios id',
),
);
// #enddocregion unlock

// #docregion showAchievements
await GamesServices.showAchievements();
// #enddocregion showAchievements

// #docregion submitScore
await GamesServices.submitScore(
score: Score(
iOSLeaderboardID: 'some_id_from_app_store',
androidLeaderboardID: 'sOmE_iD_fRoM_gPlAy',
value: 100,
),
);
// #enddocregion submitScore

// #docregion showLeaderboards
await GamesServices.showLeaderboards(
iOSLeaderboardID: 'some_id_from_app_store',
androidLeaderboardID: 'sOmE_iD_fRoM_gPlAy',
);
// #enddocregion showLeaderboards
}
19 changes: 19 additions & 0 deletions examples/cookbook/games/achievements_leaderboards/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: games_services_example
description: Games services

environment:
sdk: ^3.2.0

dependencies:
flutter:
sdk: flutter

games_services: ^4.0.0
logging: ^1.2.0

dev_dependencies:
example_utils:
path: ../../../example_utils

flutter:
uses-material-design: true
2 changes: 2 additions & 0 deletions examples/cookbook/games/firestore_multiplayer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
This is a stripped down version of the multiplayer sample
in https://github.com/flutter/games/tree/main/samples/multiplayer.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Take our settings from the example_utils analysis_options.yaml file.
# If necessary for a particular example, this file can also include
# overrides for individual lints.

include: package:example_utils/analysis.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// File generated by FlutterFire CLI.
// ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
import 'package:flutter/foundation.dart'
show defaultTargetPlatform, kIsWeb, TargetPlatform;

/// Default [FirebaseOptions] for use with your Firebase apps.
///
/// Example:
/// ```dart
/// import 'firebase_options.dart';
/// // ...
/// await Firebase.initializeApp(
/// options: DefaultFirebaseOptions.currentPlatform,
/// );
/// ```
class DefaultFirebaseOptions {
static FirebaseOptions get currentPlatform {
if (kIsWeb) {
return web;
}
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return android;
case TargetPlatform.iOS:
return ios;
case TargetPlatform.macOS:
return macos;
case TargetPlatform.windows:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for windows - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
case TargetPlatform.linux:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for linux - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
default:
throw UnsupportedError(
'DefaultFirebaseOptions are not supported for this platform.',
);
}
}

static const FirebaseOptions web = FirebaseOptions(
apiKey: 'blahblahbla',
appId: '1:123456:web:blahblahbla',
messagingSenderId: '123456',
projectId: 'card-game-deadbeef',
authDomain: 'card-game-deadbeef.firebaseapp.com',
storageBucket: 'card-game-deadbeef.appspot.com',
);

static const FirebaseOptions android = FirebaseOptions(
apiKey: 'blahblahbla',
appId: '1:123456:android:blahblahbla',
messagingSenderId: '123456',
projectId: 'card-game-deadbeef',
storageBucket: 'card-game-deadbeef.appspot.com',
);

static const FirebaseOptions ios = FirebaseOptions(
apiKey: 'blahblahbla',
appId: '1:123456:ios:blahblahbla',
messagingSenderId: '123456',
projectId: 'card-game-deadbeef',
storageBucket: 'card-game-deadbeef.appspot.com',
iosBundleId: 'com.example.card',
);

static const FirebaseOptions macos = FirebaseOptions(
apiKey: 'blahblahbla',
appId: '1:123456:ios:blahblahbla',
messagingSenderId: '123456',
projectId: 'card-game-deadbeef',
storageBucket: 'card-game-deadbeef.appspot.com',
iosBundleId: 'com.example.card.RunnerTests',
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// This file has only enough in it to satisfy `firestore_controller.dart`.

import 'package:flutter/foundation.dart';

import 'playing_area.dart';

class BoardState {
final VoidCallback onWin;

final PlayingArea areaOne = PlayingArea();

final PlayingArea areaTwo = PlayingArea();

BoardState({required this.onWin});

void dispose() {
areaOne.dispose();
areaTwo.dispose();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// This file has only enough in it to satisfy `firestore_controller.dart`.

import 'dart:async';

import 'playing_card.dart';

class PlayingArea {
final List<PlayingCard> cards = [];

final StreamController<void> _playerChanges =
StreamController<void>.broadcast();

PlayingArea();

Stream<void> get playerChanges => _playerChanges.stream;

void dispose() {
_playerChanges.close();
}

void replaceWith(List<PlayingCard> cards) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// This file has only enough in it to satisfy `firestore_controller.dart`.
class PlayingCard {
const PlayingCard();

factory PlayingCard.fromJson(Map<String, dynamic> json) {
return const PlayingCard();
}

Map<String, dynamic> toJson() => {
// Nothing
};
}
39 changes: 39 additions & 0 deletions examples/cookbook/games/firestore_multiplayer/lib/main.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// ignore_for_file: directives_ordering, prefer_const_constructors

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

// #docregion imports
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';

import 'firebase_options.dart';
// #enddocregion imports

void main() async {
// #docregion initializeApp
WidgetsFlutterBinding.ensureInitialized();

await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
// #enddocregion initializeApp

// #docregion runApp
runApp(
Provider.value(
value: FirebaseFirestore.instance,
child: MyApp(),
),
);
// #enddocregion runApp
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return const Placeholder();
}
}
Loading

0 comments on commit a78d7d2

Please sign in to comment.