From 7cecc79940bdc2637f5102a865560d7a50753b6f Mon Sep 17 00:00:00 2001 From: William Verhaeghe Date: Wed, 22 Jan 2025 20:50:46 +0100 Subject: [PATCH 1/4] Added a new Fortune wheel --- .fvm/fvm_config.json | 3 +- .fvmrc | 2 +- .gitignore | 4 +- .vscode/settings.json | 20 ++++---- .../raffle/raffle_winner_picker_screen.dart | 16 ++++-- lib/theme/theme_duration.dart | 3 +- .../raffle_winner_picker_viewmodel.dart | 24 +++++---- lib/widget/raffle/custom_fortune_wheel.dart | 51 ++++++------------- pubspec.lock | 42 +++++++-------- pubspec.yaml | 2 +- 10 files changed, 78 insertions(+), 89 deletions(-) diff --git a/.fvm/fvm_config.json b/.fvm/fvm_config.json index 36eb0ad..c0b314b 100644 --- a/.fvm/fvm_config.json +++ b/.fvm/fvm_config.json @@ -1,4 +1,3 @@ { - "flutterSdkVersion": "3.19.3", - "flavors": {} + "flutterSdkVersion": "3.27.3" } \ No newline at end of file diff --git a/.fvmrc b/.fvmrc index f79f9b4..74c2c15 100644 --- a/.fvmrc +++ b/.fvmrc @@ -1,4 +1,4 @@ { - "flutter": "3.19.3", + "flutter": "3.27.3", "flavors": {} } \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4efc4b5..15a9b4d 100644 --- a/.gitignore +++ b/.gitignore @@ -43,7 +43,6 @@ app.*.map.json /android/app/release # fvm -.fvm/flutter_sdk #=======PROD======= # Dart @@ -60,3 +59,6 @@ app.*.map.json # iOS **/GoogleService-Info.plist **.mobileprovision + +# FVM Version Cache +.fvm/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index c7e5b89..ddca12c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,13 +1,11 @@ { - "dart.flutterSdkPath": ".fvm/flutter_sdk", - "dart.sdkPath": ".fvm/flutter_sdk/bin/cache/dart-sdk", - "dart.lineLength": 180, - // Remove .fvm files from search - "search.exclude": { - "**/.fvm": true - }, - // Remove from file watching - "files.watcherExclude": { - "**/.fvm": true - } + "dart.flutterSdkPath": ".fvm/versions/3.27.3", + "dart.sdkPath": ".fvm/flutter_sdk/bin/cache/dart-sdk", + "dart.lineLength": 180, + "search.exclude": { + "**/.fvm": true + }, + "files.watcherExclude": { + "**/.fvm": true + } } \ No newline at end of file diff --git a/lib/screen/raffle/raffle_winner_picker_screen.dart b/lib/screen/raffle/raffle_winner_picker_screen.dart index 097eb71..5c58bcb 100644 --- a/lib/screen/raffle/raffle_winner_picker_screen.dart +++ b/lib/screen/raffle/raffle_winner_picker_screen.dart @@ -1,25 +1,30 @@ +import 'package:flutter_belgium/di/injectable.dart'; import 'package:flutter_belgium/theme/theme_colors.dart'; import 'package:flutter_belgium/viewmodel/raffle/raffle_winner_picker_viewmodel.dart'; import 'package:flutter_belgium/widget/general/button.dart'; +import 'package:flutter_belgium/widget/provider/provider_widget.dart'; import 'package:flutter_belgium/widget/raffle/custom_confetti.dart'; import 'package:flutter_belgium/widget/raffle/custom_fortune_wheel.dart'; import 'package:flutter_navigation_generator_annotations/flutter_navigation_generator_annotations.dart'; -import 'package:flutter_belgium/di/injectable.dart'; -import 'package:flutter_belgium/widget/provider/provider_widget.dart'; import 'package:impaktfull_architecture/impaktfull_architecture.dart'; @FlutterRoute( navigationType: NavigationType.push, ) -class RaffleWinnerPickerScreen extends StatelessWidget { +class RaffleWinnerPickerScreen extends StatefulWidget { const RaffleWinnerPickerScreen({ super.key, }); + @override + State createState() => _RaffleWinnerPickerScreenState(); +} + +class _RaffleWinnerPickerScreenState extends State with SingleTickerProviderStateMixin { @override Widget build(BuildContext context) { return ProviderWidget( - create: () => getIt()..init(), + create: () => getIt()..init(this), builder: (context, viewModel) => ImpaktfullScreen( child: Builder(builder: (context) { if (viewModel.hasInactiveRaffle) { @@ -56,7 +61,8 @@ class RaffleWinnerPickerScreen extends StatelessWidget { ); } return CustomFortuneWheel( - selected: viewModel.selectedIndexStream, + winnerIndex: viewModel.raffleWinnerIndex, + animation: viewModel.raffleAnimation, participants: viewModel.participants, ); }, diff --git a/lib/theme/theme_duration.dart b/lib/theme/theme_duration.dart index 3d54353..7290572 100644 --- a/lib/theme/theme_duration.dart +++ b/lib/theme/theme_duration.dart @@ -2,7 +2,6 @@ class ThemeDuration { const ThemeDuration._(); static const confettiDuration = Duration(seconds: 5); - static const raffleWheelDuration = Duration(seconds: 3); - static const nextRoundDelayDuration = Duration(seconds: 5); + static const raffleWheelDuration = Duration(seconds: 3); } diff --git a/lib/viewmodel/raffle/raffle_winner_picker_viewmodel.dart b/lib/viewmodel/raffle/raffle_winner_picker_viewmodel.dart index 7524be9..966e999 100644 --- a/lib/viewmodel/raffle/raffle_winner_picker_viewmodel.dart +++ b/lib/viewmodel/raffle/raffle_winner_picker_viewmodel.dart @@ -7,6 +7,7 @@ import 'package:flutter_belgium/model/data/raffle/raffle.dart'; import 'package:flutter_belgium/navigator/main_navigator.dart'; import 'package:flutter_belgium/repo/raffle/raffle_repo.dart'; import 'package:flutter_belgium/theme/theme_duration.dart'; +import 'package:flutter_crazy_fortune_wheel/flutter_crazy_fortune_wheel.dart'; import 'package:impaktfull_architecture/impaktfull_architecture.dart'; @injectable @@ -15,7 +16,9 @@ class RaffleWinnerPickerViewModel with ChangeNotifier { final MainNavigator _mainNavigator; final _confettiController = ConfettiController(); - final _selectedIndexStreamController = StreamController.broadcast(); + late final AnimationController _raffleAnimationController; + late final Animation _raffleAnimation; + int _raffleWinnerIndex = 0; StreamSubscription? _subscription; Raffle? _raffle; @@ -27,7 +30,9 @@ class RaffleWinnerPickerViewModel with ChangeNotifier { ConfettiController get confettiController => _confettiController; - Stream get selectedIndexStream => _selectedIndexStreamController.stream; + Animation get raffleAnimation => _raffleAnimation; + + int get raffleWinnerIndex => _raffleWinnerIndex; int get minRequiredParticipants => 2; @@ -46,7 +51,9 @@ class RaffleWinnerPickerViewModel with ChangeNotifier { this._mainNavigator, ); - void init() { + void init(TickerProvider vsync) { + _raffleAnimationController = AnimationController(vsync: vsync, duration: ThemeDuration.raffleWheelDuration); + _raffleAnimation = CurvedAnimation(parent: _raffleAnimationController, curve: FortuneWheelCurve()); _subscription?.cancel(); _subscription = _raffleRepository.getRaffle().listen((raffle) { final winnerIds = raffle?.winners.map((e) => e.userUid) ?? []; @@ -61,7 +68,7 @@ class RaffleWinnerPickerViewModel with ChangeNotifier { @override void dispose() { _subscription?.cancel(); - _selectedIndexStreamController.close(); + _raffleAnimationController.dispose(); super.dispose(); } @@ -78,18 +85,17 @@ class RaffleWinnerPickerViewModel with ChangeNotifier { } _winner = null; _lockedParticipants = participants; + _raffleWinnerIndex = Random.secure().nextInt(participants.length); notifyListeners(); - final winnerIndex = Random().nextInt(participants.length); - final winner = participants[winnerIndex]; - _selectedIndexStreamController.add(winnerIndex); - await Future.delayed(ThemeDuration.raffleWheelDuration); + final winner = participants[_raffleWinnerIndex]; + await _raffleAnimationController.forward(from: 0); _raffleRepository.setWinner(raffleId: raffleId, winner: winner); _winner = winner; notifyListeners(); _confettiController.play(); await Future.delayed(ThemeDuration.confettiDuration); _confettiController.stop(); - await Future.delayed(ThemeDuration.nextRoundDelayDuration); + _raffleAnimationController.reset(); _winner = null; _lockedParticipants = null; notifyListeners(); diff --git a/lib/widget/raffle/custom_fortune_wheel.dart b/lib/widget/raffle/custom_fortune_wheel.dart index 2bc93f7..8a9ec50 100644 --- a/lib/widget/raffle/custom_fortune_wheel.dart +++ b/lib/widget/raffle/custom_fortune_wheel.dart @@ -1,58 +1,37 @@ -import 'dart:math'; - import 'package:flutter/material.dart'; import 'package:flutter_belgium/model/data/raffle/participant.dart'; import 'package:flutter_belgium/theme/theme_colors.dart'; -import 'package:flutter_belgium/theme/theme_duration.dart'; -import 'package:flutter_fortune_wheel/flutter_fortune_wheel.dart'; +import 'package:flutter_crazy_fortune_wheel/flutter_crazy_fortune_wheel.dart'; class CustomFortuneWheel extends StatelessWidget { - final Stream selected; + final int winnerIndex; + final Animation animation; final List participants; const CustomFortuneWheel({ - required this.selected, + required this.winnerIndex, + required this.animation, required this.participants, super.key, }); @override Widget build(BuildContext context) { - return FortuneWheel( - selected: selected, - animateFirst: false, - duration: ThemeDuration.raffleWheelDuration, - rotationCount: Random().nextInt(10) + 20, - indicators: const [ - FortuneIndicator( - alignment: Alignment.topCenter, - child: TriangleIndicator( - color: ThemeColors.primary, - ), - ), - ], - physics: CircularPanPhysics( - duration: const Duration(seconds: 1), - curve: Curves.decelerate, - ), - items: [ - for (final participant in participants) - FortuneItem( - child: Text( + return RandomWheel( + animation: animation, + winnerIndex: winnerIndex, + children: participants + .map( + (participant) => Text( participant.name, - style: TextStyle( + style: const TextStyle( color: ThemeColors.primary, - fontSize: participants.length > 30 ? 16 : 24, + fontSize: 24, fontWeight: FontWeight.bold, ), ), - style: const FortuneItemStyle( - color: ThemeColors.primaryUltraLight, // <-- custom circle slice fill color - borderColor: ThemeColors.primary, - borderWidth: 2, - ), - ), - ], + ) + .toList(), ); } } diff --git a/pubspec.lock b/pubspec.lock index 393fb75..05f130a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -422,22 +422,14 @@ packages: description: flutter source: sdk version: "0.0.0" - flutter_fortune_wheel: + flutter_crazy_fortune_wheel: dependency: "direct main" description: - name: flutter_fortune_wheel - sha256: "2d8515483762f6f03766f655d88e1fb20bca947b9c8722dfd91c51aca2468596" + name: flutter_crazy_fortune_wheel + sha256: "8df1cb24e4b0db7614f323baf48eeb73dcbabff215604e741e0ca72ce29e82c2" url: "https://pub.dev" source: hosted - version: "1.3.1" - flutter_hooks: - dependency: transitive - description: - name: flutter_hooks - sha256: "6a126f703b89499818d73305e4ce1e3de33b4ae1c5512e3b8eab4b986f46774c" - url: "https://pub.dev" - source: hosted - version: "0.18.6" + version: "1.0.0" flutter_lints: dependency: "direct dev" description: @@ -515,6 +507,22 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.2" + flutter_shader_snap: + dependency: transitive + description: + name: flutter_shader_snap + sha256: a97fd2767391ca49dce96b15643aafe34dd3823da5b996e18bf06c3f7f02b513 + url: "https://pub.dev" + source: hosted + version: "0.0.3" + flutter_shaders: + dependency: transitive + description: + name: flutter_shaders + sha256: "34794acadd8275d971e02df03afee3dee0f98dbfb8c4837082ad0034f612a3e2" + url: "https://pub.dev" + source: hosted + version: "0.1.3" flutter_svg: dependency: transitive description: @@ -957,14 +965,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.3" - quiver: - dependency: transitive - description: - name: quiver - sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47 - url: "https://pub.dev" - source: hosted - version: "3.2.1" recase: dependency: transitive description: @@ -1403,5 +1403,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.6.0 <4.0.0" + dart: ">=3.6.1 <4.0.0" flutter: ">=3.27.0" diff --git a/pubspec.yaml b/pubspec.yaml index de46bae..61032fe 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: firebase_remote_config: ^4.3.14 flutter: sdk: flutter - flutter_fortune_wheel: ^1.3.0 + flutter_crazy_fortune_wheel: ^1.0.0 flutter_localizations: sdk: flutter flutter_navigation_generator_annotations: ^1.0.1 From fcb88a19de677435554e1044e2f33002c5cf49ea Mon Sep 17 00:00:00 2001 From: William Verhaeghe Date: Thu, 23 Jan 2025 20:26:49 +0100 Subject: [PATCH 2/4] Fix missing import --- lib/viewmodel/raffle/raffle_winner_picker_viewmodel.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/viewmodel/raffle/raffle_winner_picker_viewmodel.dart b/lib/viewmodel/raffle/raffle_winner_picker_viewmodel.dart index a753331..75bfc4b 100644 --- a/lib/viewmodel/raffle/raffle_winner_picker_viewmodel.dart +++ b/lib/viewmodel/raffle/raffle_winner_picker_viewmodel.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:math'; import 'package:confetti/confetti.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_belgium/model/data/raffle/participant.dart'; import 'package:flutter_belgium/model/data/raffle/raffle.dart'; import 'package:flutter_belgium/navigator/main_navigator.dart'; From 3ca8e4ea2f3f0c28d31312c3f7357dc68383fdf5 Mon Sep 17 00:00:00 2001 From: William Verhaeghe Date: Thu, 23 Jan 2025 20:35:21 +0100 Subject: [PATCH 3/4] Fix issues blocking from running on web --- lib/repo/remote_config.dart | 7 ++++++- lib/screen/login/login_screen.dart | 7 ++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/repo/remote_config.dart b/lib/repo/remote_config.dart index 3821089..80146da 100644 --- a/lib/repo/remote_config.dart +++ b/lib/repo/remote_config.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:firebase_remote_config/firebase_remote_config.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter_belgium/model/data/remote_config/remote_config_data.dart'; import 'package:flutter_belgium/util/flavor/flavor_config.dart'; import 'package:impaktfull_architecture/impaktfull_architecture.dart'; @@ -34,7 +35,11 @@ class AppRemoteConfigRepository extends ImpaktfullRemoteConfigRepository getDefault() async => RemoteConfigData( latestVersionCode: 1, minVersionCode: 1, - updateUrl: Platform.isAndroid ? 'https://play.google.com/store/apps/details?id=be.flutterbelgium.app' : 'https://apps.apple.com/us/app/flutter-belgium/id6479450596', + updateUrl: kIsWeb + ? '' + : Platform.isAndroid + ? 'https://play.google.com/store/apps/details?id=be.flutterbelgium.app' + : 'https://apps.apple.com/us/app/flutter-belgium/id6479450596', adminIds: [], ); diff --git a/lib/screen/login/login_screen.dart b/lib/screen/login/login_screen.dart index 8c88c6e..f8b23fc 100644 --- a/lib/screen/login/login_screen.dart +++ b/lib/screen/login/login_screen.dart @@ -1,13 +1,14 @@ import 'dart:io'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_belgium/di/injectable.dart'; import 'package:flutter_belgium/model/data/login/login_type.dart'; import 'package:flutter_belgium/theme/theme_assets.dart'; import 'package:flutter_belgium/viewmodel/login/login_viewmodel.dart'; +import 'package:flutter_belgium/widget/provider/provider_widget.dart'; import 'package:flutter_belgium/widget/social_login/social_login_button.dart'; import 'package:flutter_navigation_generator_annotations/flutter_navigation_generator_annotations.dart'; -import 'package:flutter_belgium/di/injectable.dart'; -import 'package:flutter_belgium/widget/provider/provider_widget.dart'; import 'package:impaktfull_architecture/impaktfull_architecture.dart'; @FlutterRoute( @@ -45,7 +46,7 @@ class LoginScreen extends StatelessWidget { onTap: viewModel.onLoginTapped, loginType: LoginType.github, ), - if (Platform.isIOS) ...[ + if (!kIsWeb && Platform.isIOS) ...[ SocialLoginButton( onTap: viewModel.onLoginTapped, loginType: LoginType.apple, From 43546a39206e408931d436e8c9af9374bd4cbac5 Mon Sep 17 00:00:00 2001 From: William Verhaeghe Date: Thu, 23 Jan 2025 21:05:10 +0100 Subject: [PATCH 4/4] Updated spinner --- lib/screen/raffle/raffle_winner_picker_screen.dart | 2 +- lib/theme/theme_duration.dart | 2 +- lib/viewmodel/raffle/raffle_winner_picker_viewmodel.dart | 2 +- lib/widget/raffle/add_participant_dialog.dart | 2 ++ lib/widget/raffle/custom_fortune_wheel.dart | 1 + pubspec.lock | 6 +++--- pubspec.yaml | 4 +++- 7 files changed, 12 insertions(+), 7 deletions(-) diff --git a/lib/screen/raffle/raffle_winner_picker_screen.dart b/lib/screen/raffle/raffle_winner_picker_screen.dart index 6e5b80e..c004f4e 100644 --- a/lib/screen/raffle/raffle_winner_picker_screen.dart +++ b/lib/screen/raffle/raffle_winner_picker_screen.dart @@ -1,5 +1,5 @@ -import 'package:flutter_belgium/di/injectable.dart'; import 'package:flutter/widgets.dart'; +import 'package:flutter_belgium/di/injectable.dart'; import 'package:flutter_belgium/theme/theme_colors.dart'; import 'package:flutter_belgium/viewmodel/raffle/raffle_winner_picker_viewmodel.dart'; import 'package:flutter_belgium/widget/general/button.dart'; diff --git a/lib/theme/theme_duration.dart b/lib/theme/theme_duration.dart index 7290572..fd87b8d 100644 --- a/lib/theme/theme_duration.dart +++ b/lib/theme/theme_duration.dart @@ -3,5 +3,5 @@ class ThemeDuration { static const confettiDuration = Duration(seconds: 5); - static const raffleWheelDuration = Duration(seconds: 3); + static const raffleWheelDuration = Duration(seconds: 10); } diff --git a/lib/viewmodel/raffle/raffle_winner_picker_viewmodel.dart b/lib/viewmodel/raffle/raffle_winner_picker_viewmodel.dart index 75bfc4b..985dbd2 100644 --- a/lib/viewmodel/raffle/raffle_winner_picker_viewmodel.dart +++ b/lib/viewmodel/raffle/raffle_winner_picker_viewmodel.dart @@ -114,7 +114,7 @@ class RaffleWinnerPickerViewModel extends ChangeNotifierEx { Future onAddParticipantTapped() async { final docId = _raffle?.id; if (docId == null) { - _mainNavigator.showErrorMessage('Failed to make raffle active (no raffle available)'); + _mainNavigator.showErrorMessage('Failed to add participant (no raffle available)'); return; } final name = await _mainNavigator.goToAddParticipantDialog(); diff --git a/lib/widget/raffle/add_participant_dialog.dart b/lib/widget/raffle/add_participant_dialog.dart index 841153b..b99e364 100644 --- a/lib/widget/raffle/add_participant_dialog.dart +++ b/lib/widget/raffle/add_participant_dialog.dart @@ -38,7 +38,9 @@ class _AddParticipantDialogState extends State { ], child: TextField( controller: textController, + onSubmitted: (_) => Navigator.of(context).pop(textController.text), cursorColor: ThemeColors.primary, + focusNode: FocusNode()..requestFocus(), decoration: const InputDecoration( hintText: 'Name', focusedBorder: UnderlineInputBorder( diff --git a/lib/widget/raffle/custom_fortune_wheel.dart b/lib/widget/raffle/custom_fortune_wheel.dart index 8a9ec50..3bc574d 100644 --- a/lib/widget/raffle/custom_fortune_wheel.dart +++ b/lib/widget/raffle/custom_fortune_wheel.dart @@ -20,6 +20,7 @@ class CustomFortuneWheel extends StatelessWidget { return RandomWheel( animation: animation, winnerIndex: winnerIndex, + wheelType: WheelType.values[participants.length % WheelType.values.length], children: participants .map( (participant) => Text( diff --git a/pubspec.lock b/pubspec.lock index 4e0ebcd..b63fb55 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -439,10 +439,10 @@ packages: dependency: "direct main" description: name: flutter_crazy_fortune_wheel - sha256: "8df1cb24e4b0db7614f323baf48eeb73dcbabff215604e741e0ca72ce29e82c2" + sha256: a6b299961a4bed63a3244462bd2512296f78bd37170a350022c8c332d5fcf13f url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.1" flutter_lints: dependency: "direct dev" description: @@ -1472,5 +1472,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.6.1 <4.0.0" + dart: ">=3.6.0 <4.0.0" flutter: ">=3.27.0" diff --git a/pubspec.yaml b/pubspec.yaml index 3e2d467..83266c4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: firebase_remote_config: ^5.3.0 flutter: sdk: flutter - flutter_crazy_fortune_wheel: ^1.0.0 + flutter_crazy_fortune_wheel: ^1.0.1 flutter_localizations: sdk: flutter flutter_navigation_generator_annotations: ^2.1.0 @@ -51,6 +51,8 @@ flutter: - family: Ubuntu fonts: - asset: assets/font/ubuntu/ubuntu_regular.ttf + shaders: + - packages/flutter_crazy_fortune_wheel/shaders/sliced_wheel_shader.frag locale_gen: default_language: "en"