1
0
mirror of https://github.com/flutter/samples.git synced 2025-11-10 14:58:34 +00:00

Flutter 3.29 beta (#2571)

This commit is contained in:
Eric Windmill
2025-02-12 18:08:01 -05:00
committed by GitHub
parent d62c784789
commit 719fd72c38
685 changed files with 76244 additions and 53721 deletions

View File

@@ -36,12 +36,15 @@ class AdsController {
void preloadAd() {
// TODO: When ready, change this to the Ad Unit IDs provided by AdMob.
// The current values are AdMob's sample IDs.
final adUnitId = defaultTargetPlatform == TargetPlatform.android
? 'ca-app-pub-3940256099942544/6300978111'
// iOS
: 'ca-app-pub-3940256099942544/2934735716';
_preloadedAd =
PreloadedBannerAd(size: AdSize.mediumRectangle, adUnitId: adUnitId);
final adUnitId =
defaultTargetPlatform == TargetPlatform.android
? 'ca-app-pub-3940256099942544/6300978111'
// iOS
: 'ca-app-pub-3940256099942544/2934735716';
_preloadedAd = PreloadedBannerAd(
size: AdSize.mediumRectangle,
adUnitId: adUnitId,
);
// Wait a bit so that calling at start of a new screen doesn't have
// adverse effects on performance.

View File

@@ -53,8 +53,11 @@ class _BannerAdWidgetState extends State<BannerAdWidget> {
if (_currentOrientation == orientation &&
_bannerAd != null &&
_adLoadingState == _LoadingState.loaded) {
_log.info(() => 'We have everything we need. Showing the ad '
'${_bannerAd.hashCode} now.');
_log.info(
() =>
'We have everything we need. Showing the ad '
'${_bannerAd.hashCode} now.',
);
return SizedBox(
width: _bannerAd!.size.width.toDouble(),
height: _bannerAd!.size.height.toDouble(),
@@ -123,7 +126,8 @@ class _BannerAdWidgetState extends State<BannerAdWidget> {
if (useAnchoredAdaptiveSize) {
final AnchoredAdaptiveBannerAdSize? adaptiveSize =
await AdSize.getCurrentOrientationAnchoredAdaptiveBannerAdSize(
MediaQuery.of(context).size.width.truncate());
MediaQuery.of(context).size.width.truncate(),
);
if (adaptiveSize == null) {
_log.warning('Unable to get height of anchored banner.');
@@ -137,16 +141,19 @@ class _BannerAdWidgetState extends State<BannerAdWidget> {
if (!mounted) return;
assert(Platform.isAndroid || Platform.isIOS,
'AdMob currently does not support ${Platform.operatingSystem}');
assert(
Platform.isAndroid || Platform.isIOS,
'AdMob currently does not support ${Platform.operatingSystem}',
);
_bannerAd = BannerAd(
// This is a test ad unit ID from
// https://developers.google.com/admob/android/test-ads. When ready,
// you replace this with your own, production ad unit ID,
// created in https://apps.admob.com/.
adUnitId: Theme.of(context).platform == TargetPlatform.android
? 'ca-app-pub-3940256099942544/6300978111'
: 'ca-app-pub-3940256099942544/2934735716',
adUnitId:
Theme.of(context).platform == TargetPlatform.android
? 'ca-app-pub-3940256099942544/6300978111'
: 'ca-app-pub-3940256099942544/2934735716',
size: size,
request: const AdRequest(),
listener: BannerAdListener(

View File

@@ -31,8 +31,10 @@ class PreloadedBannerAd {
Future<BannerAd> get ready => _adCompleter.future;
Future<void> load() {
assert(Platform.isAndroid || Platform.isIOS,
'AdMob currently does not support ${Platform.operatingSystem}');
assert(
Platform.isAndroid || Platform.isIOS,
'AdMob currently does not support ${Platform.operatingSystem}',
);
_bannerAd = BannerAd(
// This is a test ad unit ID from

View File

@@ -19,8 +19,9 @@ class _AppLifecycleObserverState extends State<AppLifecycleObserver>
with WidgetsBindingObserver {
static final _log = Logger('AppLifecycleObserver');
final ValueNotifier<AppLifecycleState> lifecycleListenable =
ValueNotifier(AppLifecycleState.inactive);
final ValueNotifier<AppLifecycleState> lifecycleListenable = ValueNotifier(
AppLifecycleState.inactive,
);
@override
Widget build(BuildContext context) {

View File

@@ -43,12 +43,13 @@ class AudioController {
/// Background music does not count into the [polyphony] limit. Music will
/// never be overridden by sound effects because that would be silly.
AudioController({int polyphony = 2})
: assert(polyphony >= 1),
_musicPlayer = AudioPlayer(playerId: 'musicPlayer'),
_sfxPlayers = Iterable.generate(
polyphony, (i) => AudioPlayer(playerId: 'sfxPlayer#$i'))
.toList(growable: false),
_playlist = Queue.of(List<Song>.of(songs)..shuffle()) {
: assert(polyphony >= 1),
_musicPlayer = AudioPlayer(playerId: 'musicPlayer'),
_sfxPlayers = Iterable.generate(
polyphony,
(i) => AudioPlayer(playerId: 'sfxPlayer#$i'),
).toList(growable: false),
_playlist = Queue.of(List<Song>.of(songs)..shuffle()) {
_musicPlayer.onPlayerComplete.listen(_changeSong);
}
@@ -56,7 +57,8 @@ class AudioController {
/// and therefore do things like stopping playback when the game
/// goes into the background.
void attachLifecycleNotifier(
ValueNotifier<AppLifecycleState> lifecycleNotifier) {
ValueNotifier<AppLifecycleState> lifecycleNotifier,
) {
_lifecycleNotifier?.removeListener(_handleAppLifecycle);
lifecycleNotifier.addListener(_handleAppLifecycle);
@@ -108,10 +110,12 @@ class AudioController {
// This assumes there is only a limited number of sound effects in the game.
// If there are hundreds of long sound effect files, it's better
// to be more selective when preloading.
await AudioCache.instance.loadAll(SfxType.values
.expand(soundTypeToFilename)
.map((path) => 'sfx/$path')
.toList());
await AudioCache.instance.loadAll(
SfxType.values
.expand(soundTypeToFilename)
.map((path) => 'sfx/$path')
.toList(),
);
}
/// Plays a single sound effect, defined by [type].
@@ -127,8 +131,9 @@ class AudioController {
}
final soundsOn = _settings?.soundsOn.value ?? false;
if (!soundsOn) {
_log.info(() =>
'Ignoring playing sound ($type) because sounds are turned off.');
_log.info(
() => 'Ignoring playing sound ($type) because sounds are turned off.',
);
return;
}
@@ -138,8 +143,10 @@ class AudioController {
_log.info(() => '- Chosen filename: $filename');
final currentPlayer = _sfxPlayers[_currentSfxPlayer];
currentPlayer.play(AssetSource('sfx/$filename'),
volume: soundTypeToVolume(type));
currentPlayer.play(
AssetSource('sfx/$filename'),
volume: soundTypeToVolume(type),
);
_currentSfxPlayer = (_currentSfxPlayer + 1) % _sfxPlayers.length;
}
@@ -209,17 +216,23 @@ class AudioController {
await _playFirstSongInPlaylist();
}
case PlayerState.stopped:
_log.info("resumeMusic() called when music is stopped. "
"This probably means we haven't yet started the music. "
"For example, the game was started with sound off.");
_log.info(
"resumeMusic() called when music is stopped. "
"This probably means we haven't yet started the music. "
"For example, the game was started with sound off.",
);
await _playFirstSongInPlaylist();
case PlayerState.playing:
_log.warning('resumeMusic() called when music is playing. '
'Nothing to do.');
_log.warning(
'resumeMusic() called when music is playing. '
'Nothing to do.',
);
case PlayerState.completed:
_log.warning('resumeMusic() called when music is completed. '
"Music should never be 'completed' as it's either not playing "
"or looping forever.");
_log.warning(
'resumeMusic() called when music is completed. '
"Music should never be 'completed' as it's either not playing "
"or looping forever.",
);
await _playFirstSongInPlaylist();
default:
_log.warning('Unhandled PlayerState: ${_musicPlayer.state}');

View File

@@ -5,11 +5,7 @@
List<String> soundTypeToFilename(SfxType type) {
switch (type) {
case SfxType.huhsh:
return const [
'hash1.mp3',
'hash2.mp3',
'hash3.mp3',
];
return const ['hash1.mp3', 'hash2.mp3', 'hash3.mp3'];
case SfxType.wssh:
return const [
'wssh1.mp3',
@@ -22,27 +18,13 @@ List<String> soundTypeToFilename(SfxType type) {
'kss1.mp3',
];
case SfxType.buttonTap:
return const [
'k1.mp3',
'k2.mp3',
'p1.mp3',
'p2.mp3',
];
return const ['k1.mp3', 'k2.mp3', 'p1.mp3', 'p2.mp3'];
case SfxType.congrats:
return const [
'yay1.mp3',
'wehee1.mp3',
'oo1.mp3',
];
return const ['yay1.mp3', 'wehee1.mp3', 'oo1.mp3'];
case SfxType.erase:
return const [
'fwfwfwfwfw1.mp3',
'fwfwfwfw1.mp3',
];
return const ['fwfwfwfwfw1.mp3', 'fwfwfwfw1.mp3'];
case SfxType.swishSwish:
return const [
'swishswish1.mp3',
];
return const ['swishswish1.mp3'];
}
}
@@ -61,11 +43,4 @@ double soundTypeToVolume(SfxType type) {
}
}
enum SfxType {
huhsh,
wssh,
buttonTap,
congrats,
erase,
swishSwish,
}
enum SfxType { huhsh, wssh, buttonTap, congrats, erase, swishSwish }

View File

@@ -28,8 +28,10 @@ class GamesServicesController {
///
/// Does nothing when the game isn't signed into the underlying
/// games service.
Future<void> awardAchievement(
{required String iOS, required String android}) async {
Future<void> awardAchievement({
required String iOS,
required String android,
}) async {
if (!await signedIn) {
_log.warning('Trying to award achievement when not logged in.');
return;
@@ -37,10 +39,7 @@ class GamesServicesController {
try {
await gs.GamesServices.unlock(
achievement: gs.Achievement(
androidID: android,
iOSID: iOS,
),
achievement: gs.Achievement(androidID: android, iOSID: iOS),
);
} catch (e) {
_log.severe('Cannot award achievement: $e');

View File

@@ -37,9 +37,12 @@ class Score {
buf.write('$minutes');
}
buf.write(':');
buf.write((duration.inSeconds % Duration.secondsPerMinute)
.toString()
.padLeft(2, '0'));
buf.write(
(duration.inSeconds % Duration.secondsPerMinute).toString().padLeft(
2,
'0',
),
);
return buf.toString();
}

View File

@@ -47,12 +47,15 @@ class InAppPurchaseController extends ChangeNotifier {
notifyListeners();
_log.info('Querying the store with queryProductDetails()');
final response = await inAppPurchaseInstance
.queryProductDetails({AdRemovalPurchase.productId});
final response = await inAppPurchaseInstance.queryProductDetails({
AdRemovalPurchase.productId,
});
if (response.error != null) {
_reportError('There was an error when making the purchase: '
'${response.error}');
_reportError(
'There was an error when making the purchase: '
'${response.error}',
);
return;
}
@@ -61,8 +64,10 @@ class InAppPurchaseController extends ChangeNotifier {
'Products in response: '
'${response.productDetails.map((e) => '${e.id}: ${e.title}, ').join()}',
);
_reportError('There was an error when making the purchase: '
'product ${AdRemovalPurchase.productId} does not exist?');
_reportError(
'There was an error when making the purchase: '
'product ${AdRemovalPurchase.productId} does not exist?',
);
return;
}
final productDetails = response.productDetails.single;
@@ -71,14 +76,16 @@ class InAppPurchaseController extends ChangeNotifier {
final purchaseParam = PurchaseParam(productDetails: productDetails);
try {
final success = await inAppPurchaseInstance.buyNonConsumable(
purchaseParam: purchaseParam);
purchaseParam: purchaseParam,
);
_log.info('buyNonConsumable() request was sent with success: $success');
// The result of the purchase will be reported in the purchaseStream,
// which is handled in [_listenToPurchaseUpdated].
} catch (e) {
_log.severe(
'Problem with calling inAppPurchaseInstance.buyNonConsumable(): '
'$e');
'Problem with calling inAppPurchaseInstance.buyNonConsumable(): '
'$e',
);
}
}
@@ -107,29 +114,38 @@ class InAppPurchaseController extends ChangeNotifier {
/// Subscribes to the [inAppPurchaseInstance.purchaseStream].
void subscribe() {
_subscription?.cancel();
_subscription =
inAppPurchaseInstance.purchaseStream.listen((purchaseDetailsList) {
_listenToPurchaseUpdated(purchaseDetailsList);
}, onDone: () {
_subscription?.cancel();
}, onError: (dynamic error) {
_log.severe('Error occurred on the purchaseStream: $error');
});
_subscription = inAppPurchaseInstance.purchaseStream.listen(
(purchaseDetailsList) {
_listenToPurchaseUpdated(purchaseDetailsList);
},
onDone: () {
_subscription?.cancel();
},
onError: (dynamic error) {
_log.severe('Error occurred on the purchaseStream: $error');
},
);
}
Future<void> _listenToPurchaseUpdated(
List<PurchaseDetails> purchaseDetailsList) async {
List<PurchaseDetails> purchaseDetailsList,
) async {
for (final purchaseDetails in purchaseDetailsList) {
_log.info(() => 'New PurchaseDetails instance received: '
'productID=${purchaseDetails.productID}, '
'status=${purchaseDetails.status}, '
'purchaseID=${purchaseDetails.purchaseID}, '
'error=${purchaseDetails.error}, '
'pendingCompletePurchase=${purchaseDetails.pendingCompletePurchase}');
_log.info(
() =>
'New PurchaseDetails instance received: '
'productID=${purchaseDetails.productID}, '
'status=${purchaseDetails.status}, '
'purchaseID=${purchaseDetails.purchaseID}, '
'error=${purchaseDetails.error}, '
'pendingCompletePurchase=${purchaseDetails.pendingCompletePurchase}',
);
if (purchaseDetails.productID != AdRemovalPurchase.productId) {
_log.severe("The handling of the product with id "
"'${purchaseDetails.productID}' is not implemented.");
_log.severe(
"The handling of the product with id "
"'${purchaseDetails.productID}' is not implemented.",
);
_adRemoval = const AdRemovalPurchase.notStarted();
notifyListeners();
continue;
@@ -151,7 +167,8 @@ class InAppPurchaseController extends ChangeNotifier {
} else {
_log.severe('Purchase verification failed: $purchaseDetails');
_adRemoval = AdRemovalPurchase.error(
StateError('Purchase could not be verified'));
StateError('Purchase could not be verified'),
);
notifyListeners();
}
case PurchaseStatus.error:

View File

@@ -31,8 +31,10 @@ class LevelSelectionScreen extends StatelessWidget {
child: Center(
child: Text(
'Select level',
style:
TextStyle(fontFamily: 'Permanent Marker', fontSize: 30),
style: TextStyle(
fontFamily: 'Permanent Marker',
fontSize: 30,
),
),
),
),
@@ -42,18 +44,20 @@ class LevelSelectionScreen extends StatelessWidget {
children: [
for (final level in gameLevels)
ListTile(
enabled: playerProgress.highestLevelReached >=
enabled:
playerProgress.highestLevelReached >=
level.number - 1,
onTap: () {
final audioController = context.read<AudioController>();
audioController.playSfx(SfxType.buttonTap);
GoRouter.of(context)
.go('/play/session/${level.number}');
GoRouter.of(
context,
).go('/play/session/${level.number}');
},
leading: Text(level.number.toString()),
title: Text('Level #${level.number}'),
)
),
],
),
),

View File

@@ -12,10 +12,7 @@ const gameLevels = [
// You get this string when you configure an achievement in Play Console.
achievementIdAndroid: 'NhkIwB69ejkMAOOLDb',
),
GameLevel(
number: 2,
difficulty: 42,
),
GameLevel(number: 2, difficulty: 42),
GameLevel(
number: 3,
difficulty: 100,
@@ -42,8 +39,9 @@ class GameLevel {
this.achievementIdIOS,
this.achievementIdAndroid,
}) : assert(
(achievementIdAndroid != null && achievementIdIOS != null) ||
(achievementIdAndroid == null && achievementIdIOS == null),
'Either both iOS and Android achievement ID must be provided, '
'or none');
(achievementIdAndroid != null && achievementIdIOS != null) ||
(achievementIdAndroid == null && achievementIdIOS == null),
'Either both iOS and Android achievement ID must be provided, '
'or none',
);
}

View File

@@ -48,10 +48,9 @@ class _PlaySessionScreenState extends State<PlaySessionScreen> {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => LevelState(
goal: widget.level.difficulty,
onWin: _playerWon,
),
create:
(context) =>
LevelState(goal: widget.level.difficulty, onWin: _playerWon),
),
],
child: IgnorePointer(
@@ -76,17 +75,22 @@ class _PlaySessionScreenState extends State<PlaySessionScreen> {
),
),
const Spacer(),
Text('Drag the slider to ${widget.level.difficulty}%'
' or above!'),
Text(
'Drag the slider to ${widget.level.difficulty}%'
' or above!',
),
Consumer<LevelState>(
builder: (context, levelState, child) => Slider(
label: 'Level Progress',
autofocus: true,
value: levelState.progress / 100,
onChanged: (value) =>
levelState.setProgress((value * 100).round()),
onChangeEnd: (value) => levelState.evaluate(),
),
builder:
(context, levelState, child) => Slider(
label: 'Level Progress',
autofocus: true,
value: levelState.progress / 100,
onChanged:
(value) => levelState.setProgress(
(value * 100).round(),
),
onChangeEnd: (value) => levelState.evaluate(),
),
),
const Spacer(),
Padding(
@@ -106,9 +110,7 @@ class _PlaySessionScreenState extends State<PlaySessionScreen> {
child: Visibility(
visible: _duringCelebration,
child: IgnorePointer(
child: Confetti(
isStopped: !_duringCelebration,
),
child: Confetti(isStopped: !_duringCelebration),
),
),
),

View File

@@ -9,9 +9,11 @@ import 'settings.dart';
void showCustomNameDialog(BuildContext context) {
showGeneralDialog(
context: context,
pageBuilder: (context, animation, secondaryAnimation) =>
CustomNameDialog(animation: animation));
context: context,
pageBuilder:
(context, animation, secondaryAnimation) =>
CustomNameDialog(animation: animation),
);
}
class CustomNameDialog extends StatefulWidget {

View File

@@ -23,7 +23,7 @@ class SettingsController {
/// Creates a new instance of [SettingsController] backed by [persistence].
SettingsController({required SettingsPersistence persistence})
: _persistence = persistence;
: _persistence = persistence;
/// Asynchronously loads values from the injected persistence store.
Future<void> loadStateFromPersistence() async {

View File

@@ -39,52 +39,49 @@ class SettingsScreen extends StatelessWidget {
),
),
_gap,
const _NameChangeLine(
'Name',
),
const _NameChangeLine('Name'),
ValueListenableBuilder<bool>(
valueListenable: settings.soundsOn,
builder: (context, soundsOn, child) => _SettingsLine(
'Sound FX',
Icon(soundsOn ? Icons.graphic_eq : Icons.volume_off),
onSelected: () => settings.toggleSoundsOn(),
),
builder:
(context, soundsOn, child) => _SettingsLine(
'Sound FX',
Icon(soundsOn ? Icons.graphic_eq : Icons.volume_off),
onSelected: () => settings.toggleSoundsOn(),
),
),
ValueListenableBuilder<bool>(
valueListenable: settings.musicOn,
builder: (context, musicOn, child) => _SettingsLine(
'Music',
Icon(musicOn ? Icons.music_note : Icons.music_off),
onSelected: () => settings.toggleMusicOn(),
),
builder:
(context, musicOn, child) => _SettingsLine(
'Music',
Icon(musicOn ? Icons.music_note : Icons.music_off),
onSelected: () => settings.toggleMusicOn(),
),
),
Consumer<InAppPurchaseController?>(
builder: (context, inAppPurchase, child) {
if (inAppPurchase == null) {
// In-app purchases are not supported yet.
// Go to lib/main.dart and uncomment the lines that create
// the InAppPurchaseController.
return const SizedBox.shrink();
}
builder: (context, inAppPurchase, child) {
if (inAppPurchase == null) {
// In-app purchases are not supported yet.
// Go to lib/main.dart and uncomment the lines that create
// the InAppPurchaseController.
return const SizedBox.shrink();
}
Widget icon;
VoidCallback? callback;
if (inAppPurchase.adRemoval.active) {
icon = const Icon(Icons.check);
} else if (inAppPurchase.adRemoval.pending) {
icon = const CircularProgressIndicator();
} else {
icon = const Icon(Icons.ad_units);
callback = () {
inAppPurchase.buy();
};
}
return _SettingsLine(
'Remove ads',
icon,
onSelected: callback,
);
}),
Widget icon;
VoidCallback? callback;
if (inAppPurchase.adRemoval.active) {
icon = const Icon(Icons.check);
} else if (inAppPurchase.adRemoval.pending) {
icon = const CircularProgressIndicator();
} else {
icon = const Icon(Icons.ad_units);
callback = () {
inAppPurchase.buy();
};
}
return _SettingsLine('Remove ads', icon, onSelected: callback);
},
),
_SettingsLine(
'Reset progress',
const Icon(Icons.delete),
@@ -94,7 +91,8 @@ class SettingsScreen extends StatelessWidget {
final messenger = ScaffoldMessenger.of(context);
messenger.showSnackBar(
const SnackBar(
content: Text('Player progress has been reset.')),
content: Text('Player progress has been reset.'),
),
);
},
),
@@ -129,21 +127,24 @@ class _NameChangeLine extends StatelessWidget {
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(title,
style: const TextStyle(
fontFamily: 'Permanent Marker',
fontSize: 30,
)),
Text(
title,
style: const TextStyle(
fontFamily: 'Permanent Marker',
fontSize: 30,
),
),
const Spacer(),
ValueListenableBuilder(
valueListenable: settings.playerName,
builder: (context, name, child) => Text(
'$name',
style: const TextStyle(
fontFamily: 'Permanent Marker',
fontSize: 30,
),
),
builder:
(context, name, child) => Text(
'$name',
style: const TextStyle(
fontFamily: 'Permanent Marker',
fontSize: 30,
),
),
),
],
),

View File

@@ -52,21 +52,21 @@ class ConfettiPainter extends CustomPainter {
final UnmodifiableListView<Color> colors;
ConfettiPainter(
{required Listenable animation, required Iterable<Color> colors})
: colors = UnmodifiableListView(colors),
super(repaint: animation);
ConfettiPainter({
required Listenable animation,
required Iterable<Color> colors,
}) : colors = UnmodifiableListView(colors),
super(repaint: animation);
@override
void paint(Canvas canvas, Size size) {
if (_size == null) {
// First time we have a size.
_snippings = List.generate(
snippingsCount,
(i) => _PaperSnipping(
frontColor: colors[i % colors.length],
bounds: size,
));
snippingsCount,
(i) =>
_PaperSnipping(frontColor: colors[i % colors.length], bounds: size),
);
}
final didResize = _size != null && _size != size;
@@ -97,10 +97,7 @@ class _ConfettiState extends State<Confetti>
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: ConfettiPainter(
colors: widget.colors,
animation: _controller,
),
painter: ConfettiPainter(colors: widget.colors, animation: _controller),
willChange: true,
child: const SizedBox.expand(),
);
@@ -181,10 +178,8 @@ class _PaperSnipping {
final paint = Paint()..style = PaintingStyle.fill;
_PaperSnipping({
required this.frontColor,
required Size bounds,
}) : _bounds = bounds;
_PaperSnipping({required this.frontColor, required Size bounds})
: _bounds = bounds;
void draw(Canvas canvas) {
if (cosA > 0) {
@@ -193,16 +188,17 @@ class _PaperSnipping {
paint.color = backColor;
}
final path = Path()
..addPolygon(
List.generate(
final path =
Path()..addPolygon(
List.generate(
4,
(index) => Offset(
position.x + corners[index].x * size,
position.y + corners[index].y * size * cosA,
)),
true,
);
position.x + corners[index].x * size,
position.y + corners[index].y * size * cosA,
),
),
true,
);
canvas.drawPath(path, paint);
}

View File

@@ -17,11 +17,7 @@ CustomTransitionPage<T> buildMyTransition<T>({
return CustomTransitionPage<T>(
child: child,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return _MyReveal(
animation: animation,
color: color,
child: child,
);
return _MyReveal(animation: animation, color: color, child: child);
},
key: key,
name: name,
@@ -90,9 +86,7 @@ class _MyRevealState extends State<_MyReveal> {
reverseCurve: Curves.easeOutCubic,
),
),
child: Container(
color: widget.color,
),
child: Container(color: widget.color),
),
AnimatedOpacity(
opacity: _finished ? 1 : 0,

View File

@@ -46,10 +46,7 @@ class ResponsiveScreen extends StatelessWidget {
children: [
SafeArea(
bottom: false,
child: Padding(
padding: padding,
child: topMessageArea,
),
child: Padding(padding: padding, child: topMessageArea),
),
Expanded(
flex: (mainAreaProminence * 100).round(),
@@ -63,10 +60,7 @@ class ResponsiveScreen extends StatelessWidget {
SafeArea(
top: false,
maintainBottomViewPadding: true,
child: Padding(
padding: padding,
child: rectangularMenuArea,
),
child: Padding(padding: padding, child: rectangularMenuArea),
),
],
);
@@ -94,10 +88,7 @@ class ResponsiveScreen extends StatelessWidget {
bottom: false,
left: false,
maintainBottomViewPadding: true,
child: Padding(
padding: padding,
child: topMessageArea,
),
child: Padding(padding: padding, child: topMessageArea),
),
Expanded(
child: SafeArea(
@@ -112,7 +103,7 @@ class ResponsiveScreen extends StatelessWidget {
),
),
),
)
),
],
),
),

View File

@@ -8,11 +8,10 @@ import 'package:flutter/material.dart';
/// with global key [scaffoldMessengerKey] is anywhere in the widget tree.
void showSnackBar(String message) {
final messenger = scaffoldMessengerKey.currentState;
messenger?.showSnackBar(
SnackBar(content: Text(message)),
);
messenger?.showSnackBar(SnackBar(content: Text(message)));
}
/// Use this when creating [MaterialApp] if you want [showSnackBar] to work.
final GlobalKey<ScaffoldMessengerState> scaffoldMessengerKey =
GlobalKey(debugLabel: 'scaffoldMessengerKey');
final GlobalKey<ScaffoldMessengerState> scaffoldMessengerKey = GlobalKey(
debugLabel: 'scaffoldMessengerKey',
);

View File

@@ -16,10 +16,7 @@ import '../style/responsive_screen.dart';
class WinGameScreen extends StatelessWidget {
final Score score;
const WinGameScreen({
super.key,
required this.score,
});
const WinGameScreen({super.key, required this.score});
@override
Widget build(BuildContext context) {
@@ -37,11 +34,7 @@ class WinGameScreen extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
if (adsControllerAvailable && !adsRemoved) ...[
const Expanded(
child: Center(
child: BannerAdWidget(),
),
),
const Expanded(child: Center(child: BannerAdWidget())),
],
gap,
const Center(
@@ -56,7 +49,9 @@ class WinGameScreen extends StatelessWidget {
'Score: ${score.score}\n'
'Time: ${score.formattedTime}',
style: const TextStyle(
fontFamily: 'Permanent Marker', fontSize: 20),
fontFamily: 'Permanent Marker',
fontSize: 20,
),
),
),
],