├── icons.sketch ├── ios ├── Runner │ ├── Runner-Bridging-Header.h │ ├── Assets.xcassets │ │ ├── LaunchImage.imageset │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ ├── README.md │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── Info.plist ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist ├── .gitignore ├── GoogleService-Info.plist ├── Podfile └── Podfile.lock ├── web ├── favicon.png ├── icons │ ├── Icon-192.png │ └── Icon-512.png ├── manifest.json └── index.html ├── launcher-icon.sketch ├── assets ├── fonts │ ├── icons.ttf │ ├── AquaIcons.ttf │ ├── Poppins-Black.ttf │ ├── Poppins-Bold.ttf │ ├── Poppins-Light.ttf │ ├── Poppins-Thin.ttf │ ├── WorkSans-Bold.ttf │ ├── WorkSans-Thin.ttf │ ├── Poppins-Italic.ttf │ ├── Poppins-Medium.ttf │ ├── Poppins-Regular.ttf │ ├── Poppins-SemiBold.ttf │ ├── WorkSans-Black.ttf │ ├── WorkSans-Italic.ttf │ ├── WorkSans-Light.ttf │ ├── WorkSans-Medium.ttf │ ├── WorkSans-Regular.ttf │ ├── Poppins-BoldItalic.ttf │ ├── Poppins-ExtraBold.ttf │ ├── Poppins-ExtraLight.ttf │ ├── Poppins-ThinItalic.ttf │ ├── SourceCodePro-Bold.ttf │ ├── WorkSans-ExtraBold.ttf │ ├── WorkSans-SemiBold.ttf │ ├── Poppins-BlackItalic.ttf │ ├── Poppins-LightItalic.ttf │ ├── Poppins-MediumItalic.ttf │ ├── SourceCodePro-Black.ttf │ ├── SourceCodePro-Italic.ttf │ ├── SourceCodePro-Light.ttf │ ├── SourceCodePro-Medium.ttf │ ├── SourceCodePro-Regular.ttf │ ├── WorkSans-BlackItalic.ttf │ ├── WorkSans-BoldItalic.ttf │ ├── WorkSans-ExtraLight.ttf │ ├── WorkSans-LightItalic.ttf │ ├── WorkSans-MediumItalic.ttf │ ├── WorkSans-ThinItalic.ttf │ ├── Poppins-ExtraBoldItalic.ttf │ ├── Poppins-SemiBoldItalic.ttf │ ├── SourceCodePro-SemiBold.ttf │ ├── WorkSans-SemiBoldItalic.ttf │ ├── Poppins-ExtraLightItalic.ttf │ ├── SourceCodePro-BlackItalic.ttf │ ├── SourceCodePro-BoldItalic.ttf │ ├── SourceCodePro-ExtraLight.ttf │ ├── SourceCodePro-LightItalic.ttf │ ├── SourceCodePro-MediumItalic.ttf │ ├── WorkSans-ExtraBoldItalic.ttf │ ├── WorkSans-ExtraLightItalic.ttf │ ├── SourceCodePro-SemiBoldItalic.ttf │ └── SourceCodePro-ExtraLightItalic.ttf └── launcher-icon │ ├── icon@1024px.png │ ├── adaptive-icon-background@432px.png │ └── adaptive-icon-foreground@432px.png ├── android ├── gradle.properties ├── app │ ├── src │ │ ├── main │ │ │ ├── kotlin │ │ │ │ └── app │ │ │ │ │ └── axross │ │ │ │ │ └── aqua │ │ │ │ │ └── MainActivity.kt │ │ │ ├── res │ │ │ │ ├── drawable │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-v21 │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── values │ │ │ │ │ └── styles.xml │ │ │ │ └── values-night │ │ │ │ │ └── styles.xml │ │ │ └── AndroidManifest.xml │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── google-services.json │ └── build.gradle ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── .gitignore ├── settings.gradle └── build.gradle ├── lib ├── src │ ├── models │ │ ├── anonymous_user.dart │ │ ├── hand_range_preset.dart │ │ └── calculation.dart │ ├── services │ │ ├── auth_manager_service.dart │ │ ├── analytics_service.dart │ │ ├── error_reporter_service.dart │ │ └── isolate_evaluation_service.dart │ ├── common_widgets │ │ ├── fill.dart │ │ ├── analytics.dart │ │ ├── authentication.dart │ │ ├── error_reporter.dart │ │ ├── aqua_environment.dart │ │ ├── current_simulation_session.dart │ │ ├── aqua_popup_transition.dart │ │ ├── aqua_icons.dart │ │ ├── aqua_preferences.dart │ │ ├── aqua_appear_animation.dart │ │ ├── aqua_slider.dart │ │ ├── aqua_tab_view.dart │ │ ├── playing_card.dart │ │ ├── readonly_rank_pair_grid.dart │ │ ├── aqua_theme.dart │ │ ├── aqua_scaffold.dart │ │ ├── aqua_button.dart │ │ ├── card_picker.dart │ │ ├── aqua_tab_bar.dart │ │ ├── digits_text.dart │ │ └── rank_pair_select_grid.dart │ ├── constants │ │ ├── hand.dart │ │ ├── card.dart │ │ ├── theme.dart │ │ └── hand_range.dart │ ├── view_models │ │ ├── card_pair_draft.dart │ │ ├── community_cards_draft.dart │ │ ├── hand_range_draft_list.dart │ │ ├── hand_range_draft.dart │ │ └── simulation_session.dart │ └── utilities │ │ └── system_ui_overlay_style.dart ├── app │ ├── services │ │ ├── noop_error_reporter_service.dart │ │ ├── firebase_auth_manager_service.dart │ │ ├── sentry_error_reporter_service.dart │ │ ├── flutter_isolate_evaluation_service.dart │ │ └── amplitude_analytics_service.dart │ ├── main_route.dart │ ├── main_route │ │ └── preferences_tab_page.dart │ ├── app.dart │ └── preset_select_route.dart └── main.dart ├── .metadata ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── .gitignore ├── README.md ├── pubspec.yaml └── pubspec.lock /icons.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/icons.sketch -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/web/favicon.png -------------------------------------------------------------------------------- /launcher-icon.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/launcher-icon.sketch -------------------------------------------------------------------------------- /assets/fonts/icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/icons.ttf -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/web/icons/Icon-512.png -------------------------------------------------------------------------------- /assets/fonts/AquaIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/AquaIcons.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/Poppins-Black.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/Poppins-Bold.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/Poppins-Light.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/Poppins-Thin.ttf -------------------------------------------------------------------------------- /assets/fonts/WorkSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/WorkSans-Bold.ttf -------------------------------------------------------------------------------- /assets/fonts/WorkSans-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/WorkSans-Thin.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/Poppins-Italic.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/Poppins-Medium.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/Poppins-Regular.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/Poppins-SemiBold.ttf -------------------------------------------------------------------------------- /assets/fonts/WorkSans-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/WorkSans-Black.ttf -------------------------------------------------------------------------------- /assets/fonts/WorkSans-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/WorkSans-Italic.ttf -------------------------------------------------------------------------------- /assets/fonts/WorkSans-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/WorkSans-Light.ttf -------------------------------------------------------------------------------- /assets/fonts/WorkSans-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/WorkSans-Medium.ttf -------------------------------------------------------------------------------- /assets/fonts/WorkSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/WorkSans-Regular.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/Poppins-BoldItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/Poppins-ExtraBold.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/Poppins-ExtraLight.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/Poppins-ThinItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/SourceCodePro-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/SourceCodePro-Bold.ttf -------------------------------------------------------------------------------- /assets/fonts/WorkSans-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/WorkSans-ExtraBold.ttf -------------------------------------------------------------------------------- /assets/fonts/WorkSans-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/WorkSans-SemiBold.ttf -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /assets/fonts/Poppins-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/Poppins-BlackItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/Poppins-LightItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/Poppins-MediumItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/SourceCodePro-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/SourceCodePro-Black.ttf -------------------------------------------------------------------------------- /assets/fonts/SourceCodePro-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/SourceCodePro-Italic.ttf -------------------------------------------------------------------------------- /assets/fonts/SourceCodePro-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/SourceCodePro-Light.ttf -------------------------------------------------------------------------------- /assets/fonts/SourceCodePro-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/SourceCodePro-Medium.ttf -------------------------------------------------------------------------------- /assets/fonts/SourceCodePro-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/SourceCodePro-Regular.ttf -------------------------------------------------------------------------------- /assets/fonts/WorkSans-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/WorkSans-BlackItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/WorkSans-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/WorkSans-BoldItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/WorkSans-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/WorkSans-ExtraLight.ttf -------------------------------------------------------------------------------- /assets/fonts/WorkSans-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/WorkSans-LightItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/WorkSans-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/WorkSans-MediumItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/WorkSans-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/WorkSans-ThinItalic.ttf -------------------------------------------------------------------------------- /assets/launcher-icon/icon@1024px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/launcher-icon/icon@1024px.png -------------------------------------------------------------------------------- /assets/fonts/Poppins-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/Poppins-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/Poppins-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/SourceCodePro-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/SourceCodePro-SemiBold.ttf -------------------------------------------------------------------------------- /assets/fonts/WorkSans-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/WorkSans-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/Poppins-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/SourceCodePro-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/SourceCodePro-BlackItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/SourceCodePro-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/SourceCodePro-BoldItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/SourceCodePro-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/SourceCodePro-ExtraLight.ttf -------------------------------------------------------------------------------- /assets/fonts/SourceCodePro-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/SourceCodePro-LightItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/SourceCodePro-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/SourceCodePro-MediumItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/WorkSans-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/WorkSans-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/WorkSans-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/WorkSans-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/SourceCodePro-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/SourceCodePro-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/SourceCodePro-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/fonts/SourceCodePro-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /assets/launcher-icon/adaptive-icon-background@432px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/launcher-icon/adaptive-icon-background@432px.png -------------------------------------------------------------------------------- /assets/launcher-icon/adaptive-icon-foreground@432px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/assets/launcher-icon/adaptive-icon-foreground@432px.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axross/aqua/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /lib/src/models/anonymous_user.dart: -------------------------------------------------------------------------------- 1 | import "package:meta/meta.dart"; 2 | 3 | @immutable 4 | class AnonymousUser { 5 | AnonymousUser({required this.id}); 6 | 7 | final String id; 8 | } 9 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/app/axross/aqua/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package app.axross.aqua 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/src/services/auth_manager_service.dart: -------------------------------------------------------------------------------- 1 | import "package:aqua/src/models/anonymous_user.dart"; 2 | import "package:flutter/foundation.dart"; 3 | 4 | abstract class AuthManagerService extends ChangeNotifier { 5 | AnonymousUser? get user; 6 | 7 | Future initialize(); 8 | } 9 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip 7 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/src/models/hand_range_preset.dart: -------------------------------------------------------------------------------- 1 | import "package:meta/meta.dart"; 2 | import "package:poker/poker.dart"; 3 | 4 | @immutable 5 | class HandRangePreset { 6 | const HandRangePreset({ 7 | required this.name, 8 | required this.handRange, 9 | }); 10 | 11 | final String name; 12 | 13 | final HandRange handRange; 14 | } 15 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: f4abaa0735eba4dfd8f33f73363911d63931fe03 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /lib/src/services/analytics_service.dart: -------------------------------------------------------------------------------- 1 | import "package:aqua/src/models/anonymous_user.dart"; 2 | 3 | abstract class AnalyticsService { 4 | void setUser(AnonymousUser? user); 5 | 6 | void logScreenChange({ 7 | required String screenName, 8 | Map parameters = const {}, 9 | }); 10 | 11 | void logEvent({ 12 | required String name, 13 | Map parameters = const {}, 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /lib/src/services/error_reporter_service.dart: -------------------------------------------------------------------------------- 1 | import "package:aqua/src/models/anonymous_user.dart"; 2 | import "package:flutter/foundation.dart"; 3 | 4 | abstract class ErrorReporterService { 5 | void setUser(AnonymousUser? user); 6 | 7 | Future captureException({ 8 | required dynamic exception, 9 | required StackTrace stackTrace, 10 | }); 11 | 12 | Future captureFlutterException(FlutterErrorDetails details); 13 | } 14 | -------------------------------------------------------------------------------- /lib/src/common_widgets/fill.dart: -------------------------------------------------------------------------------- 1 | import "package:flutter/widgets.dart"; 2 | 3 | @immutable 4 | class Fill extends StatelessWidget { 5 | const Fill({@required this.child, Key? key}) : super(key: key); 6 | 7 | final child; 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return ConstrainedBox( 12 | constraints: BoxConstraints.loose(MediaQuery.of(context).size), 13 | child: child, 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/services/isolate_evaluation_service.dart: -------------------------------------------------------------------------------- 1 | import "package:aqua/src/models/calculation.dart"; 2 | import "package:poker/poker.dart"; 3 | 4 | abstract class IsolateEvaluationService { 5 | Stream get onProgress; 6 | 7 | Future initialize(); 8 | 9 | Future dispose(); 10 | 11 | void requestEvaluation({ 12 | required CardSet communityCards, 13 | required List players, 14 | required int times, 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/src/constants/hand.dart: -------------------------------------------------------------------------------- 1 | import "package:poker/poker.dart"; 2 | 3 | const handTypeStrings = { 4 | MadeHandType.straightFlush: "Str. Flush", 5 | MadeHandType.quads: "Four of a Kind", 6 | MadeHandType.fullHouse: "Fullhouse", 7 | MadeHandType.flush: "Flush", 8 | MadeHandType.straight: "Straight", 9 | MadeHandType.trips: "Three of a Kind", 10 | MadeHandType.twoPairs: "Two Pairs", 11 | MadeHandType.pair: "Pair", 12 | MadeHandType.highcard: "High Card", 13 | }; 14 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/src/models/calculation.dart: -------------------------------------------------------------------------------- 1 | import "package:meta/meta.dart"; 2 | import "package:poker/poker.dart"; 3 | 4 | @immutable 5 | class Calculation { 6 | Calculation({ 7 | required this.communityCards, 8 | required this.players, 9 | required this.result, 10 | required this.startedAt, 11 | required this.endedAt, 12 | }); 13 | 14 | final CardSet communityCards; 15 | 16 | final List players; 17 | 18 | final EvaluationResult result; 19 | 20 | final DateTime startedAt; 21 | 22 | final DateTime endedAt; 23 | } 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: feature-request 6 | assignees: '' 7 | 8 | --- 9 | 10 | 14 | 15 | ## ✏️ Is your feature request related to a problem? Or new one? Please describe. 16 | 17 | Please write here... 18 | 19 | ## 📸 Is there any screenshots or URL to show me? 20 | 21 | Please paste screenshots or URL here... 22 | -------------------------------------------------------------------------------- /lib/app/services/noop_error_reporter_service.dart: -------------------------------------------------------------------------------- 1 | import "package:aqua/src/models/anonymous_user.dart"; 2 | import "package:aqua/src/services/error_reporter_service.dart"; 3 | import "package:flutter/foundation.dart"; 4 | 5 | class NoopErrorReporterService implements ErrorReporterService { 6 | NoopErrorReporterService(); 7 | 8 | void setUser(AnonymousUser? user) {} 9 | 10 | Future captureException({ 11 | required dynamic exception, 12 | required StackTrace stackTrace, 13 | }) async {} 14 | 15 | Future captureFlutterException(FlutterErrorDetails details) async {} 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/common_widgets/analytics.dart: -------------------------------------------------------------------------------- 1 | import "package:aqua/src/services/analytics_service.dart"; 2 | import "package:flutter/widgets.dart"; 3 | 4 | class Analytics extends InheritedWidget { 5 | Analytics({ 6 | required this.analytics, 7 | required Widget child, 8 | Key? key, 9 | }) : super(key: key, child: child); 10 | 11 | final AnalyticsService analytics; 12 | 13 | @override 14 | bool updateShouldNotify(Analytics old) => analytics != old.analytics; 15 | 16 | static AnalyticsService of(BuildContext context) => 17 | context.dependOnInheritedWidgetOfExactType()!.analytics; 18 | } 19 | -------------------------------------------------------------------------------- /lib/src/common_widgets/authentication.dart: -------------------------------------------------------------------------------- 1 | import "package:aqua/src/services/auth_manager_service.dart"; 2 | import "package:flutter/widgets.dart"; 3 | 4 | class Authentication extends InheritedWidget { 5 | Authentication({ 6 | Key? key, 7 | required this.manager, 8 | required Widget child, 9 | }) : super(key: key, child: child); 10 | 11 | final AuthManagerService manager; 12 | 13 | @override 14 | bool updateShouldNotify(Authentication old) => manager != old.manager; 15 | 16 | static AuthManagerService of(BuildContext context) => 17 | context.dependOnInheritedWidgetOfExactType()!.manager; 18 | } 19 | -------------------------------------------------------------------------------- /lib/src/common_widgets/error_reporter.dart: -------------------------------------------------------------------------------- 1 | import "package:aqua/src/services/error_reporter_service.dart"; 2 | import "package:flutter/widgets.dart"; 3 | 4 | class ErrorReporter extends InheritedWidget { 5 | ErrorReporter({ 6 | required this.service, 7 | required Widget child, 8 | Key? key, 9 | }) : super(key: key, child: child); 10 | 11 | final ErrorReporterService service; 12 | 13 | @override 14 | bool updateShouldNotify(ErrorReporter old) => service != old.service; 15 | 16 | static ErrorReporterService of(BuildContext context) => 17 | context.dependOnInheritedWidgetOfExactType()!.service; 18 | } 19 | -------------------------------------------------------------------------------- /lib/src/constants/card.dart: -------------------------------------------------------------------------------- 1 | import "package:poker/poker.dart"; 2 | 3 | const ranksInStrongnessOrder = [ 4 | Rank.ace, 5 | Rank.king, 6 | Rank.queen, 7 | Rank.jack, 8 | Rank.ten, 9 | Rank.nine, 10 | Rank.eight, 11 | Rank.seven, 12 | Rank.six, 13 | Rank.five, 14 | Rank.four, 15 | Rank.trey, 16 | Rank.deuce, 17 | ]; 18 | 19 | const rankChars = { 20 | Rank.ace: "A", 21 | Rank.king: "K", 22 | Rank.queen: "Q", 23 | Rank.jack: "J", 24 | Rank.ten: "T", 25 | Rank.nine: "9", 26 | Rank.eight: "8", 27 | Rank.seven: "7", 28 | Rank.six: "6", 29 | Rank.five: "5", 30 | Rank.four: "4", 31 | Rank.trey: "3", 32 | Rank.deuce: "2", 33 | }; 34 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aqua", 3 | "short_name": "aqua", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/ephemeral/ 22 | Flutter/app.flx 23 | Flutter/app.zip 24 | Flutter/flutter_assets/ 25 | Flutter/flutter_export_environment.sh 26 | ServiceDefinitions.json 27 | Runner/GeneratedPluginRegistrant.* 28 | 29 | # Exceptions to above rules. 30 | !default.mode1v3 31 | !default.mode2v3 32 | !default.pbxuser 33 | !default.perspectivev3 34 | -------------------------------------------------------------------------------- /lib/src/common_widgets/aqua_environment.dart: -------------------------------------------------------------------------------- 1 | import "package:flutter/foundation.dart"; 2 | import "package:flutter/widgets.dart"; 3 | 4 | class AquaEnvironment extends InheritedWidget { 5 | AquaEnvironment({ 6 | Key? key, 7 | required Widget child, 8 | }) : data = AquaEnvironmentData(), 9 | super(key: key, child: child); 10 | 11 | final AquaEnvironmentData data; 12 | 13 | @override 14 | bool updateShouldNotify(AquaEnvironment old) => data != old.data; 15 | 16 | static AquaEnvironmentData of(BuildContext context) => 17 | context.dependOnInheritedWidgetOfExactType()!.data; 18 | } 19 | 20 | @immutable 21 | class AquaEnvironmentData { 22 | final isDebugBuild = kDebugMode; 23 | } 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | 14 | 15 | ## ✏️ Could you concisely describe about the bug? 16 | 17 | Please write here... 18 | 19 | ## 🤔 What is it expected? 20 | 21 | Please write here... 22 | 23 | ## 🏃‍♀️ Could you introduce how to reproduce the bug? 24 | 25 | 1. Tap "..." button 26 | 2. Scroll down to "..." 27 | 3. What you made should be a coffee, but it was tea actually 28 | 29 | ## 📸 Do you have screenshots? 30 | 31 | If you have, please paste here... 32 | -------------------------------------------------------------------------------- /lib/src/common_widgets/current_simulation_session.dart: -------------------------------------------------------------------------------- 1 | import "package:aqua/src/view_models/simulation_session.dart"; 2 | import "package:flutter/widgets.dart"; 3 | 4 | class CurrentSimulationSession extends InheritedWidget { 5 | CurrentSimulationSession({ 6 | required this.simulationSession, 7 | required Widget child, 8 | Key? key, 9 | }) : super(key: key, child: child); 10 | 11 | final CalculationSession simulationSession; 12 | 13 | @override 14 | bool updateShouldNotify(CurrentSimulationSession old) => 15 | simulationSession != old.simulationSession; 16 | 17 | static CalculationSession of(BuildContext context) => context 18 | .dependOnInheritedWidgetOfExactType()! 19 | .simulationSession; 20 | } 21 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.1.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | classpath 'com.google.gms:google-services:4.3.8' 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | google() 18 | jcenter() 19 | } 20 | } 21 | 22 | rootProject.buildDir = '../build' 23 | subprojects { 24 | project.buildDir = "${rootProject.buildDir}/${project.name}" 25 | project.evaluationDependsOn(':app') 26 | } 27 | 28 | task clean(type: Delete) { 29 | delete rootProject.buildDir 30 | } 31 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/src/common_widgets/aqua_popup_transition.dart: -------------------------------------------------------------------------------- 1 | import "package:flutter/widgets.dart"; 2 | 3 | class AquaPopupTransition extends AnimatedWidget { 4 | AquaPopupTransition({ 5 | Key? key, 6 | required this.animation, 7 | required this.child, 8 | }) : super(key: key, listenable: animation); 9 | 10 | final Animation animation; 11 | 12 | final Widget child; 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Opacity( 17 | opacity: Tween(begin: 0.0, end: 1.0).animate(animation).value, 18 | child: Transform.scale( 19 | scale: Tween(begin: .98, end: 1.0).animate(animation).value, 20 | child: Transform.translate( 21 | offset: Tween( 22 | begin: Offset(0.0, -8.0), 23 | end: Offset(0.0, 0.0), 24 | ).animate(animation).value, 25 | child: child, 26 | ), 27 | ), 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/app/services/firebase_auth_manager_service.dart: -------------------------------------------------------------------------------- 1 | import "package:aqua/src/models/anonymous_user.dart"; 2 | import "package:aqua/src/services/auth_manager_service.dart"; 3 | import "package:firebase_auth/firebase_auth.dart"; 4 | import "package:flutter/foundation.dart"; 5 | 6 | class FirebaseAuthManagerService extends ChangeNotifier 7 | implements AuthManagerService { 8 | FirebaseAuthManagerService(); 9 | 10 | AnonymousUser? _user; 11 | 12 | AnonymousUser? get user => _user; 13 | 14 | initialize() async { 15 | final FirebaseAuth firebaseAuth = FirebaseAuth.instance; 16 | 17 | firebaseAuth.userChanges().listen((firebaseUser) { 18 | _user = firebaseUser != null ? AnonymousUser(id: firebaseUser.uid) : null; 19 | 20 | notifyListeners(); 21 | }); 22 | 23 | try { 24 | await firebaseAuth.signInAnonymously(); 25 | } on Exception catch (error) { 26 | print(error); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | 48 | # 49 | android/app/src/main/res/drawable-*/ic_launcher*.png 50 | android/app/src/main/res/mipmap-*/ic_launcher.png 51 | android/app/src/main/res/mipmap-*/ic_launcher.xml 52 | ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-*.png 53 | -------------------------------------------------------------------------------- /lib/src/common_widgets/aqua_icons.dart: -------------------------------------------------------------------------------- 1 | import "package:flutter/widgets.dart"; 2 | 3 | abstract class AquaIcons { 4 | AquaIcons._(); 5 | 6 | static const IconData backspace = IconData(0xe800, fontFamily: _family); 7 | static const IconData cardPair = IconData(0xe801, fontFamily: _family); 8 | static const IconData check = IconData(0xe802, fontFamily: _family); 9 | static const IconData chevronLeft = IconData(0xe803, fontFamily: _family); 10 | static const IconData clock = IconData(0xe804, fontFamily: _family); 11 | static const IconData club = IconData(0xe805, fontFamily: _family); 12 | static const IconData diamond = IconData(0xe806, fontFamily: _family); 13 | static const IconData gear = IconData(0xe807, fontFamily: _family); 14 | static const IconData grid = IconData(0xe808, fontFamily: _family); 15 | static const IconData heart = IconData(0xe809, fontFamily: _family); 16 | static const IconData percent = IconData(0xe80a, fontFamily: _family); 17 | static const IconData plus = IconData(0xe80b, fontFamily: _family); 18 | static const IconData save = IconData(0xe80c, fontFamily: _family); 19 | static const IconData spade = IconData(0xe80d, fontFamily: _family); 20 | static const IconData trash = IconData(0xe80e, fontFamily: _family); 21 | } 22 | 23 | const _family = "AquaIcons"; 24 | -------------------------------------------------------------------------------- /lib/src/view_models/card_pair_draft.dart: -------------------------------------------------------------------------------- 1 | import "package:flutter/foundation.dart"; 2 | import "package:poker/poker.dart"; 3 | 4 | class CardPairDraft extends ChangeNotifier { 5 | CardPairDraft(this._a, this._b) : assert(_a != _b); 6 | 7 | CardPairDraft.empty(); 8 | 9 | Card? _a; 10 | 11 | Card? _b; 12 | 13 | bool get isComplete => _a != null && _b != null; 14 | 15 | @override 16 | int get hashCode { 17 | int result = 17; 18 | 19 | result = 37 * result + _a.hashCode; 20 | result = 37 * result + _b.hashCode; 21 | 22 | return result; 23 | } 24 | 25 | CardPair toCardPair() { 26 | assert(isComplete); 27 | 28 | return CardPair(_a!, _b!); 29 | } 30 | 31 | /// Returns a string representation such like `"AsKh"`. 32 | @override 33 | String toString() => "$_a$_b"; 34 | 35 | Card? operator [](int index) { 36 | assert(index == 0 || index == 1, "index should 0 or 1."); 37 | 38 | return index == 0 ? _a : _b; 39 | } 40 | 41 | operator []=(int index, Card? card) { 42 | assert(index == 0 || index == 1, "index should 0 or 1."); 43 | 44 | if (index == 0) { 45 | _a = card; 46 | } else { 47 | _b = card; 48 | } 49 | 50 | notifyListeners(); 51 | } 52 | 53 | @override 54 | operator ==(Object other) => 55 | other is CardPairDraft && other._a == _a && other._b == _b; 56 | } 57 | -------------------------------------------------------------------------------- /ios/GoogleService-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CLIENT_ID 6 | 1070741331805-rmt0hnk3ieurnp3hnnlq0krr98ongupj.apps.googleusercontent.com 7 | REVERSED_CLIENT_ID 8 | com.googleusercontent.apps.1070741331805-rmt0hnk3ieurnp3hnnlq0krr98ongupj 9 | ANDROID_CLIENT_ID 10 | 1070741331805-dlv2er04ppa5obsu7rc84o1hqlkv3rfi.apps.googleusercontent.com 11 | API_KEY 12 | AIzaSyAKZY4Vq1c7dnz6eGeUlUG1xZqKnrFaE1s 13 | GCM_SENDER_ID 14 | 1070741331805 15 | PLIST_VERSION 16 | 1 17 | BUNDLE_ID 18 | app.axross.aqua 19 | PROJECT_ID 20 | ax-aqua 21 | STORAGE_BUCKET 22 | ax-aqua.appspot.com 23 | IS_ADS_ENABLED 24 | 25 | IS_ANALYTICS_ENABLED 26 | 27 | IS_APPINVITE_ENABLED 28 | 29 | IS_GCM_ENABLED 30 | 31 | IS_SIGNIN_ENABLED 32 | 33 | GOOGLE_APP_ID 34 | 1:1070741331805:ios:44fd52f657f127f9c052b4 35 | DATABASE_URL 36 | https://ax-aqua.firebaseio.com 37 | 38 | -------------------------------------------------------------------------------- /lib/src/common_widgets/aqua_preferences.dart: -------------------------------------------------------------------------------- 1 | import "package:flutter/widgets.dart"; 2 | import "package:shared_preferences/shared_preferences.dart"; 3 | 4 | class AquaPreferences extends InheritedWidget { 5 | AquaPreferences({ 6 | required this.data, 7 | required Widget child, 8 | Key? key, 9 | }) : super(key: key, child: child); 10 | 11 | final AquaPreferenceData data; 12 | 13 | @override 14 | bool updateShouldNotify(AquaPreferences old) => data != old.data; 15 | 16 | static AquaPreferenceData of(BuildContext context) => 17 | context.dependOnInheritedWidgetOfExactType()!.data; 18 | } 19 | 20 | class AquaPreferenceData extends ChangeNotifier { 21 | AquaPreferenceData(); 22 | 23 | late SharedPreferences preferences; 24 | 25 | bool _isLoaded = false; 26 | 27 | get isLoaded => _isLoaded; 28 | 29 | late bool _prefersWinRate; 30 | 31 | bool get prefersWinRate => _prefersWinRate; 32 | 33 | Future setPreferWinRate(bool value) async { 34 | _prefersWinRate = value; 35 | 36 | notifyListeners(); 37 | 38 | await preferences.setBool("prefersWinRate", value); 39 | } 40 | 41 | Future initialize() async { 42 | preferences = await SharedPreferences.getInstance(); 43 | 44 | if (!preferences.containsKey("prefersWinRate")) { 45 | await preferences.setBool("prefersWinRate", false); 46 | } 47 | 48 | _prefersWinRate = preferences.getBool("prefersWinRate")!; 49 | _isLoaded = true; 50 | 51 | notifyListeners(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | platform :ios, '10.0' 14 | 15 | def flutter_root 16 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 17 | unless File.exist?(generated_xcode_build_settings_path) 18 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 19 | end 20 | 21 | File.foreach(generated_xcode_build_settings_path) do |line| 22 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 23 | return matches[1].strip if matches 24 | end 25 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 26 | end 27 | 28 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 29 | 30 | flutter_ios_podfile_setup 31 | 32 | target 'Runner' do 33 | use_frameworks! 34 | use_modular_headers! 35 | 36 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 37 | end 38 | 39 | post_install do |installer| 40 | installer.pods_project.targets.each do |target| 41 | flutter_additional_ios_build_settings(target) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/src/utilities/system_ui_overlay_style.dart: -------------------------------------------------------------------------------- 1 | import "package:flutter/painting.dart"; 2 | import "package:flutter/services.dart"; 3 | 4 | void setSystemUIOverlayStyle({ 5 | required Color topColor, 6 | Color? bottomColor, 7 | }) { 8 | final topBrightness = _estimateBrightness(topColor); 9 | final bottomBrightness = _estimateBrightness(topColor); 10 | 11 | SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( 12 | statusBarBrightness: topBrightness, 13 | statusBarColor: topColor, 14 | statusBarIconBrightness: _opposite(topBrightness), 15 | systemNavigationBarColor: bottomColor ?? topColor, 16 | systemNavigationBarIconBrightness: _opposite(bottomBrightness), 17 | )); 18 | } 19 | 20 | Brightness _opposite(Brightness brightness) => 21 | brightness == Brightness.light ? Brightness.dark : Brightness.light; 22 | 23 | Brightness _estimateBrightness(Color color) { 24 | final double relativeLuminance = color.computeLuminance(); 25 | 26 | // See 27 | // The spec says to use kThreshold=0.0525, but Material Design appears to bias 28 | // more towards using light text than WCAG20 recommends. Material Design spec 29 | // doesn't say what value to use, but 0.15 seemed close to what the Material 30 | // Design spec shows for its color palette on 31 | // . 32 | const double kThreshold = 0.15; 33 | 34 | return (relativeLuminance + 0.05) * (relativeLuminance + 0.05) > kThreshold 35 | ? Brightness.light 36 | : Brightness.dark; 37 | } 38 | -------------------------------------------------------------------------------- /lib/app/services/sentry_error_reporter_service.dart: -------------------------------------------------------------------------------- 1 | import "package:aqua/src/models/anonymous_user.dart"; 2 | import "package:aqua/src/services/error_reporter_service.dart"; 3 | import "package:flutter/foundation.dart"; 4 | import "package:package_info/package_info.dart"; 5 | import "package:sentry/sentry.dart"; 6 | 7 | class SentryErrorReporterService implements ErrorReporterService { 8 | SentryErrorReporterService({required String sentryDsn}) 9 | : _sentryClient = SentryClient(SentryOptions(dsn: sentryDsn)) { 10 | PackageInfo.fromPlatform().then((packageInfo) { 11 | Sentry.configureScope((scope) { 12 | scope.setTag("environment", "production"); 13 | scope.setTag("version", packageInfo.version); 14 | }); 15 | }); 16 | } 17 | 18 | final SentryClient _sentryClient; 19 | 20 | void setUser(AnonymousUser? user) { 21 | Sentry.configureScope((scope) { 22 | scope.user = user != null ? SentryUser(id: user.id) : null; 23 | }); 24 | } 25 | 26 | Future captureException({ 27 | required dynamic exception, 28 | required StackTrace stackTrace, 29 | }) async { 30 | await _sentryClient.captureException(exception, stackTrace: stackTrace); 31 | } 32 | 33 | Future captureFlutterException(FlutterErrorDetails details) async { 34 | Sentry.configureScope((scope) { 35 | scope.setExtra("details", details.toString()); 36 | }); 37 | 38 | await _sentryClient.captureException( 39 | details.exception, 40 | stackTrace: details.stack, 41 | ); 42 | 43 | Sentry.configureScope((scope) { 44 | scope.setExtra("details", null); 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/src/view_models/community_cards_draft.dart: -------------------------------------------------------------------------------- 1 | import "package:flutter/foundation.dart"; 2 | import "package:meta/meta.dart"; 3 | import "package:poker/poker.dart"; 4 | 5 | class CommunityCardsDraft extends ChangeNotifier { 6 | CommunityCardsDraft(Iterable cards) 7 | : assert(cards.length <= 5), 8 | assert(cards.every((c) => c == null || c is Card)), 9 | cards = [null, null, null, null, null] 10 | ..setRange(0, cards.length, cards); 11 | 12 | CommunityCardsDraft.empty() : cards = [null, null, null, null, null]; 13 | 14 | @visibleForTesting 15 | final List cards; 16 | 17 | int get hashCode { 18 | int result = 17; 19 | 20 | result = 37 * result + cards[0].hashCode; 21 | result = 37 * result + cards[1].hashCode; 22 | result = 37 * result + cards[2].hashCode; 23 | result = 37 * result + cards[3].hashCode; 24 | result = 37 * result + cards[4].hashCode; 25 | 26 | return result; 27 | } 28 | 29 | // Set toSet() => cards.where((c) => c != null).toSet(); 30 | Set toSet() => cards.whereType().toSet(); 31 | 32 | operator [](index) { 33 | assert(index != null); 34 | assert(index >= 0 && index < 5); 35 | 36 | return cards[index]; 37 | } 38 | 39 | operator []=(index, card) { 40 | assert(index != null); 41 | assert(index >= 0 && index < 5); 42 | assert(card != null); 43 | 44 | cards[index] = card; 45 | } 46 | 47 | operator ==(Object other) => 48 | other is CommunityCardsDraft && 49 | other[0] == this[0] && 50 | other[1] == this[1] && 51 | other[2] == this[2] && 52 | other[3] == this[3] && 53 | other[4] == this[4]; 54 | } 55 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | aqua 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /lib/src/common_widgets/aqua_appear_animation.dart: -------------------------------------------------------------------------------- 1 | import "package:aqua/src/common_widgets/aqua_popup_transition.dart"; 2 | import "package:flutter/widgets.dart"; 3 | 4 | @immutable 5 | class AquaAppearAnimation extends StatefulWidget { 6 | AquaAppearAnimation({Key? key, required this.child, this.isVisible = false}) 7 | : super(key: key); 8 | 9 | final bool isVisible; 10 | 11 | final Widget child; 12 | 13 | @override 14 | _AquaAppearAnimationState createState() => _AquaAppearAnimationState(); 15 | } 16 | 17 | class _AquaAppearAnimationState extends State 18 | with SingleTickerProviderStateMixin { 19 | late AnimationController _animationController; 20 | 21 | late Animation _curvedAnimation; 22 | 23 | @override 24 | void initState() { 25 | super.initState(); 26 | 27 | _animationController = AnimationController( 28 | duration: Duration(milliseconds: 200), 29 | vsync: this, 30 | value: widget.isVisible ? 1.0 : 0.0, 31 | ); 32 | _curvedAnimation = CurvedAnimation( 33 | parent: _animationController, 34 | curve: Curves.easeInOutCubic, 35 | ); 36 | } 37 | 38 | @override 39 | void didUpdateWidget(oldWidget) { 40 | super.didUpdateWidget(oldWidget); 41 | 42 | if (widget.isVisible != oldWidget.isVisible) { 43 | if (widget.isVisible) { 44 | _animationController.forward(); 45 | } else { 46 | _animationController.reverse(); 47 | } 48 | } 49 | } 50 | 51 | @override 52 | Widget build(BuildContext context) { 53 | return AquaPopupTransition( 54 | animation: _curvedAnimation, 55 | child: AnimatedBuilder( 56 | animation: _animationController, 57 | builder: (context, child) => _animationController.isDismissed 58 | ? SizedBox(height: 0) 59 | : widget.child, 60 | ), 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /android/app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "1070741331805", 4 | "firebase_url": "https://ax-aqua.firebaseio.com", 5 | "project_id": "ax-aqua", 6 | "storage_bucket": "ax-aqua.appspot.com" 7 | }, 8 | "client": [ 9 | { 10 | "client_info": { 11 | "mobilesdk_app_id": "1:1070741331805:android:472a2516c8a8aa71c052b4", 12 | "android_client_info": { 13 | "package_name": "app.axross.aqua" 14 | } 15 | }, 16 | "oauth_client": [ 17 | { 18 | "client_id": "1070741331805-dlv2er04ppa5obsu7rc84o1hqlkv3rfi.apps.googleusercontent.com", 19 | "client_type": 1, 20 | "android_info": { 21 | "package_name": "app.axross.aqua", 22 | "certificate_hash": "e019e9559cc05ae53f2f4ff97551b43e9f69314e" 23 | } 24 | }, 25 | { 26 | "client_id": "1070741331805-25q27uuoqf0k9kv8gqai7cfrs689j9rf.apps.googleusercontent.com", 27 | "client_type": 3 28 | } 29 | ], 30 | "api_key": [ 31 | { 32 | "current_key": "AIzaSyAuphIwAWuvlxnXfKRiW0JdartJYQPTp-8" 33 | } 34 | ], 35 | "services": { 36 | "appinvite_service": { 37 | "other_platform_oauth_client": [ 38 | { 39 | "client_id": "1070741331805-25q27uuoqf0k9kv8gqai7cfrs689j9rf.apps.googleusercontent.com", 40 | "client_type": 3 41 | }, 42 | { 43 | "client_id": "1070741331805-rmt0hnk3ieurnp3hnnlq0krr98ongupj.apps.googleusercontent.com", 44 | "client_type": 2, 45 | "ios_info": { 46 | "bundle_id": "app.axross.aqua", 47 | "app_store_id": "1485519383" 48 | } 49 | } 50 | ] 51 | } 52 | } 53 | } 54 | ], 55 | "configuration_version": "1" 56 | } -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply plugin: 'com.google.gms.google-services' 27 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 28 | 29 | android { 30 | compileSdkVersion 30 31 | 32 | sourceSets { 33 | main.java.srcDirs += 'src/main/kotlin' 34 | } 35 | 36 | defaultConfig { 37 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 38 | applicationId "app.axross.aqua" 39 | minSdkVersion 21 40 | targetSdkVersion 30 41 | versionCode flutterVersionCode.toInteger() 42 | versionName flutterVersionName 43 | } 44 | 45 | buildTypes { 46 | release { 47 | // TODO: Add your own signing config for the release build. 48 | // Signing with the debug keys for now, so `flutter run --release` works. 49 | signingConfig signingConfigs.debug 50 | } 51 | } 52 | } 53 | 54 | flutter { 55 | source '../..' 56 | } 57 | 58 | dependencies { 59 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 60 | } 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Aqua 2 | 3 | [![Codemagic build status](https://api.codemagic.io/apps/5f13196520f165f7137dc471/5f288e6c62e59203f98e84e8/status_badge.svg)](https://codemagic.io/apps/5f13196520f165f7137dc471/5f288e6c62e59203f98e84e8/latest_build) 4 | 5 | ♠️ Beautiful Texas Hold'em poker odds calculator made of Flutter. 6 | 7 | - 🎛 Effectively uses [ChangeNotifier](https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html)/[ValueNotifier](https://api.flutter.dev/flutter/foundation/ValueNotifier-class.html) with [AnimatedBuilder](https://api.flutter.dev/flutter/widgets/AnimatedBuilder-class.html)/[ValueListenableBuilder](https://api.flutter.dev/flutter/widgets/ValueListenableBuilder-class.html). No [provider](https://pub.dev/packages/provider) or BLoC pattern used 8 | - 🧩 [Isolate](https://api.dart.dev/stable/2.9.0/dart-isolate/dart-isolate-library.html) and [Stream](https://api.dart.dev/stable/2.9.0/dart-async/Stream-class.html) are used for multithreading calculation and communication between main thread and isolates 9 | - 💅 Built of an original [theme](lib/src/constants/theme.dart) class and corresponding [widgets](lib/src/common_widgets). Dark mode is supported 10 | 11 | ![Aqua](https://user-images.githubusercontent.com/4289883/89248433-9a76ba80-d5c4-11ea-9b23-b4d0a4dcc867.gif) 12 | 13 | ## Download 14 | 15 | [Get it on Google Play](https://play.google.com/store/apps/details?id=app.axross.aqua&hl=en) [Download on the App Store](https://apps.apple.com/us/app/odds-calculator-for-poker/id1485519383) 16 | 17 | If you want an access of the beta program, feel free to contact at [yo@kohei.dev](mailto:yo@kohei.dev)! 18 | 19 | ## Bug Report / Feature Request / Contribution 20 | 21 | Feel free to open an issue or pull request! Everything you do would be grateful! 22 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 13 | 17 | 21 | 26 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /lib/src/common_widgets/aqua_slider.dart: -------------------------------------------------------------------------------- 1 | import "dart:io"; 2 | import "package:aqua/src/common_widgets/aqua_theme.dart"; 3 | import "package:flutter/material.dart"; 4 | import "package:flutter/services.dart"; 5 | 6 | class AquaSlider extends StatefulWidget { 7 | AquaSlider({ 8 | Key? key, 9 | required this.divisions, 10 | required this.value, 11 | required this.label, 12 | this.onChanged, 13 | this.onChangeStart, 14 | this.onChangeEnd, 15 | }); 16 | 17 | final int divisions; 18 | 19 | final double value; 20 | 21 | final String label; 22 | 23 | final void Function(double value)? onChanged; 24 | 25 | final void Function(double value)? onChangeStart; 26 | 27 | final void Function(double value)? onChangeEnd; 28 | 29 | @override 30 | State createState() => _AquaSliderState(); 31 | } 32 | 33 | class _AquaSliderState extends State { 34 | @override 35 | Widget build(BuildContext context) { 36 | final theme = AquaTheme.of(context); 37 | final style = theme.sliderStyle; 38 | 39 | return SliderTheme( 40 | data: SliderThemeData( 41 | thumbColor: style.thumbColor, 42 | activeTrackColor: style.activeTrackColor, 43 | inactiveTrackColor: style.inactiveTrackColor, 44 | valueIndicatorColor: style.valueIndicatorColor, 45 | valueIndicatorTextStyle: style.valueIndicatorTextStyle, 46 | ), 47 | child: Material( 48 | color: Color(0x00000000), 49 | child: Slider( 50 | divisions: widget.divisions, 51 | value: widget.value, 52 | label: widget.label, 53 | onChanged: (value) { 54 | if (Platform.isIOS) { 55 | HapticFeedback.selectionClick(); 56 | } 57 | 58 | if (widget.onChanged != null) { 59 | widget.onChanged!(value); 60 | } 61 | }, 62 | onChangeStart: (value) { 63 | HapticFeedback.lightImpact(); 64 | 65 | if (widget.onChangeStart != null) { 66 | widget.onChangeStart!(value); 67 | } 68 | }, 69 | onChangeEnd: (value) { 70 | if (widget.onChangeEnd != null) { 71 | widget.onChangeEnd!(value); 72 | } 73 | }, 74 | ), 75 | ), 76 | ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/src/common_widgets/aqua_tab_view.dart: -------------------------------------------------------------------------------- 1 | import "package:flutter/widgets.dart"; 2 | 3 | class AquaTabView extends StatefulWidget { 4 | AquaTabView({ 5 | Key? key, 6 | required this.views, 7 | this.activeViewIndex = 0, 8 | }) : assert(views.length >= 1), 9 | assert(activeViewIndex < views.length), 10 | super(key: key); 11 | 12 | final List views; 13 | 14 | final int activeViewIndex; 15 | 16 | @override 17 | State createState() => _AquaTabViewState(); 18 | } 19 | 20 | class _AquaTabViewState extends State { 21 | late int _previousActiveViewIndex; 22 | 23 | @override 24 | void initState() { 25 | super.initState(); 26 | 27 | _previousActiveViewIndex = widget.activeViewIndex; 28 | } 29 | 30 | @override 31 | void didUpdateWidget(AquaTabView oldWidget) { 32 | super.didUpdateWidget(oldWidget); 33 | 34 | if (oldWidget.activeViewIndex != widget.activeViewIndex) { 35 | setState(() { 36 | _previousActiveViewIndex = oldWidget.activeViewIndex; 37 | }); 38 | } 39 | } 40 | 41 | Widget build(BuildContext context) => AnimatedSwitcher( 42 | duration: const Duration(milliseconds: 200), 43 | transitionBuilder: (child, animation) => FadeTransition( 44 | opacity: Tween(begin: 0.5, end: 1).animate( 45 | CurvedAnimation( 46 | parent: animation, 47 | curve: Curves.easeOutCubic, 48 | ), 49 | ), 50 | child: SlideTransition( 51 | position: _previousActiveViewIndex > widget.activeViewIndex 52 | ? Tween( 53 | begin: Offset(-0.05, 0), 54 | end: Offset(0, 0), 55 | ).animate(CurvedAnimation( 56 | parent: animation, 57 | curve: Curves.easeOutCubic, 58 | )) 59 | : Tween( 60 | begin: Offset(0.05, 0), 61 | end: Offset(0, 0), 62 | ).animate(CurvedAnimation( 63 | parent: animation, 64 | curve: Curves.easeOutCubic, 65 | )), 66 | child: child, 67 | ), 68 | ), 69 | child: widget.views[widget.activeViewIndex], 70 | ); 71 | } 72 | -------------------------------------------------------------------------------- /lib/src/view_models/hand_range_draft_list.dart: -------------------------------------------------------------------------------- 1 | import "dart:collection"; 2 | import "package:aqua/src/view_models/hand_range_draft.dart"; 3 | import "package:flutter/foundation.dart"; 4 | import "package:poker/poker.dart"; 5 | 6 | class HandRangeDraftList extends ChangeNotifier with ListMixin { 7 | HandRangeDraftList.empty() : _handRangeDrafts = []; 8 | 9 | HandRangeDraftList.of(Iterable handRangeDrafts) 10 | : _handRangeDrafts = handRangeDrafts.toList(); 11 | 12 | final List _handRangeDrafts; 13 | 14 | final Map _listeners = {}; 15 | 16 | int get length => _handRangeDrafts.length; 17 | 18 | bool get hasIncomplete => 19 | _handRangeDrafts.any((handRange) => handRange.isEmpty); 20 | 21 | CardSet get usedCards { 22 | final usedCards = CardSet.empty; 23 | 24 | for (final handRangeDraft in _handRangeDrafts) { 25 | if (handRangeDraft.type != HandRangeDraftInputType.cardPair) { 26 | continue; 27 | } 28 | 29 | if (handRangeDraft.firstCardPair[0] != null) { 30 | usedCards.union(CardSet.single(handRangeDraft.firstCardPair[0]!)); 31 | } 32 | 33 | if (handRangeDraft.firstCardPair[1] != null) { 34 | usedCards.union(CardSet.single(handRangeDraft.firstCardPair[1]!)); 35 | } 36 | } 37 | 38 | return usedCards; 39 | } 40 | 41 | set length(value) { 42 | if (value <= length) { 43 | for (final handRangeDraft in _handRangeDrafts.getRange(value, length)) { 44 | handRangeDraft.removeListener(_listeners[handRangeDraft]!); 45 | } 46 | 47 | _handRangeDrafts.length = value; 48 | 49 | return; 50 | } 51 | 52 | for (int i = length; i < value; ++i) { 53 | final handRange = HandRangeDraft.emptyCardPair(); 54 | 55 | _handRangeDrafts.add(handRange); 56 | _listeners[handRange] = () { 57 | notifyListeners(); 58 | }; 59 | } 60 | } 61 | 62 | operator [](index) => _handRangeDrafts[index]; 63 | 64 | operator []=(index, handRangeDraft) { 65 | assert(index < length); 66 | 67 | final previous = _handRangeDrafts[index]; 68 | 69 | previous.removeListener(_listeners[previous]!); 70 | 71 | _handRangeDrafts[index] = handRangeDraft; 72 | 73 | _listeners[handRangeDraft] = () { 74 | notifyListeners(); 75 | }; 76 | 77 | handRangeDraft.addListener(_listeners[handRangeDraft]!); 78 | 79 | notifyListeners(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import "dart:async"; 2 | import "package:amplitude_flutter/amplitude.dart"; 3 | import "package:aqua/app/app.dart"; 4 | import "package:aqua/app/services/amplitude_analytics_service.dart"; 5 | import "package:aqua/app/services/firebase_auth_manager_service.dart"; 6 | import "package:aqua/app/services/noop_error_reporter_service.dart"; 7 | import "package:aqua/app/services/sentry_error_reporter_service.dart"; 8 | import 'package:aqua/src/common_widgets/aqua_preferences.dart'; 9 | import "package:firebase_analytics/firebase_analytics.dart"; 10 | import 'package:firebase_core/firebase_core.dart'; 11 | import "package:flutter/foundation.dart"; 12 | import "package:flutter/widgets.dart"; 13 | 14 | void main() async { 15 | WidgetsFlutterBinding.ensureInitialized(); 16 | 17 | final authManagerService = FirebaseAuthManagerService(); 18 | final amplitudeInstance = Amplitude.getInstance(); 19 | final analyticsService = AmplitudeAnalyticsService( 20 | amplitudeAnalytics: amplitudeInstance, 21 | firebaseAnalytics: FirebaseAnalytics(), 22 | ); 23 | final applicationPreferenceData = AquaPreferenceData(); 24 | 25 | if (!kDebugMode) { 26 | final errorReporter = SentryErrorReporterService( 27 | sentryDsn: 28 | "https://7f698d26a29e495881c4adf639830a1a@o30395.ingest.sentry.io/5375048", 29 | ); 30 | 31 | FlutterError.onError = (details) { 32 | errorReporter.captureFlutterException(details); 33 | }; 34 | 35 | runZonedGuarded(() async { 36 | runApp(AquaApp( 37 | analyticsService: analyticsService, 38 | authManagerService: authManagerService, 39 | errorReporter: errorReporter, 40 | applicationPreferenceData: applicationPreferenceData, 41 | prepare: () async { 42 | await Firebase.initializeApp(); 43 | await authManagerService.initialize(); 44 | await amplitudeInstance.init("94ba98446847f79253029f7f8e6d9cf3"); 45 | await amplitudeInstance 46 | .setUserProperties({"Environment": "production"}); 47 | await amplitudeInstance.trackingSessionEvents(true); 48 | await applicationPreferenceData.initialize(); 49 | }, 50 | )); 51 | }, (exception, stackTrace) async { 52 | errorReporter.captureException( 53 | exception: exception, 54 | stackTrace: stackTrace, 55 | ); 56 | }); 57 | } else { 58 | runApp(AquaApp( 59 | analyticsService: analyticsService, 60 | authManagerService: authManagerService, 61 | errorReporter: NoopErrorReporterService(), 62 | applicationPreferenceData: applicationPreferenceData, 63 | )); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/app/services/flutter_isolate_evaluation_service.dart: -------------------------------------------------------------------------------- 1 | import "dart:async"; 2 | import "dart:isolate"; 3 | import "package:aqua/src/models/calculation.dart"; 4 | import "package:aqua/src/services/isolate_evaluation_service.dart"; 5 | import "package:poker/poker.dart"; 6 | 7 | class FlutterIsolateEvaluationService implements IsolateEvaluationService { 8 | Isolate? _isolate; 9 | 10 | SendPort? _toIsolate; 11 | 12 | final _streamController = StreamController.broadcast(); 13 | 14 | Stream get onProgress => _streamController.stream; 15 | 16 | Future initialize() async { 17 | final completer = Completer(); 18 | final receivePort = ReceivePort(); 19 | 20 | receivePort.listen((data) async { 21 | if (_toIsolate == null) { 22 | _toIsolate = data; 23 | completer.complete(); 24 | 25 | return; 26 | } 27 | 28 | if (_streamController.isClosed) return; 29 | 30 | if (data is Exception) { 31 | _streamController.addError(data); 32 | } else { 33 | _streamController.add(data); 34 | } 35 | }); 36 | 37 | _isolate = await Isolate.spawn(_isolateFunction, receivePort.sendPort); 38 | 39 | await completer.future; 40 | } 41 | 42 | Future dispose() async { 43 | _isolate?.kill(priority: Isolate.immediate); 44 | 45 | _streamController.close(); 46 | } 47 | 48 | void requestEvaluation({ 49 | required CardSet communityCards, 50 | required List players, 51 | required int times, 52 | }) { 53 | _toIsolate?.send([communityCards, players, times]); 54 | } 55 | } 56 | 57 | void _isolateFunction(SendPort toMain) { 58 | final receivePort = ReceivePort(); 59 | 60 | receivePort.listen((data) async { 61 | final communityCards = data[0] as CardSet; 62 | final players = data[1] as List; 63 | final times = data[2] as int; 64 | final startedAt = DateTime.now(); 65 | 66 | if (times % 2000 != 0) { 67 | toMain.send(ArgumentError.value(times, "times must be multiple of 2000")); 68 | } 69 | 70 | final evaluator = MontecarloEvaluator( 71 | communityCards: communityCards, 72 | players: players, 73 | ); 74 | EvaluationResult accumulatedResult = 75 | EvaluationResult.empty(playerLength: players.length); 76 | 77 | for (int i = 1; i <= times / 2000; ++i) { 78 | for (final result in evaluator.take(2000)) { 79 | accumulatedResult += result; 80 | } 81 | 82 | toMain.send(Calculation( 83 | communityCards: communityCards, 84 | players: players, 85 | result: accumulatedResult, 86 | startedAt: startedAt, 87 | endedAt: DateTime.now(), 88 | )); 89 | } 90 | }); 91 | 92 | toMain.send(receivePort.sendPort); 93 | } 94 | -------------------------------------------------------------------------------- /lib/app/services/amplitude_analytics_service.dart: -------------------------------------------------------------------------------- 1 | import "package:amplitude_flutter/amplitude.dart"; 2 | import "package:aqua/src/services/analytics_service.dart"; 3 | import "package:aqua/src/models/anonymous_user.dart"; 4 | import "package:firebase_analytics/firebase_analytics.dart"; 5 | import "package:flutter/widgets.dart"; 6 | 7 | @immutable 8 | class AmplitudeAnalyticsService implements AnalyticsService { 9 | const AmplitudeAnalyticsService({ 10 | required Amplitude amplitudeAnalytics, 11 | required FirebaseAnalytics firebaseAnalytics, 12 | }) : _amplitudeAnalytics = amplitudeAnalytics, 13 | _firebaseAnalytics = firebaseAnalytics; 14 | 15 | final Amplitude _amplitudeAnalytics; 16 | 17 | final FirebaseAnalytics _firebaseAnalytics; 18 | 19 | void setUser(AnonymousUser? user) { 20 | _amplitudeAnalytics.setUserId(user?.id); 21 | _firebaseAnalytics.setUserId(user?.id); 22 | } 23 | 24 | void logScreenChange({ 25 | required String screenName, 26 | Map parameters = const {}, 27 | }) { 28 | final encodedParameters = _encodeParameters(parameters); 29 | 30 | _amplitudeAnalytics.logEvent( 31 | "Transition to $screenName", 32 | eventProperties: encodedParameters, 33 | ); 34 | _firebaseAnalytics.logEvent( 35 | name: _toSnakeCase("Transition to $screenName"), 36 | parameters: _snakeCaseParameters(encodedParameters), 37 | ); 38 | _firebaseAnalytics.setCurrentScreen( 39 | screenName: screenName, 40 | ); 41 | } 42 | 43 | void logEvent({ 44 | required String name, 45 | Map parameters = const {}, 46 | }) { 47 | final encodedParameters = _encodeParameters(parameters); 48 | 49 | _amplitudeAnalytics.logEvent( 50 | name, 51 | eventProperties: encodedParameters, 52 | ); 53 | _firebaseAnalytics.logEvent( 54 | name: _toSnakeCase(name), 55 | parameters: _snakeCaseParameters(encodedParameters), 56 | ); 57 | } 58 | 59 | Map _encodeParameters(Map parameters) { 60 | return parameters.map((key, value) { 61 | dynamic processedValue; 62 | 63 | if (value is num || value is String || value is bool) { 64 | processedValue = value; 65 | } 66 | 67 | return MapEntry(key, processedValue); 68 | }); 69 | } 70 | 71 | Map _snakeCaseParameters(Map parameters) { 72 | return parameters.map((key, value) => MapEntry(_toSnakeCase(key), value)); 73 | } 74 | } 75 | 76 | String _toSnakeCase(String value) { 77 | return value 78 | .replaceAll(RegExp(r" +"), "_") 79 | .replaceAllMapped( 80 | RegExp(r"[A-Z]"), 81 | (match) => String.fromCharCode(match[0]!.codeUnitAt(0) + 32), 82 | ) 83 | .replaceAll(RegExp(r"[^A-Za-z0-9_]+"), ""); 84 | } 85 | -------------------------------------------------------------------------------- /lib/src/common_widgets/playing_card.dart: -------------------------------------------------------------------------------- 1 | import "package:aqua/src/common_widgets/aqua_icons.dart"; 2 | import "package:aqua/src/common_widgets/aqua_theme.dart"; 3 | import "package:aqua/src/common_widgets/fill.dart"; 4 | import "package:aqua/src/constants/card.dart"; 5 | import "package:flutter/widgets.dart"; 6 | import "package:poker/poker.dart"; 7 | 8 | class PlayingCard extends StatelessWidget { 9 | PlayingCard({Key? key, required this.card}) : super(key: key); 10 | 11 | final Card card; 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | final style = AquaTheme.of(context).playingCardStyle; 16 | 17 | return Fill( 18 | child: AspectRatio( 19 | aspectRatio: 2.25 / 3.5, 20 | child: LayoutBuilder( 21 | builder: (_, constraints) => DecoratedBox( 22 | decoration: BoxDecoration( 23 | color: style.backgroundColor, 24 | borderRadius: BorderRadius.circular(constraints.maxWidth * 0.1), 25 | ), 26 | child: Stack( 27 | children: [ 28 | Align( 29 | alignment: Alignment.topCenter, 30 | child: Text( 31 | rankChars[card.rank]!, 32 | textAlign: TextAlign.center, 33 | style: style.textStyle.copyWith( 34 | color: style.suitColors[card.suit], 35 | fontSize: constraints.maxHeight * 0.475, 36 | ), 37 | ), 38 | ), 39 | Align( 40 | alignment: Alignment.bottomCenter, 41 | child: Padding( 42 | padding: EdgeInsets.only( 43 | bottom: constraints.maxHeight * 0.05, 44 | ), 45 | child: FittedBox( 46 | fit: BoxFit.contain, 47 | child: Icon( 48 | _suitIcons[card.suit], 49 | color: style.suitColors[card.suit], 50 | size: constraints.maxHeight * 0.475, 51 | ), 52 | ), 53 | ), 54 | ), 55 | ], 56 | ), 57 | ), 58 | ), 59 | ), 60 | ); 61 | } 62 | } 63 | 64 | const _suitIcons = { 65 | Suit.spade: AquaIcons.spade, 66 | Suit.heart: AquaIcons.heart, 67 | Suit.diamond: AquaIcons.diamond, 68 | Suit.club: AquaIcons.club, 69 | }; 70 | 71 | class PlayingCardBack extends StatelessWidget { 72 | @override 73 | Widget build(BuildContext context) { 74 | return Fill( 75 | child: AspectRatio( 76 | aspectRatio: 2.25 / 3.5, 77 | child: LayoutBuilder( 78 | builder: (_, constraints) => Container( 79 | decoration: BoxDecoration( 80 | color: AquaTheme.of(context).playingCardStyle.backgroundColor, 81 | borderRadius: BorderRadius.circular(constraints.maxWidth * 0.1), 82 | ), 83 | ), 84 | ), 85 | ), 86 | ); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /lib/src/common_widgets/readonly_rank_pair_grid.dart: -------------------------------------------------------------------------------- 1 | import "package:aqua/src/common_widgets/aqua_theme.dart"; 2 | import "package:aqua/src/constants/card.dart"; 3 | import "package:flutter/widgets.dart"; 4 | import "package:poker/poker.dart"; 5 | 6 | class ReadonlyRankPairGrid extends StatelessWidget { 7 | ReadonlyRankPairGrid({required this.rankPairs, Key? key}) : super(key: key); 8 | 9 | final Set rankPairs; 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | final style = AquaTheme.of(context).rankPairGridStyle; 14 | 15 | return LayoutBuilder( 16 | builder: (context, constraints) => DecoratedBox( 17 | decoration: BoxDecoration( 18 | color: style.backgroundColor, 19 | borderRadius: BorderRadius.circular(constraints.maxWidth * 0.05), 20 | ), 21 | child: Table( 22 | children: List.generate(ranksInStrongnessOrder.length, (row) { 23 | return TableRow( 24 | children: List.generate(ranksInStrongnessOrder.length, (column) { 25 | final high = row < column ? row : column; 26 | final kicker = high == row ? column : row; 27 | final rankPairsPart = row < column 28 | ? RankPair.suited( 29 | high: ranksInStrongnessOrder[high], 30 | kicker: ranksInStrongnessOrder[kicker], 31 | ) 32 | : RankPair.ofsuit( 33 | high: ranksInStrongnessOrder[high], 34 | kicker: ranksInStrongnessOrder[kicker], 35 | ); 36 | BorderRadius? borderRadius; 37 | 38 | if (row == 0 && column == 0) { 39 | borderRadius = BorderRadius.only( 40 | topLeft: Radius.circular(constraints.maxWidth * 0.05)); 41 | } 42 | 43 | if (row == 0 && column == ranksInStrongnessOrder.length - 1) { 44 | borderRadius = BorderRadius.only( 45 | topRight: Radius.circular(constraints.maxWidth * 0.05)); 46 | } 47 | 48 | if (row == ranksInStrongnessOrder.length - 1 && column == 0) { 49 | borderRadius = BorderRadius.only( 50 | bottomLeft: Radius.circular(constraints.maxWidth * 0.05)); 51 | } 52 | 53 | if (row == ranksInStrongnessOrder.length - 1 && 54 | column == ranksInStrongnessOrder.length - 1) { 55 | borderRadius = BorderRadius.only( 56 | bottomRight: 57 | Radius.circular(constraints.maxWidth * 0.05)); 58 | } 59 | 60 | return TableCell( 61 | child: Container( 62 | height: constraints.maxWidth / 13, 63 | decoration: BoxDecoration( 64 | color: rankPairs.contains(rankPairsPart) 65 | ? style.selectedForegroundColor 66 | : null, 67 | borderRadius: borderRadius, 68 | ), 69 | ), 70 | ); 71 | }), 72 | ); 73 | }), 74 | ), 75 | ), 76 | ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/src/common_widgets/aqua_theme.dart: -------------------------------------------------------------------------------- 1 | import "package:aqua/src/common_widgets/aqua_button.dart"; 2 | import "package:aqua/src/common_widgets/aqua_scaffold.dart"; 3 | import "package:aqua/src/common_widgets/digits_text.dart"; 4 | import "package:flutter/widgets.dart"; 5 | import "package:poker/poker.dart"; 6 | 7 | class AquaTheme extends InheritedWidget { 8 | AquaTheme({ 9 | required this.data, 10 | required Widget child, 11 | Key? key, 12 | }) : super( 13 | key: key, 14 | child: DefaultAquaButtonStyle( 15 | style: data.buttonStyleSet.normal, 16 | child: DefaultTextStyle( 17 | style: data.textStyleSet.body, 18 | child: child, 19 | ), 20 | ), 21 | ); 22 | 23 | final AquaThemeData data; 24 | 25 | @override 26 | bool updateShouldNotify(AquaTheme old) => data != old.data; 27 | 28 | static AquaThemeData of(BuildContext context) => 29 | context.dependOnInheritedWidgetOfExactType()!.data; 30 | } 31 | 32 | @immutable 33 | class AquaThemeData { 34 | const AquaThemeData({ 35 | required this.textStyleSet, 36 | required this.buttonStyleSet, 37 | required this.scaffoldStyle, 38 | required this.playingCardStyle, 39 | required this.rankPairGridStyle, 40 | required this.digitTextStyle, 41 | required this.sliderStyle, 42 | required this.cursorColor, 43 | required this.elevationBoxShadows, 44 | }); 45 | 46 | final AquaTextStyleSet textStyleSet; 47 | 48 | final AquaButtonStyleSet buttonStyleSet; 49 | 50 | final AquaScaffoldStyle scaffoldStyle; 51 | 52 | final AquaPlayingCardStyle playingCardStyle; 53 | 54 | final AquaRankPairGridStyle rankPairGridStyle; 55 | 56 | final AquaDigitTextStyle digitTextStyle; 57 | 58 | final AquaSliderStyle sliderStyle; 59 | 60 | final Color cursorColor; 61 | 62 | final List elevationBoxShadows; 63 | } 64 | 65 | @immutable 66 | class AquaTextStyleSet { 67 | const AquaTextStyleSet({ 68 | required this.headline, 69 | required this.body, 70 | required this.caption, 71 | required this.errorCaption, 72 | }); 73 | 74 | final TextStyle headline; 75 | 76 | final TextStyle body; 77 | 78 | final TextStyle caption; 79 | 80 | final TextStyle errorCaption; 81 | } 82 | 83 | class AquaPlayingCardStyle { 84 | const AquaPlayingCardStyle({ 85 | required this.textStyle, 86 | required this.backgroundColor, 87 | required this.suitColors, 88 | }); 89 | 90 | final TextStyle textStyle; 91 | 92 | final Color backgroundColor; 93 | 94 | final Map suitColors; 95 | } 96 | 97 | class AquaRankPairGridStyle { 98 | AquaRankPairGridStyle({ 99 | required this.backgroundColor, 100 | required this.textStyle, 101 | required this.selectedBackgroundColor, 102 | required this.selectedForegroundColor, 103 | }); 104 | 105 | final Color backgroundColor; 106 | 107 | final TextStyle textStyle; 108 | 109 | final Color selectedBackgroundColor; 110 | 111 | final Color selectedForegroundColor; 112 | } 113 | 114 | class AquaSliderStyle { 115 | AquaSliderStyle({ 116 | required this.thumbColor, 117 | required this.activeTrackColor, 118 | required this.inactiveTrackColor, 119 | required this.valueIndicatorColor, 120 | required this.valueIndicatorTextStyle, 121 | }); 122 | 123 | final Color thumbColor; 124 | 125 | final Color activeTrackColor; 126 | 127 | final Color inactiveTrackColor; 128 | 129 | final Color valueIndicatorColor; 130 | 131 | final TextStyle valueIndicatorTextStyle; 132 | } 133 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /lib/app/main_route.dart: -------------------------------------------------------------------------------- 1 | import "package:aqua/app/main_route/preferences_tab_page.dart"; 2 | import "package:aqua/app/main_route/simulation_tab_page.dart"; 3 | import "package:aqua/src/common_widgets/analytics.dart"; 4 | import "package:aqua/src/common_widgets/aqua_icons.dart"; 5 | import "package:aqua/src/common_widgets/aqua_tab_bar.dart"; 6 | import "package:aqua/src/common_widgets/aqua_tab_view.dart"; 7 | import "package:aqua/src/view_models/simulation_session.dart"; 8 | import "package:flutter/cupertino.dart"; 9 | 10 | class MainRoute extends CupertinoPageRoute { 11 | MainRoute({ 12 | RouteSettings? settings, 13 | }) : super( 14 | title: "Main", 15 | builder: (context) => _MainPage(), 16 | settings: settings, 17 | ); 18 | } 19 | 20 | class _MainPage extends StatefulWidget { 21 | @override 22 | State<_MainPage> createState() => _MainPageState(); 23 | } 24 | 25 | class _MainPageState extends State<_MainPage> { 26 | late int _activeTabViewIndex; 27 | 28 | /// A ValueNotifier that holds a CalculationSession inside. 29 | /// Replace the held CalculationSession to start a new session 30 | late ValueNotifier _calculationSession; 31 | 32 | @override 33 | void initState() { 34 | super.initState(); 35 | 36 | _activeTabViewIndex = 0; 37 | 38 | late CalculationSession calculationSession; 39 | calculationSession = CalculationSession.initial( 40 | onStartCalculation: (_, __) { 41 | Analytics.of(context).logEvent( 42 | name: "Start Simulation", 43 | parameters: { 44 | "Number of Community Cards": 45 | calculationSession.communityCards.toSet().length, 46 | "Number of Hand Ranges": calculationSession.players.length, 47 | }, 48 | ); 49 | }, 50 | onFinishCalculation: (snapshot) { 51 | Analytics.of(context).logEvent( 52 | name: "Finish Simulation", 53 | parameters: { 54 | "Number of Community Cards": 55 | calculationSession.communityCards.toSet().length, 56 | "Number of Hand Ranges": calculationSession.players.length, 57 | }, 58 | ); 59 | }, 60 | ); 61 | 62 | _calculationSession = ValueNotifier(calculationSession); 63 | } 64 | 65 | @override 66 | Widget build(BuildContext context) { 67 | return ValueListenableBuilder( 68 | valueListenable: _calculationSession, 69 | builder: (context, calculationSession, _) => Container( 70 | color: Color(0xffffffff), 71 | child: Column( 72 | children: [ 73 | Expanded( 74 | child: AquaTabView( 75 | views: [ 76 | SimulationTabPage( 77 | key: ValueKey(0), 78 | calculationSession: calculationSession, 79 | ), 80 | PreferencesTabPage(key: ValueKey(1)), 81 | ], 82 | activeViewIndex: _activeTabViewIndex, 83 | ), 84 | ), 85 | AquaTabBar( 86 | activeIndex: _activeTabViewIndex, 87 | onChanged: (index) { 88 | setState(() { 89 | _activeTabViewIndex = index; 90 | }); 91 | }, 92 | items: [ 93 | AquaTabBarItem(label: "Calculation", icon: AquaIcons.percent), 94 | AquaTabBarItem(label: "Preferences", icon: AquaIcons.gear), 95 | ], 96 | ), 97 | ], 98 | ), 99 | ), 100 | ); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /lib/src/common_widgets/aqua_scaffold.dart: -------------------------------------------------------------------------------- 1 | import "package:aqua/src/common_widgets/analytics.dart"; 2 | import "package:aqua/src/common_widgets/aqua_button.dart"; 3 | import "package:aqua/src/common_widgets/aqua_icons.dart"; 4 | import "package:aqua/src/common_widgets/aqua_theme.dart"; 5 | import "package:flutter/widgets.dart"; 6 | 7 | @immutable 8 | class AquaScadffold extends StatelessWidget { 9 | AquaScadffold({ 10 | Key? key, 11 | this.scrollController, 12 | this.title = "Title", 13 | this.actions = const [], 14 | this.slivers = const [], 15 | }) : super(key: key); 16 | 17 | final ScrollController? scrollController; 18 | 19 | final String title; 20 | 21 | final List actions; 22 | 23 | final List slivers; 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | final theme = AquaTheme.of(context); 28 | final style = theme.scaffoldStyle; 29 | final defaultActionButtonStyle = 30 | theme.buttonStyleSet[style.defaultActionButtonvariant]; 31 | 32 | return Container( 33 | color: style.backgroundColor, 34 | child: CustomScrollView( 35 | controller: scrollController, 36 | slivers: [ 37 | SliverSafeArea( 38 | sliver: SliverPadding( 39 | padding: const EdgeInsets.fromLTRB(16, 32, 16, 16), 40 | sliver: SliverToBoxAdapter( 41 | child: Row( 42 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 43 | children: [ 44 | Row( 45 | children: [ 46 | if (ModalRoute.of(context)?.canPop ?? false) ...[ 47 | AquaButton( 48 | variant: AquaButtonVariant.secondary, 49 | icon: AquaIcons.chevronLeft, 50 | onTap: () { 51 | Analytics.of(context).logEvent( 52 | name: "Tap a Scaffold Back Button", 53 | ); 54 | 55 | Navigator.of(context).pop(); 56 | }, 57 | ), 58 | SizedBox(width: 16), 59 | ], 60 | Text( 61 | title, 62 | textAlign: TextAlign.start, 63 | style: style.titleTextStyle, 64 | ), 65 | ], 66 | ), 67 | DefaultAquaButtonStyle( 68 | style: defaultActionButtonStyle, 69 | child: Row( 70 | crossAxisAlignment: CrossAxisAlignment.center, 71 | children: actions, 72 | ), 73 | ), 74 | ], 75 | ), 76 | ), 77 | ), 78 | ), 79 | ...slivers, 80 | SliverSafeArea( 81 | bottom: true, 82 | sliver: SliverPadding( 83 | padding: EdgeInsets.only(bottom: 64.0), 84 | ), 85 | ), 86 | ], 87 | ), 88 | ); 89 | } 90 | } 91 | 92 | @immutable 93 | class AquaScaffoldStyle { 94 | const AquaScaffoldStyle({ 95 | required this.titleTextStyle, 96 | this.defaultActionButtonvariant = AquaButtonVariant.secondary, 97 | required this.backgroundColor, 98 | }); 99 | 100 | final TextStyle titleTextStyle; 101 | 102 | final AquaButtonVariant defaultActionButtonvariant; 103 | 104 | final Color backgroundColor; 105 | } 106 | -------------------------------------------------------------------------------- /lib/src/view_models/hand_range_draft.dart: -------------------------------------------------------------------------------- 1 | import "package:aqua/src/view_models/card_pair_draft.dart"; 2 | import "package:flutter/foundation.dart"; 3 | import "package:poker/poker.dart"; 4 | 5 | extension Open on HandRange { 6 | Set get onlyCardPairs => components.whereType().toSet(); 7 | 8 | bool get hasCardPair => onlyCardPairs.length >= 1; 9 | 10 | Set get onlyRankPairs => components.whereType().toSet(); 11 | 12 | bool get hasRankPair => onlyRankPairs.length >= 1; 13 | } 14 | 15 | class HandRangeDraft extends ChangeNotifier { 16 | HandRangeDraft({ 17 | required HandRangeDraftInputType type, 18 | required List cardPairs, 19 | required Set rankPairs, 20 | }) : _type = type, 21 | _cardPairs = cardPairs, 22 | _rankPairs = rankPairs; 23 | 24 | HandRangeDraft.emptyCardPair() 25 | : _type = HandRangeDraftInputType.cardPair, 26 | _cardPairs = [CardPairDraft.empty()], 27 | _rankPairs = {}; 28 | 29 | HandRangeDraft.emptyRankPairs() 30 | : _type = HandRangeDraftInputType.rankPairs, 31 | _cardPairs = [CardPairDraft.empty()], 32 | _rankPairs = {}; 33 | 34 | HandRangeDraft.fromHandRange(HandRange handRange) 35 | : _type = handRange.hasCardPair 36 | ? handRange.hasRankPair 37 | ? HandRangeDraftInputType.mixed 38 | : HandRangeDraftInputType.cardPair 39 | : HandRangeDraftInputType.rankPairs, 40 | _cardPairs = handRange.onlyCardPairs.length >= 1 41 | ? handRange.onlyCardPairs 42 | .map((cp) => CardPairDraft(cp.first, cp.last)) 43 | .toList() 44 | : [CardPairDraft.empty()], 45 | _rankPairs = handRange.onlyRankPairs; 46 | 47 | HandRangeDraftInputType _type; 48 | 49 | List _cardPairs; 50 | 51 | Set _rankPairs; 52 | 53 | HandRangeDraftInputType get type => _type; 54 | 55 | set type(HandRangeDraftInputType type) { 56 | if (type == _type) { 57 | return; 58 | } 59 | 60 | _type = type; 61 | 62 | if (type == HandRangeDraftInputType.cardPair) { 63 | _rankPairs = {}; 64 | } 65 | 66 | if (type == HandRangeDraftInputType.rankPairs) { 67 | _cardPairs = [CardPairDraft.empty()]; 68 | } 69 | 70 | notifyListeners(); 71 | } 72 | 73 | CardPairDraft get firstCardPair => _cardPairs[0]; 74 | 75 | set firstCardPair(CardPairDraft cardPair) { 76 | if (cardPair == _cardPairs[0]) { 77 | return; 78 | } 79 | 80 | _cardPairs[0] = cardPair; 81 | 82 | notifyListeners(); 83 | } 84 | 85 | Set get rankPairs => _rankPairs; 86 | 87 | set rankPairs(Set rankPairs) { 88 | if (rankPairs == _rankPairs) { 89 | return; 90 | } 91 | 92 | _rankPairs = rankPairs; 93 | 94 | notifyListeners(); 95 | } 96 | 97 | bool get isEmpty => toHandRange().isEmpty; 98 | 99 | bool get isNotEmpty => toHandRange().isNotEmpty; 100 | 101 | HandRange toHandRange() { 102 | switch (_type) { 103 | case HandRangeDraftInputType.cardPair: 104 | return HandRange( 105 | firstCardPair.isComplete ? {firstCardPair.toCardPair()} : {}); 106 | case HandRangeDraftInputType.rankPairs: 107 | return HandRange(_rankPairs); 108 | case HandRangeDraftInputType.mixed: 109 | return HandRange({ 110 | ..._cardPairs 111 | .where((cp) => cp.isComplete) 112 | .map((cp) => cp.toCardPair()), 113 | ..._rankPairs 114 | }); 115 | default: 116 | throw UnimplementedError(); 117 | } 118 | } 119 | 120 | @override 121 | String toString() { 122 | return "{ type: $_type, cardPairs: _cardPairs, rankPairs: _rankPairs }"; 123 | } 124 | } 125 | 126 | enum HandRangeDraftInputType { 127 | cardPair, 128 | rankPairs, 129 | mixed, 130 | } 131 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | aqua 27 | 28 | 29 | 30 | 33 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /lib/src/view_models/simulation_session.dart: -------------------------------------------------------------------------------- 1 | import "package:aqua/app/services/flutter_isolate_evaluation_service.dart"; 2 | import "package:aqua/src/models/calculation.dart"; 3 | import "package:aqua/src/services/isolate_evaluation_service.dart"; 4 | import "package:aqua/src/view_models/hand_range_draft_list.dart"; 5 | import "package:flutter/foundation.dart"; 6 | import "package:poker/poker.dart"; 7 | 8 | class CalculationSession extends ChangeNotifier { 9 | CalculationSession.initial({ 10 | this.onStartCalculation, 11 | this.onFinishCalculation, 12 | }) : _communityCards = CardSet.empty, 13 | _players = HandRangeDraftList.empty(), 14 | _result = EvaluationResult.empty(playerLength: 0) { 15 | _players.addListener(() { 16 | _clearResults(); 17 | _enqueueCalculation(); 18 | notifyListeners(); 19 | }); 20 | } 21 | 22 | final void Function( 23 | CardSet communityCards, 24 | List players, 25 | )? onStartCalculation; 26 | 27 | final void Function(Calculation? calculation)? onFinishCalculation; 28 | 29 | HandRangeDraftList _players; 30 | 31 | CardSet _communityCards; 32 | 33 | CardSet get communityCards => _communityCards; 34 | 35 | set communityCards(CardSet communityCards) { 36 | if (communityCards == _communityCards) { 37 | return; 38 | } 39 | 40 | _communityCards = communityCards; 41 | 42 | _clearResults(); 43 | _enqueueCalculation(); 44 | notifyListeners(); 45 | } 46 | 47 | HandRangeDraftList get players => _players; 48 | 49 | EvaluationResult _result; 50 | 51 | EvaluationResult get result => _result; 52 | 53 | bool _hasPossibleMatchup = true; 54 | 55 | get hasPossibleMatchup => _hasPossibleMatchup; 56 | 57 | IsolateEvaluationService? _isolateEvaluationService; 58 | 59 | void _clearResults() { 60 | _result = EvaluationResult.empty(playerLength: players.length); 61 | } 62 | 63 | void _enqueueCalculation() async { 64 | if (_players.length <= 1) return; 65 | if (_players.hasIncomplete) return; 66 | 67 | final players = _players.map((hr) => hr.toHandRange()).toList(); 68 | 69 | if (_isolateEvaluationService != null) { 70 | _isolateEvaluationService!.dispose(); 71 | _isolateEvaluationService = null; 72 | 73 | if (onFinishCalculation != null) { 74 | onFinishCalculation!(null); 75 | } 76 | } 77 | 78 | _result = EvaluationResult.empty(playerLength: _players.length); 79 | 80 | notifyListeners(); 81 | 82 | final isolateEvaluationService = FlutterIsolateEvaluationService(); 83 | 84 | _isolateEvaluationService = isolateEvaluationService; 85 | 86 | await isolateEvaluationService.initialize(); 87 | 88 | if (onStartCalculation != null) { 89 | onStartCalculation!(communityCards, players); 90 | } 91 | 92 | final timesToSimulate = 200000; 93 | 94 | isolateEvaluationService 95 | ..onProgress.listen( 96 | (calculation) { 97 | _hasPossibleMatchup = true; 98 | _result = calculation.result; 99 | 100 | notifyListeners(); 101 | 102 | if (calculation.result.tries == timesToSimulate) { 103 | isolateEvaluationService.dispose(); 104 | _isolateEvaluationService = null; 105 | 106 | if (onFinishCalculation != null) { 107 | onFinishCalculation!(calculation); 108 | } 109 | } 110 | }, 111 | onError: (error) { 112 | isolateEvaluationService.dispose(); 113 | _isolateEvaluationService = null; 114 | 115 | // if (error is NoPossibleMatchupException) { 116 | // debugPrint("calculation canceled: ${error.runtimeType}"); 117 | 118 | _hasPossibleMatchup = false; 119 | 120 | notifyListeners(); 121 | 122 | // return; 123 | // } 124 | 125 | throw error; 126 | }, 127 | ) 128 | ..requestEvaluation( 129 | players: players, 130 | communityCards: communityCards, 131 | times: timesToSimulate, 132 | ); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /lib/src/common_widgets/aqua_button.dart: -------------------------------------------------------------------------------- 1 | import "package:aqua/src/common_widgets/aqua_theme.dart"; 2 | import "package:flutter/widgets.dart"; 3 | 4 | class AquaButton extends StatelessWidget { 5 | AquaButton({ 6 | this.variant, 7 | this.label, 8 | this.icon, 9 | this.style, 10 | this.onTap, 11 | Key? key, 12 | }) : assert(style != null || variant != null), 13 | super(key: key); 14 | 15 | final AquaButtonVariant? variant; 16 | 17 | final String? label; 18 | 19 | final IconData? icon; 20 | 21 | final AquaButtonStyle? style; 22 | 23 | final void Function()? onTap; 24 | 25 | AquaButtonStyle _resolveStyle(BuildContext context) { 26 | if (style != null) return style!; 27 | if (variant != null) return AquaTheme.of(context).buttonStyleSet[variant!]; 28 | 29 | final defaultStyle = DefaultAquaButtonStyle.of(context); 30 | 31 | assert(defaultStyle != null); 32 | 33 | return defaultStyle!; 34 | } 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | final style = _resolveStyle(context); 39 | 40 | return GestureDetector( 41 | onTap: onTap, 42 | child: DecoratedBox( 43 | decoration: ShapeDecoration( 44 | color: style.backgroundColor, 45 | shape: RoundedRectangleBorder( 46 | borderRadius: BorderRadius.circular(8.0), 47 | ), 48 | ), 49 | child: ConstrainedBox( 50 | constraints: BoxConstraints( 51 | minWidth: aquaButtonHeight, 52 | maxWidth: double.infinity, 53 | minHeight: aquaButtonHeight, 54 | maxHeight: aquaButtonHeight, 55 | ), 56 | child: Padding( 57 | padding: label != null 58 | ? const EdgeInsets.symmetric(horizontal: 12.0) 59 | : EdgeInsets.zero, 60 | child: Row( 61 | mainAxisAlignment: MainAxisAlignment.center, 62 | crossAxisAlignment: CrossAxisAlignment.center, 63 | children: [ 64 | if (icon != null) 65 | Icon( 66 | icon, 67 | size: style.labelTextStyle.fontSize! * 20 / 14, 68 | color: style.labelTextStyle.color, 69 | ), 70 | if (label != null && icon != null) SizedBox(width: 4.0), 71 | if (label != null) Text(label!, style: style.labelTextStyle), 72 | ], 73 | ), 74 | ), 75 | ), 76 | ), 77 | ); 78 | } 79 | } 80 | 81 | class DefaultAquaButtonStyle extends InheritedWidget { 82 | DefaultAquaButtonStyle({ 83 | required this.style, 84 | required Widget child, 85 | Key? key, 86 | }) : super(key: key, child: child); 87 | 88 | final AquaButtonStyle style; 89 | 90 | @override 91 | bool updateShouldNotify(DefaultAquaButtonStyle old) => style != old.style; 92 | 93 | static AquaButtonStyle? of(BuildContext context) { 94 | final a = 95 | context.dependOnInheritedWidgetOfExactType(); 96 | 97 | return a != null ? a.style : null; 98 | } 99 | } 100 | 101 | enum AquaButtonVariant { 102 | normal, 103 | primary, 104 | secondary, 105 | danger, 106 | } 107 | 108 | @immutable 109 | class AquaButtonStyleSet { 110 | const AquaButtonStyleSet({ 111 | required this.normal, 112 | required this.primary, 113 | required this.secondary, 114 | required this.danger, 115 | }); 116 | 117 | final AquaButtonStyle normal; 118 | 119 | final AquaButtonStyle primary; 120 | 121 | final AquaButtonStyle secondary; 122 | 123 | final AquaButtonStyle danger; 124 | 125 | AquaButtonStyle operator [](AquaButtonVariant variant) { 126 | switch (variant) { 127 | case AquaButtonVariant.normal: 128 | return normal; 129 | case AquaButtonVariant.primary: 130 | return primary; 131 | case AquaButtonVariant.secondary: 132 | return secondary; 133 | case AquaButtonVariant.danger: 134 | return danger; 135 | } 136 | } 137 | } 138 | 139 | class AquaButtonStyle { 140 | const AquaButtonStyle({ 141 | required this.labelTextStyle, 142 | required this.backgroundColor, 143 | }); 144 | 145 | final TextStyle labelTextStyle; 146 | 147 | final Color backgroundColor; 148 | } 149 | 150 | const aquaButtonHeight = 36.0; 151 | -------------------------------------------------------------------------------- /lib/app/main_route/preferences_tab_page.dart: -------------------------------------------------------------------------------- 1 | import "package:aqua/src/common_widgets/analytics.dart"; 2 | import "package:aqua/src/common_widgets/aqua_preferences.dart"; 3 | import "package:aqua/src/common_widgets/aqua_scaffold.dart"; 4 | import "package:aqua/src/common_widgets/aqua_theme.dart"; 5 | import "package:aqua/src/utilities/system_ui_overlay_style.dart"; 6 | import "package:flutter/cupertino.dart"; 7 | 8 | class PreferencesTabPage extends StatefulWidget { 9 | PreferencesTabPage({Key? key}) : super(key: key); 10 | 11 | @override 12 | State createState() => _PreferencesTabPageState(); 13 | } 14 | 15 | class _PreferencesTabPageState extends State { 16 | @override 17 | void initState() { 18 | super.initState(); 19 | 20 | WidgetsBinding.instance?.addPostFrameCallback((_) { 21 | Analytics.of(context).logScreenChange( 22 | screenName: "Preferences Screen", 23 | ); 24 | }); 25 | } 26 | 27 | @override 28 | void didChangeDependencies() { 29 | super.didChangeDependencies(); 30 | 31 | final theme = AquaTheme.of(context); 32 | 33 | setSystemUIOverlayStyle( 34 | topColor: theme.scaffoldStyle.backgroundColor, 35 | bottomColor: theme.scaffoldStyle.backgroundColor, 36 | ); 37 | } 38 | 39 | @override 40 | Widget build(BuildContext context) { 41 | final theme = AquaTheme.of(context); 42 | final preferences = AquaPreferences.of(context); 43 | 44 | return AquaScadffold( 45 | title: "Preferences", 46 | slivers: [ 47 | SliverToBoxAdapter( 48 | child: Padding( 49 | padding: const EdgeInsets.symmetric(horizontal: 16.0), 50 | child: Column( 51 | crossAxisAlignment: CrossAxisAlignment.start, 52 | children: [ 53 | Text( 54 | "Calculation", 55 | style: theme.textStyleSet.headline, 56 | ), 57 | SizedBox(height: 16.0), 58 | AnimatedBuilder( 59 | animation: preferences, 60 | builder: (context, _) => Padding( 61 | padding: const EdgeInsets.symmetric(vertical: 8.0), 62 | child: Row( 63 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 64 | crossAxisAlignment: CrossAxisAlignment.start, 65 | children: [ 66 | Expanded( 67 | child: Column( 68 | crossAxisAlignment: CrossAxisAlignment.start, 69 | children: [ 70 | Text( 71 | "Display in Equity", 72 | style: theme.textStyleSet.body, 73 | ), 74 | SizedBox(height: 8.0), 75 | Text( 76 | preferences.prefersWinRate 77 | ? "Shows both win and tie rate.\n\"Win\" is you're the only player who got the pot. \"Tie\" is when you share the pot with others." 78 | : "Shows how many probability of shares for the pot you have.", 79 | style: theme.textStyleSet.caption, 80 | ), 81 | ], 82 | ), 83 | ), 84 | CupertinoSwitch( 85 | value: !preferences.prefersWinRate, 86 | onChanged: (value) { 87 | preferences.setPreferWinRate(!value); 88 | }, 89 | // TODO: 90 | // create a style class for those colors 91 | // probably including whole this preferences widgets 92 | activeColor: 93 | theme.buttonStyleSet.primary.backgroundColor, 94 | trackColor: theme.playingCardStyle.backgroundColor, 95 | ), 96 | ], 97 | ), 98 | ), 99 | ), 100 | ], 101 | ), 102 | ), 103 | ), 104 | ], 105 | ); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /lib/app/app.dart: -------------------------------------------------------------------------------- 1 | import "package:aqua/app/main_route.dart"; 2 | import "package:aqua/src/common_widgets/analytics.dart"; 3 | import "package:aqua/src/common_widgets/aqua_environment.dart"; 4 | import "package:aqua/src/common_widgets/aqua_preferences.dart"; 5 | import "package:aqua/src/common_widgets/aqua_theme.dart"; 6 | import "package:aqua/src/common_widgets/authentication.dart"; 7 | import "package:aqua/src/common_widgets/error_reporter.dart"; 8 | import "package:aqua/src/constants/theme.dart"; 9 | import "package:aqua/src/services/analytics_service.dart"; 10 | import "package:aqua/src/services/auth_manager_service.dart"; 11 | import "package:aqua/src/services/error_reporter_service.dart"; 12 | import "package:flutter/material.dart"; 13 | 14 | class AquaApp extends StatefulWidget { 15 | AquaApp({ 16 | Key? key, 17 | this.prepare, 18 | required this.analyticsService, 19 | required this.authManagerService, 20 | required this.errorReporter, 21 | required this.applicationPreferenceData, 22 | }) : super(key: key); 23 | 24 | final AnalyticsService analyticsService; 25 | 26 | final AuthManagerService authManagerService; 27 | 28 | final ErrorReporterService errorReporter; 29 | 30 | final AquaPreferenceData applicationPreferenceData; 31 | 32 | final Future Function()? prepare; 33 | 34 | @override 35 | State createState() => _AquaAppState(); 36 | } 37 | 38 | class _AquaAppState extends State { 39 | /// A singleton AquaPreferenceData object that is used in entire aqua app. 40 | 41 | bool _isReady = false; 42 | 43 | @override 44 | void initState() { 45 | super.initState(); 46 | 47 | widget.authManagerService.addListener(() { 48 | final user = widget.authManagerService.user; 49 | 50 | widget.analyticsService.setUser(user); 51 | widget.errorReporter.setUser(user); 52 | }); 53 | 54 | final user = widget.authManagerService.user; 55 | 56 | widget.analyticsService.setUser(user); 57 | widget.errorReporter.setUser(user); 58 | 59 | if (widget.prepare == null) { 60 | _isReady = true; 61 | } 62 | } 63 | 64 | @override 65 | void didChangeDependencies() { 66 | super.didChangeDependencies(); 67 | 68 | if (widget.prepare != null) { 69 | widget.prepare!().then((_) { 70 | setState(() { 71 | _isReady = true; 72 | }); 73 | }); 74 | } 75 | } 76 | 77 | @override 78 | Widget build(BuildContext context) { 79 | return ErrorReporter( 80 | service: widget.errorReporter, 81 | child: AquaEnvironment( 82 | child: Authentication( 83 | manager: widget.authManagerService, 84 | child: Analytics( 85 | analytics: widget.analyticsService, 86 | child: AquaPreferences( 87 | data: widget.applicationPreferenceData, 88 | child: _isReady 89 | ? WidgetsApp( 90 | title: "Odds Calculator", 91 | color: Color(0xff19232e), 92 | builder: (context, child) => AquaTheme( 93 | data: MediaQuery.of(context).platformBrightness == 94 | Brightness.dark 95 | ? darkTheme 96 | : lightTheme, 97 | child: child!, 98 | ), 99 | onGenerateRoute: (settings) => MainRoute(), 100 | ) 101 | : WidgetsApp( 102 | title: "Odds Calculator", 103 | color: Color(0xff19232e), 104 | builder: (context, child) => AquaTheme( 105 | data: MediaQuery.of(context).platformBrightness == 106 | Brightness.dark 107 | ? darkTheme 108 | : lightTheme, 109 | child: Container( 110 | color: Color(0xffffffff), 111 | child: Center( 112 | child: CircularProgressIndicator( 113 | valueColor: 114 | AlwaysStoppedAnimation(Color(0xff54a0ff)), 115 | ), 116 | ), 117 | ), 118 | ), 119 | ), 120 | ), 121 | ), 122 | ), 123 | ), 124 | ); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /lib/app/preset_select_route.dart: -------------------------------------------------------------------------------- 1 | import "package:aqua/src/common_widgets/analytics.dart"; 2 | import "package:aqua/src/common_widgets/aqua_scaffold.dart"; 3 | import "package:aqua/src/common_widgets/aqua_theme.dart"; 4 | import "package:aqua/src/common_widgets/readonly_rank_pair_grid.dart"; 5 | import "package:aqua/src/constants/hand_range_preset.dart"; 6 | import "package:aqua/src/utilities/system_ui_overlay_style.dart"; 7 | import "package:aqua/src/view_models/hand_range_draft.dart"; 8 | import "package:flutter/cupertino.dart"; 9 | 10 | class PresetSelectRoute extends CupertinoPageRoute { 11 | PresetSelectRoute({ 12 | RouteSettings? settings, 13 | }) : super( 14 | title: "PresetSelect", 15 | builder: (context) => _PresetSelectPage(), 16 | settings: settings, 17 | ); 18 | } 19 | 20 | class _PresetSelectPage extends StatefulWidget { 21 | _PresetSelectPage({Key? key}) : super(key: key); 22 | 23 | @override 24 | State<_PresetSelectPage> createState() => _PresetSelectPageState(); 25 | } 26 | 27 | class _PresetSelectPageState extends State<_PresetSelectPage> { 28 | @override 29 | void initState() { 30 | super.initState(); 31 | 32 | WidgetsBinding.instance?.addPostFrameCallback((_) { 33 | Analytics.of(context).logScreenChange( 34 | screenName: "Preset Select Screen", 35 | ); 36 | }); 37 | } 38 | 39 | @override 40 | void didChangeDependencies() { 41 | super.didChangeDependencies(); 42 | 43 | final theme = AquaTheme.of(context); 44 | 45 | setSystemUIOverlayStyle( 46 | topColor: theme.scaffoldStyle.backgroundColor, 47 | bottomColor: theme.scaffoldStyle.backgroundColor, 48 | ); 49 | } 50 | 51 | @override 52 | Widget build(BuildContext context) { 53 | final theme = AquaTheme.of(context); 54 | 55 | return AquaScadffold( 56 | title: "Presets", 57 | slivers: [ 58 | SliverList( 59 | delegate: SliverChildBuilderDelegate( 60 | (context, index) { 61 | if (index % 2 == 0) { 62 | final preset = bundledPresets[index ~/ 2]; 63 | final handRangeDraft = 64 | HandRangeDraft.fromHandRange(preset.handRange); 65 | 66 | return GestureDetector( 67 | behavior: HitTestBehavior.opaque, 68 | onTap: () { 69 | Analytics.of(context).logEvent( 70 | name: "Tap a Preset Item", 71 | parameters: { 72 | "Preset Name": preset.name, 73 | "Hand Range Type": handRangeDraft.type.toString(), 74 | "Bundled": true, 75 | }, 76 | ); 77 | 78 | Navigator.of(context).pop(preset); 79 | }, 80 | child: Padding( 81 | padding: const EdgeInsets.symmetric(horizontal: 16.0), 82 | child: Row( 83 | crossAxisAlignment: CrossAxisAlignment.start, 84 | children: [ 85 | SizedBox( 86 | width: 64, 87 | height: 64, 88 | child: ReadonlyRankPairGrid( 89 | rankPairs: preset.handRange.onlyRankPairs, 90 | ), 91 | ), 92 | SizedBox(width: 16.0), 93 | Column( 94 | crossAxisAlignment: CrossAxisAlignment.start, 95 | children: [ 96 | SizedBox(height: 4), 97 | Text( 98 | preset.name, 99 | style: theme.textStyleSet.body, 100 | ), 101 | SizedBox(height: 4), 102 | Text( 103 | "${(preset.handRange.length / 1326 * 100).floor()}% combs", 104 | style: theme.textStyleSet.caption, 105 | ), 106 | ], 107 | ), 108 | ], 109 | ), 110 | ), 111 | ); 112 | } 113 | 114 | return SizedBox(height: 16.0); 115 | }, 116 | childCount: bundledPresets.length * 2 - 1, 117 | ), 118 | ), 119 | ], 120 | ); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /lib/src/common_widgets/card_picker.dart: -------------------------------------------------------------------------------- 1 | import "package:aqua/src/common_widgets/playing_card.dart"; 2 | import "package:flutter/services.dart"; 3 | import "package:flutter/widgets.dart"; 4 | import "package:poker/poker.dart"; 5 | 6 | class CardPicker extends StatelessWidget { 7 | CardPicker({ 8 | CardSet? unavailableCards, 9 | void Function(Card)? onCardTap, 10 | Key? key, 11 | }) : unavailableCards = unavailableCards ?? CardSet.empty, 12 | onCardTap = onCardTap ?? ((_) {}), 13 | super(key: key); 14 | 15 | final CardSet unavailableCards; 16 | 17 | final void Function(Card) onCardTap; 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return Column( 22 | mainAxisSize: MainAxisSize.min, 23 | children: List.generate( 24 | _cards.length * 2 - 1, 25 | (i) => i % 2 == 0 26 | ? Row( 27 | children: List.generate( 28 | _cards[i ~/ 2].length * 2 - 1, 29 | (j) => j % 2 == 0 30 | ? Expanded( 31 | child: 32 | unavailableCards.contains(_cards[i ~/ 2][j ~/ 2]) 33 | ? Opacity( 34 | opacity: 0.25, 35 | child: PlayingCard( 36 | card: _cards[i ~/ 2][j ~/ 2], 37 | ), 38 | ) 39 | : GestureDetector( 40 | onTap: () { 41 | HapticFeedback.selectionClick(); 42 | 43 | onCardTap(_cards[i ~/ 2][j ~/ 2]); 44 | }, 45 | child: PlayingCard( 46 | card: _cards[i ~/ 2][j ~/ 2], 47 | ), 48 | ), 49 | ) 50 | : SizedBox(width: 2), 51 | ), 52 | ) 53 | : SizedBox(height: 2), 54 | ), 55 | ); 56 | } 57 | } 58 | 59 | final _cards = [ 60 | [ 61 | Card(rank: Rank.ace, suit: Suit.spade), 62 | Card(rank: Rank.king, suit: Suit.spade), 63 | Card(rank: Rank.queen, suit: Suit.spade), 64 | Card(rank: Rank.jack, suit: Suit.spade), 65 | Card(rank: Rank.ten, suit: Suit.spade), 66 | Card(rank: Rank.nine, suit: Suit.spade), 67 | Card(rank: Rank.eight, suit: Suit.spade), 68 | Card(rank: Rank.seven, suit: Suit.spade), 69 | Card(rank: Rank.six, suit: Suit.spade), 70 | Card(rank: Rank.five, suit: Suit.spade), 71 | Card(rank: Rank.four, suit: Suit.spade), 72 | Card(rank: Rank.trey, suit: Suit.spade), 73 | Card(rank: Rank.deuce, suit: Suit.spade), 74 | ], 75 | [ 76 | Card(rank: Rank.ace, suit: Suit.heart), 77 | Card(rank: Rank.king, suit: Suit.heart), 78 | Card(rank: Rank.queen, suit: Suit.heart), 79 | Card(rank: Rank.jack, suit: Suit.heart), 80 | Card(rank: Rank.ten, suit: Suit.heart), 81 | Card(rank: Rank.nine, suit: Suit.heart), 82 | Card(rank: Rank.eight, suit: Suit.heart), 83 | Card(rank: Rank.seven, suit: Suit.heart), 84 | Card(rank: Rank.six, suit: Suit.heart), 85 | Card(rank: Rank.five, suit: Suit.heart), 86 | Card(rank: Rank.four, suit: Suit.heart), 87 | Card(rank: Rank.trey, suit: Suit.heart), 88 | Card(rank: Rank.deuce, suit: Suit.heart), 89 | ], 90 | [ 91 | Card(rank: Rank.ace, suit: Suit.diamond), 92 | Card(rank: Rank.king, suit: Suit.diamond), 93 | Card(rank: Rank.queen, suit: Suit.diamond), 94 | Card(rank: Rank.jack, suit: Suit.diamond), 95 | Card(rank: Rank.ten, suit: Suit.diamond), 96 | Card(rank: Rank.nine, suit: Suit.diamond), 97 | Card(rank: Rank.eight, suit: Suit.diamond), 98 | Card(rank: Rank.seven, suit: Suit.diamond), 99 | Card(rank: Rank.six, suit: Suit.diamond), 100 | Card(rank: Rank.five, suit: Suit.diamond), 101 | Card(rank: Rank.four, suit: Suit.diamond), 102 | Card(rank: Rank.trey, suit: Suit.diamond), 103 | Card(rank: Rank.deuce, suit: Suit.diamond), 104 | ], 105 | [ 106 | Card(rank: Rank.ace, suit: Suit.club), 107 | Card(rank: Rank.king, suit: Suit.club), 108 | Card(rank: Rank.queen, suit: Suit.club), 109 | Card(rank: Rank.jack, suit: Suit.club), 110 | Card(rank: Rank.ten, suit: Suit.club), 111 | Card(rank: Rank.nine, suit: Suit.club), 112 | Card(rank: Rank.eight, suit: Suit.club), 113 | Card(rank: Rank.seven, suit: Suit.club), 114 | Card(rank: Rank.six, suit: Suit.club), 115 | Card(rank: Rank.five, suit: Suit.club), 116 | Card(rank: Rank.four, suit: Suit.club), 117 | Card(rank: Rank.trey, suit: Suit.club), 118 | Card(rank: Rank.deuce, suit: Suit.club), 119 | ], 120 | ]; 121 | -------------------------------------------------------------------------------- /lib/src/common_widgets/aqua_tab_bar.dart: -------------------------------------------------------------------------------- 1 | import "package:aqua/src/common_widgets/aqua_theme.dart"; 2 | import "package:flutter/widgets.dart"; 3 | 4 | class AquaTabBar extends StatefulWidget { 5 | AquaTabBar({ 6 | Key? key, 7 | required this.items, 8 | this.initialSelectedIndex = 0, 9 | this.activeIndex = 0, 10 | this.onChanged, 11 | }) : super(key: key); 12 | 13 | final List items; 14 | 15 | final int initialSelectedIndex; 16 | 17 | final int activeIndex; 18 | 19 | final void Function(int index)? onChanged; 20 | 21 | @override 22 | _AquaTabBarState createState() => _AquaTabBarState(); 23 | } 24 | 25 | class _AquaTabBarState extends State { 26 | late int _selectedIndex; 27 | 28 | @override 29 | void initState() { 30 | super.initState(); 31 | 32 | _selectedIndex = widget.activeIndex; 33 | } 34 | 35 | @override 36 | void didUpdateWidget(AquaTabBar oldWidget) { 37 | super.didUpdateWidget(oldWidget); 38 | 39 | if (widget.activeIndex != oldWidget.activeIndex) { 40 | _selectedIndex = widget.activeIndex; 41 | } 42 | } 43 | 44 | @override 45 | Widget build(BuildContext context) { 46 | return DecoratedBox( 47 | decoration: BoxDecoration( 48 | color: AquaTheme.of(context).scaffoldStyle.backgroundColor, 49 | boxShadow: AquaTheme.of(context) 50 | .elevationBoxShadows 51 | .map((boxShadow) => BoxShadow( 52 | color: boxShadow.color, 53 | offset: boxShadow.offset * -1, 54 | blurRadius: boxShadow.blurRadius, 55 | spreadRadius: boxShadow.spreadRadius, 56 | )) 57 | .toList(), 58 | ), 59 | child: SafeArea( 60 | top: false, 61 | bottom: true, 62 | child: LayoutBuilder(builder: (context, constraints) { 63 | return Stack( 64 | children: [ 65 | Row( 66 | mainAxisSize: MainAxisSize.max, 67 | children: List.generate(widget.items.length, (index) { 68 | return Expanded( 69 | child: GestureDetector( 70 | behavior: HitTestBehavior.opaque, 71 | onTap: () { 72 | setState(() { 73 | _selectedIndex = index; 74 | }); 75 | 76 | if (widget.onChanged != null) { 77 | widget.onChanged!(index); 78 | } 79 | }, 80 | child: Padding( 81 | padding: EdgeInsets.only(top: 8, bottom: 4), 82 | child: Column( 83 | children: [ 84 | TweenAnimationBuilder( 85 | tween: ColorTween( 86 | begin: AquaTheme.of(context) 87 | .textStyleSet 88 | .caption 89 | .color, 90 | end: _selectedIndex == index 91 | ? AquaTheme.of(context).cursorColor 92 | : AquaTheme.of(context) 93 | .textStyleSet 94 | .caption 95 | .color, 96 | ), 97 | duration: Duration(milliseconds: 200), 98 | builder: (context, color, _) => Icon( 99 | widget.items[index].icon, 100 | size: 24, 101 | color: color, 102 | ), 103 | ), 104 | TweenAnimationBuilder( 105 | tween: TextStyleTween( 106 | begin: AquaTheme.of(context) 107 | .textStyleSet 108 | .caption 109 | .copyWith(fontSize: 10), 110 | end: AquaTheme.of(context) 111 | .textStyleSet 112 | .caption 113 | .copyWith( 114 | color: _selectedIndex == index 115 | ? AquaTheme.of(context).cursorColor 116 | : AquaTheme.of(context) 117 | .textStyleSet 118 | .caption 119 | .color, 120 | fontSize: 10, 121 | ), 122 | ), 123 | duration: Duration(milliseconds: 200), 124 | builder: (context, style, _) => Text( 125 | widget.items[index].label, 126 | style: style, 127 | ), 128 | ), 129 | ], 130 | ), 131 | ), 132 | ), 133 | ); 134 | }), 135 | ), 136 | ], 137 | ); 138 | }), 139 | ), 140 | ); 141 | } 142 | } 143 | 144 | @immutable 145 | class AquaTabBarItem { 146 | const AquaTabBarItem({ 147 | required this.label, 148 | required this.icon, 149 | }); 150 | 151 | final String label; 152 | 153 | final IconData icon; 154 | } 155 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Amplitude (7.2.0) 3 | - amplitude_flutter (0.0.1): 4 | - Amplitude (= 7.2.0) 5 | - Flutter 6 | - Firebase/Analytics (8.6.0): 7 | - Firebase/Core 8 | - Firebase/Auth (8.6.0): 9 | - Firebase/CoreOnly 10 | - FirebaseAuth (~> 8.6.0) 11 | - Firebase/Core (8.6.0): 12 | - Firebase/CoreOnly 13 | - FirebaseAnalytics (~> 8.6.0) 14 | - Firebase/CoreOnly (8.6.0): 15 | - FirebaseCore (= 8.6.0) 16 | - firebase_analytics (8.3.1): 17 | - Firebase/Analytics (= 8.6.0) 18 | - firebase_core 19 | - Flutter 20 | - firebase_auth (3.1.0): 21 | - Firebase/Auth (= 8.6.0) 22 | - firebase_core 23 | - Flutter 24 | - firebase_core (1.6.0): 25 | - Firebase/CoreOnly (= 8.6.0) 26 | - Flutter 27 | - FirebaseAnalytics (8.6.0): 28 | - FirebaseAnalytics/AdIdSupport (= 8.6.0) 29 | - FirebaseCore (~> 8.0) 30 | - FirebaseInstallations (~> 8.0) 31 | - GoogleUtilities/AppDelegateSwizzler (~> 7.4) 32 | - GoogleUtilities/MethodSwizzler (~> 7.4) 33 | - GoogleUtilities/Network (~> 7.4) 34 | - "GoogleUtilities/NSData+zlib (~> 7.4)" 35 | - nanopb (~> 2.30908.0) 36 | - FirebaseAnalytics/AdIdSupport (8.6.0): 37 | - FirebaseCore (~> 8.0) 38 | - FirebaseInstallations (~> 8.0) 39 | - GoogleAppMeasurement (= 8.6.0) 40 | - GoogleUtilities/AppDelegateSwizzler (~> 7.4) 41 | - GoogleUtilities/MethodSwizzler (~> 7.4) 42 | - GoogleUtilities/Network (~> 7.4) 43 | - "GoogleUtilities/NSData+zlib (~> 7.4)" 44 | - nanopb (~> 2.30908.0) 45 | - FirebaseAuth (8.6.0): 46 | - FirebaseCore (~> 8.0) 47 | - GoogleUtilities/AppDelegateSwizzler (~> 7.4) 48 | - GoogleUtilities/Environment (~> 7.4) 49 | - GTMSessionFetcher/Core (~> 1.5) 50 | - FirebaseCore (8.6.0): 51 | - FirebaseCoreDiagnostics (~> 8.0) 52 | - GoogleUtilities/Environment (~> 7.4) 53 | - GoogleUtilities/Logger (~> 7.4) 54 | - FirebaseCoreDiagnostics (8.6.0): 55 | - GoogleDataTransport (~> 9.0) 56 | - GoogleUtilities/Environment (~> 7.4) 57 | - GoogleUtilities/Logger (~> 7.4) 58 | - nanopb (~> 2.30908.0) 59 | - FirebaseInstallations (8.6.0): 60 | - FirebaseCore (~> 8.0) 61 | - GoogleUtilities/Environment (~> 7.4) 62 | - GoogleUtilities/UserDefaults (~> 7.4) 63 | - PromisesObjC (< 3.0, >= 1.2) 64 | - Flutter (1.0.0) 65 | - GoogleAppMeasurement (8.6.0): 66 | - GoogleAppMeasurement/AdIdSupport (= 8.6.0) 67 | - GoogleUtilities/AppDelegateSwizzler (~> 7.4) 68 | - GoogleUtilities/MethodSwizzler (~> 7.4) 69 | - GoogleUtilities/Network (~> 7.4) 70 | - "GoogleUtilities/NSData+zlib (~> 7.4)" 71 | - nanopb (~> 2.30908.0) 72 | - GoogleAppMeasurement/AdIdSupport (8.6.0): 73 | - GoogleUtilities/AppDelegateSwizzler (~> 7.4) 74 | - GoogleUtilities/MethodSwizzler (~> 7.4) 75 | - GoogleUtilities/Network (~> 7.4) 76 | - "GoogleUtilities/NSData+zlib (~> 7.4)" 77 | - nanopb (~> 2.30908.0) 78 | - GoogleDataTransport (9.1.0): 79 | - GoogleUtilities/Environment (~> 7.2) 80 | - nanopb (~> 2.30908.0) 81 | - PromisesObjC (< 3.0, >= 1.2) 82 | - GoogleUtilities/AppDelegateSwizzler (7.5.2): 83 | - GoogleUtilities/Environment 84 | - GoogleUtilities/Logger 85 | - GoogleUtilities/Network 86 | - GoogleUtilities/Environment (7.5.2): 87 | - PromisesObjC (< 3.0, >= 1.2) 88 | - GoogleUtilities/Logger (7.5.2): 89 | - GoogleUtilities/Environment 90 | - GoogleUtilities/MethodSwizzler (7.5.2): 91 | - GoogleUtilities/Logger 92 | - GoogleUtilities/Network (7.5.2): 93 | - GoogleUtilities/Logger 94 | - "GoogleUtilities/NSData+zlib" 95 | - GoogleUtilities/Reachability 96 | - "GoogleUtilities/NSData+zlib (7.5.2)" 97 | - GoogleUtilities/Reachability (7.5.2): 98 | - GoogleUtilities/Logger 99 | - GoogleUtilities/UserDefaults (7.5.2): 100 | - GoogleUtilities/Logger 101 | - GTMSessionFetcher/Core (1.7.0) 102 | - nanopb (2.30908.0): 103 | - nanopb/decode (= 2.30908.0) 104 | - nanopb/encode (= 2.30908.0) 105 | - nanopb/decode (2.30908.0) 106 | - nanopb/encode (2.30908.0) 107 | - package_info (0.0.1): 108 | - Flutter 109 | - PromisesObjC (2.0.0) 110 | - shared_preferences (0.0.1): 111 | - Flutter 112 | 113 | DEPENDENCIES: 114 | - amplitude_flutter (from `.symlinks/plugins/amplitude_flutter/ios`) 115 | - firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`) 116 | - firebase_auth (from `.symlinks/plugins/firebase_auth/ios`) 117 | - firebase_core (from `.symlinks/plugins/firebase_core/ios`) 118 | - Flutter (from `Flutter`) 119 | - package_info (from `.symlinks/plugins/package_info/ios`) 120 | - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) 121 | 122 | SPEC REPOS: 123 | trunk: 124 | - Amplitude 125 | - Firebase 126 | - FirebaseAnalytics 127 | - FirebaseAuth 128 | - FirebaseCore 129 | - FirebaseCoreDiagnostics 130 | - FirebaseInstallations 131 | - GoogleAppMeasurement 132 | - GoogleDataTransport 133 | - GoogleUtilities 134 | - GTMSessionFetcher 135 | - nanopb 136 | - PromisesObjC 137 | 138 | EXTERNAL SOURCES: 139 | amplitude_flutter: 140 | :path: ".symlinks/plugins/amplitude_flutter/ios" 141 | firebase_analytics: 142 | :path: ".symlinks/plugins/firebase_analytics/ios" 143 | firebase_auth: 144 | :path: ".symlinks/plugins/firebase_auth/ios" 145 | firebase_core: 146 | :path: ".symlinks/plugins/firebase_core/ios" 147 | Flutter: 148 | :path: Flutter 149 | package_info: 150 | :path: ".symlinks/plugins/package_info/ios" 151 | shared_preferences: 152 | :path: ".symlinks/plugins/shared_preferences/ios" 153 | 154 | SPEC CHECKSUMS: 155 | Amplitude: c948c6f99c7f798c196523b2a5584367401d910f 156 | amplitude_flutter: 5c934cf8331619b62bbd5a69f3cfea9090b3ca94 157 | Firebase: 21ac9f28b09a8bdfc005f34c984fca84e7e8786d 158 | firebase_analytics: de68415415782a5ad1d922d5c51789afbb2bb82d 159 | firebase_auth: 2dfa8e886191c24ddcf4da34463d47b72e6d19dc 160 | firebase_core: c21ac09a8d23afd3594b56ed786bad12e5266bba 161 | FirebaseAnalytics: 8f32ae54ad42754f503354782575c4ddfc1425c3 162 | FirebaseAuth: 223adeeb2262b417532e89bf06a960e3a0a1e9e4 163 | FirebaseCore: 620b677f70f5470a8e59cb77f3ddc666f6f09785 164 | FirebaseCoreDiagnostics: 3721920bde3a9a6d5aa093c1d25e9d3e47f694af 165 | FirebaseInstallations: 0ede6ffcd215b8f93c19d9b06c1c54e2d4107e98 166 | Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c 167 | GoogleAppMeasurement: 2c0c6e2a7ab3fe730ade6379f732bdefb46f50b0 168 | GoogleDataTransport: 85fd18ff3019bb85d3f2c551d04c481dedf71fc9 169 | GoogleUtilities: 8de2a97a17e15b6b98e38e8770e2d129a57c0040 170 | GTMSessionFetcher: 43748f93435c2aa068b1cbe39655aaf600652e91 171 | nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96 172 | package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62 173 | PromisesObjC: 68159ce6952d93e17b2dfe273b8c40907db5ba58 174 | shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d 175 | 176 | PODFILE CHECKSUM: d73294e4f74b04b437987fd6964a7b06dcc2d509 177 | 178 | COCOAPODS: 1.10.2 179 | -------------------------------------------------------------------------------- /lib/src/common_widgets/digits_text.dart: -------------------------------------------------------------------------------- 1 | import "package:aqua/src/common_widgets/aqua_theme.dart"; 2 | import "package:flutter/widgets.dart"; 3 | 4 | class DigitsText extends StatelessWidget { 5 | DigitsText( 6 | this.value, { 7 | this.style, 8 | this.textStyle, 9 | this.useLargeWholeNumberPart = true, 10 | this.suffix = "%", 11 | this.fractionDigits = 2, 12 | this.showAlmostEqualPrefix = false, 13 | }); 14 | 15 | final double value; 16 | 17 | final AquaDigitTextStyle? style; 18 | 19 | final TextStyle? textStyle; 20 | 21 | final bool useLargeWholeNumberPart; 22 | 23 | final String suffix; 24 | 25 | final int fractionDigits; 26 | 27 | final bool showAlmostEqualPrefix; 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | final theme = AquaTheme.of(context); 32 | final textStyle = this.textStyle ?? DefaultTextStyle.of(context).style; 33 | final style = this.style ?? theme.digitTextStyle; 34 | 35 | return RichText( 36 | text: TextSpan( 37 | children: [ 38 | if (fractionDigits == 0 && 39 | showAlmostEqualPrefix && 40 | value % 1 != 0) ...[ 41 | TextSpan( 42 | text: "≈ ", 43 | style: textStyle 44 | .copyWith( 45 | color: this.textStyle?.color ?? style.color, 46 | fontFamily: this.textStyle?.fontFamily ?? style.fontFamily, 47 | fontWeight: this.textStyle?.fontWeight ?? style.fontWeight, 48 | ) 49 | .apply(fontSizeFactor: style.fontSizeFactor), 50 | ), 51 | ], 52 | TextSpan( 53 | text: "${(value * 100).toInt()}", 54 | style: textStyle 55 | .copyWith( 56 | color: this.textStyle?.color ?? style.color, 57 | fontFamily: this.textStyle?.fontFamily ?? style.fontFamily, 58 | fontWeight: this.textStyle?.fontWeight ?? style.fontWeight, 59 | ) 60 | .apply( 61 | fontSizeFactor: useLargeWholeNumberPart 62 | ? style.largeFontSizeFactor 63 | : style.fontSizeFactor, 64 | ), 65 | ), 66 | if (fractionDigits > 0) ...[ 67 | TextSpan( 68 | text: ".", 69 | style: textStyle 70 | .copyWith( 71 | color: this.textStyle?.color ?? style.color, 72 | fontFamily: this.textStyle?.fontFamily ?? style.fontFamily, 73 | fontWeight: this.textStyle?.fontWeight ?? style.fontWeight, 74 | ) 75 | .apply(fontSizeFactor: style.fontSizeFactor), 76 | ), 77 | TextSpan( 78 | text: 79 | "${extractDecimalPartOf(value, fractionDigits: fractionDigits)}" 80 | .padLeft(fractionDigits, "0"), 81 | style: textStyle 82 | .copyWith( 83 | color: this.textStyle?.color ?? style.color, 84 | fontFamily: this.textStyle?.fontFamily ?? style.fontFamily, 85 | fontWeight: this.textStyle?.fontWeight ?? style.fontWeight, 86 | ) 87 | .apply(fontSizeFactor: style.fontSizeFactor), 88 | ), 89 | ], 90 | TextSpan( 91 | text: suffix, 92 | style: textStyle 93 | .copyWith( 94 | color: this.textStyle?.color ?? style.color, 95 | fontFamily: this.textStyle?.fontFamily ?? style.fontFamily, 96 | ) 97 | .apply(fontSizeFactor: style.fontSizeFactor), 98 | ), 99 | ], 100 | ), 101 | ); 102 | } 103 | } 104 | 105 | int extractDecimalPartOf(double value, {required int fractionDigits}) { 106 | var onlyDecimalPart = value * 100 % 1; 107 | int asWholeNumber = 0; 108 | 109 | for (int d = 0; d < fractionDigits; ++d) { 110 | onlyDecimalPart = onlyDecimalPart * 10; 111 | asWholeNumber = asWholeNumber * 10 + onlyDecimalPart.toInt(); 112 | 113 | onlyDecimalPart = onlyDecimalPart % 1; 114 | } 115 | 116 | return asWholeNumber; 117 | } 118 | 119 | class DigitsPlaceholderText extends StatelessWidget { 120 | DigitsPlaceholderText({ 121 | this.style, 122 | this.textStyle, 123 | this.useLargeWholeNumberPart = true, 124 | this.suffix = "%", 125 | this.fractionDigits = 2, 126 | }); 127 | 128 | final AquaDigitTextStyle? style; 129 | 130 | final TextStyle? textStyle; 131 | 132 | final bool useLargeWholeNumberPart; 133 | 134 | final String suffix; 135 | 136 | final int fractionDigits; 137 | 138 | @override 139 | Widget build(BuildContext context) { 140 | final theme = AquaTheme.of(context); 141 | final textStyle = this.textStyle ?? DefaultTextStyle.of(context).style; 142 | final style = this.style ?? theme.digitTextStyle; 143 | 144 | return RichText( 145 | text: TextSpan( 146 | children: [ 147 | TextSpan( 148 | text: "??", 149 | style: textStyle 150 | .copyWith( 151 | color: this.textStyle?.color ?? style.placeholderColor, 152 | fontFamily: this.textStyle?.fontFamily ?? style.fontFamily, 153 | fontWeight: this.textStyle?.fontWeight ?? style.fontWeight, 154 | ) 155 | .apply( 156 | fontSizeFactor: useLargeWholeNumberPart 157 | ? style.largeFontSizeFactor 158 | : style.fontSizeFactor, 159 | ), 160 | ), 161 | if (fractionDigits > 0) ...[ 162 | TextSpan( 163 | text: ".", 164 | style: textStyle 165 | .copyWith( 166 | color: this.textStyle?.color ?? style.placeholderColor, 167 | fontFamily: this.textStyle?.fontFamily ?? style.fontFamily, 168 | fontWeight: this.textStyle?.fontWeight ?? style.fontWeight, 169 | ) 170 | .apply(fontSizeFactor: style.fontSizeFactor), 171 | ), 172 | TextSpan( 173 | text: "".padLeft(fractionDigits, "?"), 174 | style: textStyle 175 | .copyWith( 176 | color: this.textStyle?.color ?? style.placeholderColor, 177 | fontFamily: this.textStyle?.fontFamily ?? style.fontFamily, 178 | fontWeight: this.textStyle?.fontWeight ?? style.fontWeight, 179 | ) 180 | .apply(fontSizeFactor: style.fontSizeFactor), 181 | ), 182 | ], 183 | TextSpan( 184 | text: suffix, 185 | style: textStyle 186 | .copyWith( 187 | color: this.textStyle?.color ?? style.placeholderColor, 188 | fontFamily: this.textStyle?.fontFamily ?? style.fontFamily, 189 | ) 190 | .apply(fontSizeFactor: style.fontSizeFactor), 191 | ), 192 | ], 193 | ), 194 | ); 195 | } 196 | } 197 | 198 | class AquaDigitTextStyle { 199 | AquaDigitTextStyle({ 200 | required this.color, 201 | required this.placeholderColor, 202 | required this.fontFamily, 203 | required this.fontWeight, 204 | required this.fontSizeFactor, 205 | required this.largeFontSizeFactor, 206 | }); 207 | 208 | final Color color; 209 | 210 | final Color placeholderColor; 211 | 212 | final String fontFamily; 213 | 214 | final FontWeight fontWeight; 215 | 216 | final double fontSizeFactor; 217 | 218 | final double largeFontSizeFactor; 219 | } 220 | -------------------------------------------------------------------------------- /lib/src/constants/theme.dart: -------------------------------------------------------------------------------- 1 | import "package:aqua/src/common_widgets/aqua_button.dart"; 2 | import "package:aqua/src/common_widgets/aqua_scaffold.dart"; 3 | import "package:aqua/src/common_widgets/aqua_theme.dart"; 4 | import "package:aqua/src/common_widgets/digits_text.dart"; 5 | import "package:flutter/painting.dart"; 6 | import "package:poker/poker.dart"; 7 | 8 | const _white = Color(0xffffffff); 9 | const _lessWhite = Color(0xfff2f2f7); 10 | const _silverSand = Color(0xffC5C9D0); 11 | const _black = Color(0xff000000); 12 | const _lessBlack = Color(0xff1c1c1e); 13 | const _davysGrey = Color(0xff5A5E63); 14 | const _gunmetal = Color(0xff29333D); 15 | const _aliceBlue = Color(0xffE8ECF1); 16 | const _cadetGrey = Color(0xff98A0A8); 17 | const _lemonPeel = Color(0xffF5B83D); 18 | const _chineseYellow = Color(0xffFFB41C); 19 | const _pullmanBrown = Color(0xff503D14); 20 | const _bananaMania = Color(0xffFBE8BF); 21 | const _charcoal = Color(0xff304050); 22 | const _frenchBlue = Color(0xff368CE2); 23 | const _beauBlue = Color(0xffBDD9F5); 24 | const _prussianBlue = Color(0xff122E4A); 25 | const _candyPink = Color(0xffE96363); 26 | // ignore: unused_element 27 | const _spanishPink = Color(0xfff5bcbc); 28 | // ignore: unused_element 29 | const _darkSienna = Color(0xff4C2020); 30 | const _jungleGreen = Color(0xff19B38C); 31 | // ignore: unused_element 32 | const _lightCyan = Color(0xffC6ECE2); 33 | // ignore: unused_element 34 | const _phthaloGreen = Color(0xff062D23); 35 | 36 | const baseTextStyle = TextStyle( 37 | fontFamily: "Poppins", 38 | fontSize: 16, 39 | fontWeight: FontWeight.w400, 40 | ); 41 | 42 | final baseButtonTextStyle = baseTextStyle.copyWith( 43 | fontSize: 14, 44 | fontFamily: "Poppins", 45 | fontWeight: FontWeight.w500, 46 | ); 47 | 48 | final lightTheme = AquaThemeData( 49 | scaffoldStyle: AquaScaffoldStyle( 50 | titleTextStyle: baseTextStyle.copyWith( 51 | color: _gunmetal, 52 | fontSize: 32, 53 | fontWeight: FontWeight.w600, 54 | ), 55 | backgroundColor: _white, 56 | ), 57 | buttonStyleSet: AquaButtonStyleSet( 58 | normal: AquaButtonStyle( 59 | labelTextStyle: baseButtonTextStyle.copyWith(color: _white), 60 | backgroundColor: _gunmetal, 61 | ), 62 | primary: AquaButtonStyle( 63 | labelTextStyle: baseButtonTextStyle.copyWith(color: _white), 64 | backgroundColor: _frenchBlue, 65 | ), 66 | secondary: AquaButtonStyle( 67 | labelTextStyle: baseButtonTextStyle.copyWith(color: _frenchBlue), 68 | backgroundColor: _beauBlue, 69 | ), 70 | danger: AquaButtonStyle( 71 | labelTextStyle: baseButtonTextStyle.copyWith(color: _white), 72 | backgroundColor: _candyPink, 73 | ), 74 | ), 75 | textStyleSet: AquaTextStyleSet( 76 | headline: baseTextStyle.copyWith( 77 | color: _gunmetal, 78 | fontWeight: FontWeight.w600, 79 | fontSize: 20, 80 | ), 81 | body: baseTextStyle.copyWith(color: _gunmetal), 82 | caption: baseTextStyle.copyWith( 83 | color: _cadetGrey, 84 | fontSize: 13.0, 85 | ), 86 | errorCaption: baseTextStyle.copyWith( 87 | color: _candyPink, 88 | fontSize: 13.0, 89 | ), 90 | ), 91 | digitTextStyle: AquaDigitTextStyle( 92 | color: _gunmetal, 93 | placeholderColor: _silverSand, 94 | fontFamily: "Poppins", 95 | fontWeight: FontWeight.w700, 96 | fontSizeFactor: 1.15, 97 | largeFontSizeFactor: 2.3, 98 | ), 99 | playingCardStyle: AquaPlayingCardStyle( 100 | textStyle: TextStyle( 101 | fontFamily: "Work Sans", 102 | fontWeight: FontWeight.w600, 103 | ), 104 | backgroundColor: _lessWhite, 105 | suitColors: { 106 | Suit.spade: _charcoal, 107 | Suit.heart: _candyPink, 108 | Suit.diamond: _frenchBlue, 109 | Suit.club: _jungleGreen, 110 | }, 111 | ), 112 | rankPairGridStyle: AquaRankPairGridStyle( 113 | backgroundColor: _lessWhite, 114 | textStyle: baseTextStyle.copyWith( 115 | color: _silverSand, 116 | fontFamily: "Work Sans", 117 | ), 118 | selectedBackgroundColor: _bananaMania, 119 | selectedForegroundColor: _lemonPeel, 120 | ), 121 | sliderStyle: AquaSliderStyle( 122 | thumbColor: _gunmetal, 123 | activeTrackColor: _gunmetal, 124 | inactiveTrackColor: _silverSand, 125 | valueIndicatorColor: _gunmetal, 126 | valueIndicatorTextStyle: baseButtonTextStyle.copyWith(color: _white), 127 | ), 128 | cursorColor: _lemonPeel, 129 | elevationBoxShadows: [ 130 | BoxShadow( 131 | color: Color(0x1f000000), 132 | offset: Offset(0, 0), 133 | blurRadius: 12, 134 | ), 135 | BoxShadow( 136 | color: Color(0x0f000000), 137 | offset: Offset(0, 12.0), 138 | blurRadius: 24, 139 | ), 140 | ], 141 | ); 142 | 143 | final darkTheme = AquaThemeData( 144 | scaffoldStyle: AquaScaffoldStyle( 145 | titleTextStyle: baseTextStyle.copyWith( 146 | color: _white, 147 | fontSize: 32, 148 | fontWeight: FontWeight.w600, 149 | ), 150 | backgroundColor: _black, 151 | ), 152 | buttonStyleSet: AquaButtonStyleSet( 153 | normal: AquaButtonStyle( 154 | labelTextStyle: baseButtonTextStyle.copyWith(color: _black), 155 | backgroundColor: _aliceBlue, 156 | ), 157 | primary: AquaButtonStyle( 158 | labelTextStyle: baseButtonTextStyle.copyWith(color: _white), 159 | backgroundColor: _frenchBlue, 160 | ), 161 | secondary: AquaButtonStyle( 162 | labelTextStyle: baseButtonTextStyle.copyWith(color: _frenchBlue), 163 | backgroundColor: _prussianBlue, 164 | ), 165 | danger: AquaButtonStyle( 166 | labelTextStyle: baseButtonTextStyle.copyWith(color: _white), 167 | backgroundColor: _candyPink, 168 | ), 169 | ), 170 | textStyleSet: AquaTextStyleSet( 171 | headline: baseTextStyle.copyWith( 172 | color: _white, 173 | fontWeight: FontWeight.w600, 174 | fontSize: 20, 175 | ), 176 | body: baseTextStyle.copyWith(color: _white), 177 | caption: baseTextStyle.copyWith( 178 | color: _cadetGrey, 179 | fontSize: 13.0, 180 | ), 181 | errorCaption: baseTextStyle.copyWith( 182 | color: _candyPink, 183 | fontSize: 13.0, 184 | ), 185 | ), 186 | playingCardStyle: AquaPlayingCardStyle( 187 | textStyle: TextStyle( 188 | fontFamily: "Work Sans", 189 | fontWeight: FontWeight.w600, 190 | ), 191 | backgroundColor: _lessBlack, 192 | suitColors: { 193 | Suit.spade: _aliceBlue, 194 | Suit.heart: _candyPink, 195 | Suit.diamond: _frenchBlue, 196 | Suit.club: _jungleGreen, 197 | }, 198 | ), 199 | rankPairGridStyle: AquaRankPairGridStyle( 200 | backgroundColor: _lessBlack, 201 | textStyle: baseTextStyle.copyWith( 202 | color: _davysGrey, 203 | fontFamily: "Work Sans", 204 | ), 205 | selectedBackgroundColor: _pullmanBrown, 206 | selectedForegroundColor: _chineseYellow, 207 | ), 208 | digitTextStyle: AquaDigitTextStyle( 209 | color: _white, 210 | placeholderColor: _davysGrey, 211 | fontFamily: "Poppins", 212 | fontWeight: FontWeight.w700, 213 | fontSizeFactor: 1.15, 214 | largeFontSizeFactor: 2.3, 215 | ), 216 | sliderStyle: AquaSliderStyle( 217 | thumbColor: _aliceBlue, 218 | activeTrackColor: _aliceBlue, 219 | inactiveTrackColor: _davysGrey, 220 | valueIndicatorColor: _aliceBlue, 221 | valueIndicatorTextStyle: baseButtonTextStyle.copyWith(color: _black), 222 | ), 223 | cursorColor: _chineseYellow, 224 | elevationBoxShadows: [ 225 | BoxShadow( 226 | color: Color(0x1f000000), 227 | offset: Offset(0, 0), 228 | blurRadius: 12, 229 | ), 230 | BoxShadow( 231 | color: Color(0x0f000000), 232 | offset: Offset(0, 12.0), 233 | blurRadius: 24, 234 | ), 235 | ], 236 | ); 237 | -------------------------------------------------------------------------------- /lib/src/common_widgets/rank_pair_select_grid.dart: -------------------------------------------------------------------------------- 1 | import "package:aqua/src/common_widgets/aqua_theme.dart"; 2 | import "package:aqua/src/common_widgets/fill.dart"; 3 | import "package:aqua/src/constants/card.dart"; 4 | import "package:flutter/material.dart"; 5 | import "package:flutter/services.dart"; 6 | import "package:flutter/widgets.dart"; 7 | import "package:poker/poker.dart"; 8 | 9 | class RankPairSelectGrid extends StatefulWidget { 10 | RankPairSelectGrid({ 11 | Key? key, 12 | required this.onChanged, 13 | this.onChangeStart, 14 | this.onChangeEnd, 15 | this.value = const {}, 16 | }) : super(key: key); 17 | 18 | final void Function(Set rankPairs) onChanged; 19 | 20 | final void Function(RankPair part, bool isToMark)? onChangeStart; 21 | 22 | final void Function(RankPair part, bool wasToMark)? onChangeEnd; 23 | 24 | final Set value; 25 | 26 | @override 27 | State createState() => _RankPairSelectGridState(); 28 | } 29 | 30 | class _RankPairSelectGridState extends State { 31 | late Set selectedRange; 32 | 33 | bool isToMark = false; 34 | 35 | RankPair? lastChangedPart; 36 | 37 | @override 38 | void initState() { 39 | super.initState(); 40 | 41 | selectedRange = {...widget.value}; 42 | } 43 | 44 | @override 45 | void didUpdateWidget(RankPairSelectGrid oldWidget) { 46 | super.didUpdateWidget(oldWidget); 47 | 48 | if (oldWidget.value != widget.value) { 49 | setState(() { 50 | selectedRange = widget.value; 51 | }); 52 | } 53 | } 54 | 55 | @override 56 | Widget build(BuildContext context) => Fill( 57 | child: AspectRatio( 58 | aspectRatio: 1, 59 | child: LayoutBuilder( 60 | builder: (context, constraints) => GestureDetector( 61 | onPanStart: (details) { 62 | final x = details.localPosition.dx * 63 | Rank.values.length ~/ 64 | constraints.maxWidth; 65 | final y = details.localPosition.dy * 66 | Rank.values.length ~/ 67 | constraints.maxHeight; 68 | final rankPairsPart = x > y 69 | ? RankPair.suited( 70 | high: ranksInStrongnessOrder[y], 71 | kicker: ranksInStrongnessOrder[x], 72 | ) 73 | : RankPair.ofsuit( 74 | high: ranksInStrongnessOrder[x], 75 | kicker: ranksInStrongnessOrder[y], 76 | ); 77 | 78 | isToMark = !selectedRange.contains(rankPairsPart); 79 | 80 | if (isToMark) { 81 | HapticFeedback.lightImpact(); 82 | 83 | setState(() { 84 | selectedRange.add(rankPairsPart); 85 | lastChangedPart = rankPairsPart; 86 | }); 87 | 88 | if (widget.onChangeStart != null) { 89 | widget.onChangeStart!(rankPairsPart, true); 90 | } 91 | 92 | widget.onChanged(selectedRange); 93 | } else { 94 | HapticFeedback.lightImpact(); 95 | 96 | setState(() { 97 | selectedRange.remove(rankPairsPart); 98 | lastChangedPart = rankPairsPart; 99 | }); 100 | 101 | if (widget.onChangeStart != null) { 102 | widget.onChangeStart!(rankPairsPart, false); 103 | } 104 | 105 | widget.onChanged(selectedRange); 106 | } 107 | }, 108 | onPanUpdate: (details) { 109 | final x = details.localPosition.dx * 110 | Rank.values.length ~/ 111 | constraints.maxWidth; 112 | final y = details.localPosition.dy * 113 | Rank.values.length ~/ 114 | constraints.maxHeight; 115 | 116 | if (x < 0 || x >= Rank.values.length) return; 117 | if (y < 0 || y >= Rank.values.length) return; 118 | 119 | final rankPairsPart = x > y 120 | ? RankPair.suited( 121 | high: ranksInStrongnessOrder[y], 122 | kicker: ranksInStrongnessOrder[x], 123 | ) 124 | : RankPair.ofsuit( 125 | high: ranksInStrongnessOrder[x], 126 | kicker: ranksInStrongnessOrder[y], 127 | ); 128 | 129 | if (isToMark) { 130 | if (selectedRange.contains(rankPairsPart)) return; 131 | 132 | HapticFeedback.selectionClick(); 133 | 134 | setState(() { 135 | selectedRange.add(rankPairsPart); 136 | lastChangedPart = rankPairsPart; 137 | }); 138 | 139 | widget.onChanged(selectedRange); 140 | } else { 141 | if (!selectedRange.contains(rankPairsPart)) return; 142 | 143 | HapticFeedback.selectionClick(); 144 | 145 | setState(() { 146 | selectedRange.remove(rankPairsPart); 147 | lastChangedPart = rankPairsPart; 148 | }); 149 | 150 | widget.onChanged(selectedRange); 151 | } 152 | }, 153 | onPanEnd: (details) { 154 | if (widget.onChangeEnd != null) { 155 | widget.onChangeEnd!(lastChangedPart!, isToMark); 156 | } 157 | }, 158 | behavior: HitTestBehavior.opaque, 159 | child: Column( 160 | children: List.generate(Rank.values.length * 2 - 1, (i) { 161 | if (i % 2 == 1) return SizedBox(height: 2); 162 | 163 | final y = i ~/ 2; 164 | 165 | return Expanded( 166 | child: Row( 167 | children: List.generate(Rank.values.length * 2 - 1, (j) { 168 | if (j % 2 == 1) return SizedBox(width: 2); 169 | 170 | final x = j ~/ 2; 171 | final rankPairsPart = x > y 172 | ? RankPair.suited( 173 | high: ranksInStrongnessOrder[y], 174 | kicker: ranksInStrongnessOrder[x], 175 | ) 176 | : RankPair.ofsuit( 177 | high: ranksInStrongnessOrder[x], 178 | kicker: ranksInStrongnessOrder[y], 179 | ); 180 | 181 | return Expanded( 182 | child: RankPairSelectGridItem( 183 | rankPairsPart: rankPairsPart, 184 | isSelected: selectedRange.contains(rankPairsPart), 185 | ), 186 | ); 187 | }), 188 | ), 189 | ); 190 | }), 191 | ), 192 | ), 193 | ), 194 | ), 195 | ); 196 | } 197 | 198 | class RankPairSelectGridItem extends StatelessWidget { 199 | RankPairSelectGridItem({ 200 | required this.rankPairsPart, 201 | this.isSelected = false, 202 | Key? key, 203 | }) : super(key: key); 204 | 205 | final RankPair rankPairsPart; 206 | 207 | final bool isSelected; 208 | 209 | @override 210 | Widget build(BuildContext context) { 211 | final style = AquaTheme.of(context).rankPairGridStyle; 212 | 213 | return LayoutBuilder( 214 | builder: (context, constraints) => DecoratedBox( 215 | decoration: BoxDecoration( 216 | color: isSelected 217 | ? style.selectedBackgroundColor 218 | : style.backgroundColor, 219 | borderRadius: BorderRadius.circular(3), 220 | ), 221 | child: Center( 222 | child: Text( 223 | "${rankChars[rankPairsPart.high]}${rankChars[rankPairsPart.kicker]}", 224 | style: style.textStyle.copyWith( 225 | color: isSelected 226 | ? style.selectedForegroundColor 227 | : style.textStyle.color, 228 | fontSize: constraints.maxWidth / 2, 229 | ), 230 | ), 231 | ), 232 | ), 233 | ); 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: aqua 2 | description: A new Flutter project. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 3.0.0+31 19 | 20 | environment: 21 | sdk: ">=2.12.0 <3.0.0" 22 | 23 | dependencies: 24 | amplitude_flutter: ^3.3.0 25 | flutter: 26 | sdk: flutter 27 | firebase_analytics: ^8.3.1 28 | firebase_auth: ^3.1.0 29 | firebase_core: ^1.6.0 30 | flutter_launcher_icons: ^0.9.2 31 | package_info: ^2.0.2 32 | poker: 0.2.5 33 | sentry: ^6.0.0 34 | shared_preferences: ^2.0.7 35 | 36 | dev_dependencies: 37 | flutter_test: 38 | sdk: flutter 39 | test: ^1.16.8 40 | 41 | # For information on the generic Dart part of this file, see the 42 | # following page: https://dart.dev/tools/pub/pubspec 43 | 44 | # The following section is specific to Flutter. 45 | flutter: 46 | 47 | # The following line ensures that the Material Icons font is 48 | # included with your application, so that you can use the icons in 49 | # the material Icons class. 50 | uses-material-design: true 51 | 52 | # To add assets to your application, add an assets section, like this: 53 | # assets: 54 | # - images/a_dot_burr.jpeg 55 | # - images/a_dot_ham.jpeg 56 | 57 | # An image asset can refer to one or more resolution-specific "variants", see 58 | # https://flutter.dev/assets-and-images/#resolution-aware. 59 | 60 | # For details regarding adding assets from package dependencies, see 61 | # https://flutter.dev/assets-and-images/#from-packages 62 | 63 | # To add custom fonts to your application, add a fonts section here, 64 | # in this "flutter" section. Each entry in this list should have a 65 | # "family" key with the font family name, and a "fonts" key with a 66 | # list giving the asset and other descriptors for the font. For 67 | # example: 68 | # fonts: 69 | # - family: Schyler 70 | # fonts: 71 | # - asset: fonts/Schyler-Regular.ttf 72 | # - asset: fonts/Schyler-Italic.ttf 73 | # style: italic 74 | # - family: Trajan Pro 75 | # fonts: 76 | # - asset: fonts/TrajanPro.ttf 77 | # - asset: fonts/TrajanPro_Bold.ttf 78 | # weight: 700 79 | # 80 | # For details regarding fonts from package dependencies, 81 | # see https://flutter.dev/custom-fonts/#from-packages 82 | fonts: 83 | - family: AquaIcons 84 | fonts: 85 | - asset: assets/fonts/AquaIcons.ttf 86 | - family: Icons 87 | fonts: 88 | - asset: assets/fonts/Icons.ttf 89 | - family: Work Sans 90 | fonts: 91 | # - asset: assets/fonts/WorkSans-Thin.ttf 92 | # weight: 100 93 | # - asset: assets/fonts/WorkSans-ThinItalic.ttf 94 | # weight: 100 95 | # style: italic 96 | # - asset: assets/fonts/WorkSans-ExtraLight.ttf 97 | # weight: 200 98 | # - asset: assets/fonts/WorkSans-ExtraLightItalic.ttf 99 | # weight: 200 100 | # style: italic 101 | # - asset: assets/fonts/WorkSans-Light.ttf 102 | # weight: 300 103 | # - asset: assets/fonts/WorkSans-LightItalic.ttf 104 | # weight: 300 105 | # style: italic 106 | - asset: assets/fonts/WorkSans-Regular.ttf 107 | weight: 400 108 | # - asset: assets/fonts/WorkSans-Italic.ttf 109 | # weight: 400 110 | # style: italic 111 | # - asset: assets/fonts/WorkSans-Medium.ttf 112 | # weight: 500 113 | # - asset: assets/fonts/WorkSans-MediumItalic.ttf 114 | # weight: 500 115 | # style: italic 116 | - asset: assets/fonts/WorkSans-SemiBold.ttf 117 | weight: 600 118 | # - asset: assets/fonts/WorkSans-SemiBoldItalic.ttf 119 | # weight: 600 120 | # style: italic 121 | # - asset: assets/fonts/WorkSans-Bold.ttf 122 | # weight: 700 123 | # - asset: assets/fonts/WorkSans-BoldItalic.ttf 124 | # weight: 700 125 | # style: italic 126 | # - asset: assets/fonts/WorkSans-ExtraBold.ttf 127 | # weight: 800 128 | # - asset: assets/fonts/WorkSans-ExtraBoldItalic.ttf 129 | # weight: 800 130 | # style: italic 131 | # - asset: assets/fonts/WorkSans-Black.ttf 132 | # weight: 900 133 | # - asset: assets/fonts/WorkSans-BlackItalic.ttf 134 | # weight: 900 135 | # style: italic 136 | # - family: Source Code Pro 137 | # fonts: 138 | # - asset: assets/fonts/SourceCodePro-ExtraLight.ttf 139 | # weight: 200 140 | # - asset: assets/fonts/SourceCodePro-ExtraLightItalic.ttf 141 | # weight: 200 142 | # style: italic 143 | # - asset: assets/fonts/SourceCodePro-Light.ttf 144 | # weight: 300 145 | # - asset: assets/fonts/SourceCodePro-LightItalic.ttf 146 | # weight: 300 147 | # style: italic 148 | # - asset: assets/fonts/SourceCodePro-Regular.ttf 149 | # weight: 400 150 | # - asset: assets/fonts/SourceCodePro-Italic.ttf 151 | # weight: 400 152 | # style: italic 153 | # - asset: assets/fonts/SourceCodePro-Medium.ttf 154 | # weight: 500 155 | # - asset: assets/fonts/SourceCodePro-MediumItalic.ttf 156 | # weight: 500 157 | # style: italic 158 | # - asset: assets/fonts/SourceCodePro-SemiBold.ttf 159 | # weight: 600 160 | # - asset: assets/fonts/SourceCodePro-SemiBoldItalic.ttf 161 | # weight: 600 162 | # style: italic 163 | # - asset: assets/fonts/SourceCodePro-Bold.ttf 164 | # weight: 700 165 | # - asset: assets/fonts/SourceCodePro-BoldItalic.ttf 166 | # weight: 700 167 | # style: italic 168 | # - asset: assets/fonts/SourceCodePro-BlackItalic.ttf 169 | # weight: 900 170 | # style: italic 171 | # - asset: assets/fonts/SourceCodePro-Black.ttf 172 | # weight: 900 173 | - family: Poppins 174 | fonts: 175 | # - asset: assets/fonts/Poppins-Thin.ttf 176 | # weight: 100 177 | # - asset: assets/fonts/Poppins-ThinItalic.ttf 178 | # weight: 100 179 | # style: italic 180 | # - asset: assets/fonts/Poppins-ExtraLight.ttf 181 | # weight: 200 182 | # - asset: assets/fonts/Poppins-ExtraLightItalic.ttf 183 | # weight: 200 184 | # style: italic 185 | # - asset: assets/fonts/Poppins-Light.ttf 186 | # weight: 300 187 | # - asset: assets/fonts/Poppins-LightItalic.ttf 188 | # weight: 300 189 | # style: italic 190 | - asset: assets/fonts/Poppins-Regular.ttf 191 | weight: 400 192 | # - asset: assets/fonts/Poppins-Italic.ttf 193 | # weight: 400 194 | # style: italic 195 | - asset: assets/fonts/Poppins-Medium.ttf 196 | weight: 500 197 | # - asset: assets/fonts/Poppins-MediumItalic.ttf 198 | # weight: 500 199 | # style: italic 200 | - asset: assets/fonts/Poppins-SemiBold.ttf 201 | weight: 600 202 | # - asset: assets/fonts/Poppins-SemiBoldItalic.ttf 203 | # weight: 600 204 | # style: italic 205 | - asset: assets/fonts/Poppins-Bold.ttf 206 | weight: 700 207 | # - asset: assets/fonts/Poppins-BoldItalic.ttf 208 | # weight: 700 209 | # style: italic 210 | # - asset: assets/fonts/Poppins-ExtraBold.ttf 211 | # weight: 800 212 | # - asset: assets/fonts/Poppins-ExtraBoldItalic.ttf 213 | # weight: 800 214 | # style: italic 215 | # - asset: assets/fonts/Poppins-Black.ttf 216 | # weight: 900 217 | # - asset: assets/fonts/Poppins-BlackItalic.ttf 218 | # weight: 900 219 | # style: italic 220 | 221 | flutter_icons: 222 | android: true 223 | ios: true 224 | image_path: "assets/launcher-icon/icon@1024px.png" 225 | adaptive_icon_background: "assets/launcher-icon/adaptive-icon-background@432px.png" 226 | adaptive_icon_foreground: "assets/launcher-icon/adaptive-icon-foreground@432px.png" 227 | -------------------------------------------------------------------------------- /lib/src/constants/hand_range.dart: -------------------------------------------------------------------------------- 1 | import "package:poker/poker.dart"; 2 | 3 | final rankPairsInStrongnessOrder = [ 4 | RankPair.ofsuit(high: Rank.ace, kicker: Rank.ace), 5 | RankPair.ofsuit(high: Rank.king, kicker: Rank.king), 6 | RankPair.ofsuit(high: Rank.queen, kicker: Rank.queen), 7 | RankPair.ofsuit(high: Rank.jack, kicker: Rank.jack), 8 | RankPair.ofsuit(high: Rank.ten, kicker: Rank.ten), 9 | RankPair.ofsuit(high: Rank.nine, kicker: Rank.nine), 10 | RankPair.ofsuit(high: Rank.eight, kicker: Rank.eight), 11 | RankPair.suited(high: Rank.ace, kicker: Rank.king), 12 | RankPair.ofsuit(high: Rank.seven, kicker: Rank.seven), 13 | RankPair.suited(high: Rank.ace, kicker: Rank.queen), 14 | RankPair.ofsuit(high: Rank.ace, kicker: Rank.king), 15 | RankPair.suited(high: Rank.ace, kicker: Rank.jack), 16 | RankPair.suited(high: Rank.ace, kicker: Rank.ten), 17 | RankPair.ofsuit(high: Rank.ace, kicker: Rank.queen), 18 | RankPair.ofsuit(high: Rank.ace, kicker: Rank.jack), 19 | RankPair.suited(high: Rank.king, kicker: Rank.queen), 20 | RankPair.ofsuit(high: Rank.six, kicker: Rank.six), 21 | RankPair.suited(high: Rank.ace, kicker: Rank.nine), 22 | RankPair.ofsuit(high: Rank.ace, kicker: Rank.ten), 23 | RankPair.suited(high: Rank.king, kicker: Rank.jack), 24 | RankPair.suited(high: Rank.ace, kicker: Rank.eight), 25 | RankPair.suited(high: Rank.king, kicker: Rank.ten), 26 | RankPair.ofsuit(high: Rank.king, kicker: Rank.queen), 27 | RankPair.suited(high: Rank.ace, kicker: Rank.seven), 28 | RankPair.ofsuit(high: Rank.ace, kicker: Rank.nine), 29 | RankPair.ofsuit(high: Rank.king, kicker: Rank.jack), 30 | RankPair.suited(high: Rank.queen, kicker: Rank.jack), 31 | RankPair.ofsuit(high: Rank.five, kicker: Rank.five), 32 | RankPair.ofsuit(high: Rank.ace, kicker: Rank.eight), 33 | RankPair.suited(high: Rank.ace, kicker: Rank.six), 34 | RankPair.suited(high: Rank.king, kicker: Rank.nine), 35 | RankPair.suited(high: Rank.ace, kicker: Rank.five), 36 | RankPair.ofsuit(high: Rank.king, kicker: Rank.ten), 37 | RankPair.suited(high: Rank.queen, kicker: Rank.ten), 38 | RankPair.ofsuit(high: Rank.ace, kicker: Rank.seven), 39 | RankPair.suited(high: Rank.ace, kicker: Rank.four), 40 | RankPair.suited(high: Rank.king, kicker: Rank.eight), 41 | RankPair.ofsuit(high: Rank.queen, kicker: Rank.jack), 42 | RankPair.suited(high: Rank.ace, kicker: Rank.trey), 43 | RankPair.ofsuit(high: Rank.king, kicker: Rank.nine), 44 | RankPair.suited(high: Rank.queen, kicker: Rank.nine), 45 | RankPair.ofsuit(high: Rank.ace, kicker: Rank.six), 46 | RankPair.suited(high: Rank.king, kicker: Rank.seven), 47 | RankPair.ofsuit(high: Rank.ace, kicker: Rank.five), 48 | RankPair.suited(high: Rank.jack, kicker: Rank.ten), 49 | RankPair.ofsuit(high: Rank.queen, kicker: Rank.ten), 50 | RankPair.suited(high: Rank.ace, kicker: Rank.deuce), 51 | RankPair.ofsuit(high: Rank.four, kicker: Rank.four), 52 | RankPair.suited(high: Rank.king, kicker: Rank.six), 53 | RankPair.ofsuit(high: Rank.ace, kicker: Rank.four), 54 | RankPair.ofsuit(high: Rank.king, kicker: Rank.eight), 55 | RankPair.suited(high: Rank.queen, kicker: Rank.eight), 56 | RankPair.suited(high: Rank.king, kicker: Rank.five), 57 | RankPair.suited(high: Rank.jack, kicker: Rank.nine), 58 | RankPair.ofsuit(high: Rank.ace, kicker: Rank.trey), 59 | RankPair.ofsuit(high: Rank.queen, kicker: Rank.nine), 60 | RankPair.ofsuit(high: Rank.king, kicker: Rank.seven), 61 | RankPair.ofsuit(high: Rank.jack, kicker: Rank.ten), 62 | RankPair.suited(high: Rank.king, kicker: Rank.four), 63 | RankPair.ofsuit(high: Rank.ace, kicker: Rank.deuce), 64 | RankPair.suited(high: Rank.queen, kicker: Rank.seven), 65 | RankPair.ofsuit(high: Rank.king, kicker: Rank.six), 66 | RankPair.suited(high: Rank.ten, kicker: Rank.nine), 67 | RankPair.suited(high: Rank.jack, kicker: Rank.eight), 68 | RankPair.suited(high: Rank.king, kicker: Rank.trey), 69 | RankPair.ofsuit(high: Rank.queen, kicker: Rank.eight), 70 | RankPair.suited(high: Rank.queen, kicker: Rank.six), 71 | RankPair.ofsuit(high: Rank.trey, kicker: Rank.trey), 72 | RankPair.ofsuit(high: Rank.jack, kicker: Rank.nine), 73 | RankPair.ofsuit(high: Rank.king, kicker: Rank.five), 74 | RankPair.suited(high: Rank.king, kicker: Rank.deuce), 75 | RankPair.suited(high: Rank.queen, kicker: Rank.five), 76 | RankPair.suited(high: Rank.ten, kicker: Rank.eight), 77 | RankPair.suited(high: Rank.jack, kicker: Rank.seven), 78 | RankPair.ofsuit(high: Rank.king, kicker: Rank.four), 79 | RankPair.ofsuit(high: Rank.queen, kicker: Rank.seven), 80 | RankPair.suited(high: Rank.queen, kicker: Rank.four), 81 | RankPair.ofsuit(high: Rank.jack, kicker: Rank.eight), 82 | RankPair.ofsuit(high: Rank.ten, kicker: Rank.nine), 83 | RankPair.ofsuit(high: Rank.king, kicker: Rank.trey), 84 | RankPair.ofsuit(high: Rank.queen, kicker: Rank.six), 85 | RankPair.suited(high: Rank.nine, kicker: Rank.eight), 86 | RankPair.suited(high: Rank.ten, kicker: Rank.seven), 87 | RankPair.suited(high: Rank.jack, kicker: Rank.six), 88 | RankPair.suited(high: Rank.queen, kicker: Rank.trey), 89 | RankPair.ofsuit(high: Rank.deuce, kicker: Rank.deuce), 90 | RankPair.ofsuit(high: Rank.king, kicker: Rank.deuce), 91 | RankPair.ofsuit(high: Rank.queen, kicker: Rank.five), 92 | RankPair.suited(high: Rank.jack, kicker: Rank.five), 93 | RankPair.ofsuit(high: Rank.ten, kicker: Rank.eight), 94 | RankPair.suited(high: Rank.queen, kicker: Rank.deuce), 95 | RankPair.ofsuit(high: Rank.jack, kicker: Rank.seven), 96 | RankPair.suited(high: Rank.nine, kicker: Rank.seven), 97 | RankPair.suited(high: Rank.ten, kicker: Rank.six), 98 | RankPair.ofsuit(high: Rank.queen, kicker: Rank.four), 99 | RankPair.suited(high: Rank.jack, kicker: Rank.four), 100 | RankPair.ofsuit(high: Rank.nine, kicker: Rank.eight), 101 | RankPair.ofsuit(high: Rank.ten, kicker: Rank.seven), 102 | RankPair.suited(high: Rank.eight, kicker: Rank.seven), 103 | RankPair.ofsuit(high: Rank.queen, kicker: Rank.trey), 104 | RankPair.ofsuit(high: Rank.jack, kicker: Rank.six), 105 | RankPair.suited(high: Rank.jack, kicker: Rank.trey), 106 | RankPair.suited(high: Rank.nine, kicker: Rank.six), 107 | RankPair.suited(high: Rank.ten, kicker: Rank.five), 108 | RankPair.ofsuit(high: Rank.jack, kicker: Rank.five), 109 | RankPair.suited(high: Rank.jack, kicker: Rank.deuce), 110 | RankPair.ofsuit(high: Rank.queen, kicker: Rank.deuce), 111 | RankPair.ofsuit(high: Rank.nine, kicker: Rank.seven), 112 | RankPair.suited(high: Rank.eight, kicker: Rank.six), 113 | RankPair.suited(high: Rank.ten, kicker: Rank.four), 114 | RankPair.ofsuit(high: Rank.ten, kicker: Rank.six), 115 | RankPair.ofsuit(high: Rank.jack, kicker: Rank.four), 116 | RankPair.suited(high: Rank.nine, kicker: Rank.five), 117 | RankPair.suited(high: Rank.seven, kicker: Rank.six), 118 | RankPair.suited(high: Rank.ten, kicker: Rank.trey), 119 | RankPair.ofsuit(high: Rank.eight, kicker: Rank.seven), 120 | RankPair.ofsuit(high: Rank.jack, kicker: Rank.trey), 121 | RankPair.ofsuit(high: Rank.nine, kicker: Rank.six), 122 | RankPair.suited(high: Rank.eight, kicker: Rank.five), 123 | RankPair.suited(high: Rank.ten, kicker: Rank.deuce), 124 | RankPair.ofsuit(high: Rank.ten, kicker: Rank.five), 125 | RankPair.ofsuit(high: Rank.jack, kicker: Rank.deuce), 126 | RankPair.suited(high: Rank.nine, kicker: Rank.four), 127 | RankPair.suited(high: Rank.seven, kicker: Rank.five), 128 | RankPair.ofsuit(high: Rank.eight, kicker: Rank.six), 129 | RankPair.ofsuit(high: Rank.ten, kicker: Rank.four), 130 | RankPair.suited(high: Rank.nine, kicker: Rank.trey), 131 | RankPair.suited(high: Rank.six, kicker: Rank.five), 132 | RankPair.ofsuit(high: Rank.nine, kicker: Rank.five), 133 | RankPair.suited(high: Rank.eight, kicker: Rank.four), 134 | RankPair.ofsuit(high: Rank.seven, kicker: Rank.six), 135 | RankPair.ofsuit(high: Rank.ten, kicker: Rank.trey), 136 | RankPair.suited(high: Rank.nine, kicker: Rank.deuce), 137 | RankPair.suited(high: Rank.seven, kicker: Rank.four), 138 | RankPair.ofsuit(high: Rank.eight, kicker: Rank.five), 139 | RankPair.ofsuit(high: Rank.ten, kicker: Rank.deuce), 140 | RankPair.suited(high: Rank.six, kicker: Rank.four), 141 | RankPair.suited(high: Rank.five, kicker: Rank.four), 142 | RankPair.suited(high: Rank.eight, kicker: Rank.trey), 143 | RankPair.ofsuit(high: Rank.seven, kicker: Rank.five), 144 | RankPair.ofsuit(high: Rank.nine, kicker: Rank.four), 145 | RankPair.suited(high: Rank.eight, kicker: Rank.deuce), 146 | RankPair.ofsuit(high: Rank.six, kicker: Rank.five), 147 | RankPair.suited(high: Rank.seven, kicker: Rank.trey), 148 | RankPair.ofsuit(high: Rank.nine, kicker: Rank.trey), 149 | RankPair.ofsuit(high: Rank.eight, kicker: Rank.four), 150 | RankPair.suited(high: Rank.six, kicker: Rank.trey), 151 | RankPair.suited(high: Rank.five, kicker: Rank.trey), 152 | RankPair.ofsuit(high: Rank.nine, kicker: Rank.deuce), 153 | RankPair.ofsuit(high: Rank.seven, kicker: Rank.four), 154 | RankPair.suited(high: Rank.seven, kicker: Rank.deuce), 155 | RankPair.ofsuit(high: Rank.six, kicker: Rank.four), 156 | RankPair.suited(high: Rank.four, kicker: Rank.trey), 157 | RankPair.ofsuit(high: Rank.five, kicker: Rank.four), 158 | RankPair.ofsuit(high: Rank.eight, kicker: Rank.trey), 159 | RankPair.suited(high: Rank.six, kicker: Rank.deuce), 160 | RankPair.suited(high: Rank.five, kicker: Rank.deuce), 161 | RankPair.ofsuit(high: Rank.eight, kicker: Rank.deuce), 162 | RankPair.ofsuit(high: Rank.seven, kicker: Rank.trey), 163 | RankPair.suited(high: Rank.four, kicker: Rank.deuce), 164 | RankPair.ofsuit(high: Rank.six, kicker: Rank.trey), 165 | RankPair.ofsuit(high: Rank.five, kicker: Rank.trey), 166 | RankPair.suited(high: Rank.trey, kicker: Rank.deuce), 167 | RankPair.ofsuit(high: Rank.seven, kicker: Rank.deuce), 168 | RankPair.ofsuit(high: Rank.four, kicker: Rank.trey), 169 | RankPair.ofsuit(high: Rank.six, kicker: Rank.deuce), 170 | RankPair.ofsuit(high: Rank.five, kicker: Rank.deuce), 171 | RankPair.ofsuit(high: Rank.four, kicker: Rank.deuce), 172 | RankPair.ofsuit(high: Rank.trey, kicker: Rank.deuce), 173 | ]; 174 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | _fe_analyzer_shared: 5 | dependency: transitive 6 | description: 7 | name: _fe_analyzer_shared 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "22.0.0" 11 | amplitude_flutter: 12 | dependency: "direct main" 13 | description: 14 | name: amplitude_flutter 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "3.3.0" 18 | analyzer: 19 | dependency: transitive 20 | description: 21 | name: analyzer 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.7.1" 25 | archive: 26 | dependency: transitive 27 | description: 28 | name: archive 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "3.1.2" 32 | args: 33 | dependency: transitive 34 | description: 35 | name: args 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "2.2.0" 39 | async: 40 | dependency: transitive 41 | description: 42 | name: async 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "2.6.1" 46 | boolean_selector: 47 | dependency: transitive 48 | description: 49 | name: boolean_selector 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "2.1.0" 53 | characters: 54 | dependency: transitive 55 | description: 56 | name: characters 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "1.1.0" 60 | charcode: 61 | dependency: transitive 62 | description: 63 | name: charcode 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "1.2.0" 67 | cli_util: 68 | dependency: transitive 69 | description: 70 | name: cli_util 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "0.3.3" 74 | clock: 75 | dependency: transitive 76 | description: 77 | name: clock 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "1.1.0" 81 | collection: 82 | dependency: transitive 83 | description: 84 | name: collection 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "1.15.0" 88 | convert: 89 | dependency: transitive 90 | description: 91 | name: convert 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "3.0.1" 95 | coverage: 96 | dependency: transitive 97 | description: 98 | name: coverage 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "1.0.3" 102 | crypto: 103 | dependency: transitive 104 | description: 105 | name: crypto 106 | url: "https://pub.dartlang.org" 107 | source: hosted 108 | version: "3.0.1" 109 | fake_async: 110 | dependency: transitive 111 | description: 112 | name: fake_async 113 | url: "https://pub.dartlang.org" 114 | source: hosted 115 | version: "1.2.0" 116 | ffi: 117 | dependency: transitive 118 | description: 119 | name: ffi 120 | url: "https://pub.dartlang.org" 121 | source: hosted 122 | version: "1.1.2" 123 | file: 124 | dependency: transitive 125 | description: 126 | name: file 127 | url: "https://pub.dartlang.org" 128 | source: hosted 129 | version: "6.1.2" 130 | firebase: 131 | dependency: transitive 132 | description: 133 | name: firebase 134 | url: "https://pub.dartlang.org" 135 | source: hosted 136 | version: "9.0.1" 137 | firebase_analytics: 138 | dependency: "direct main" 139 | description: 140 | name: firebase_analytics 141 | url: "https://pub.dartlang.org" 142 | source: hosted 143 | version: "8.3.1" 144 | firebase_analytics_platform_interface: 145 | dependency: transitive 146 | description: 147 | name: firebase_analytics_platform_interface 148 | url: "https://pub.dartlang.org" 149 | source: hosted 150 | version: "2.0.1" 151 | firebase_analytics_web: 152 | dependency: transitive 153 | description: 154 | name: firebase_analytics_web 155 | url: "https://pub.dartlang.org" 156 | source: hosted 157 | version: "0.3.0+1" 158 | firebase_auth: 159 | dependency: "direct main" 160 | description: 161 | name: firebase_auth 162 | url: "https://pub.dartlang.org" 163 | source: hosted 164 | version: "3.1.0" 165 | firebase_auth_platform_interface: 166 | dependency: transitive 167 | description: 168 | name: firebase_auth_platform_interface 169 | url: "https://pub.dartlang.org" 170 | source: hosted 171 | version: "6.1.0" 172 | firebase_auth_web: 173 | dependency: transitive 174 | description: 175 | name: firebase_auth_web 176 | url: "https://pub.dartlang.org" 177 | source: hosted 178 | version: "3.1.0" 179 | firebase_core: 180 | dependency: "direct main" 181 | description: 182 | name: firebase_core 183 | url: "https://pub.dartlang.org" 184 | source: hosted 185 | version: "1.6.0" 186 | firebase_core_platform_interface: 187 | dependency: transitive 188 | description: 189 | name: firebase_core_platform_interface 190 | url: "https://pub.dartlang.org" 191 | source: hosted 192 | version: "4.0.1" 193 | firebase_core_web: 194 | dependency: transitive 195 | description: 196 | name: firebase_core_web 197 | url: "https://pub.dartlang.org" 198 | source: hosted 199 | version: "1.1.0" 200 | flutter: 201 | dependency: "direct main" 202 | description: flutter 203 | source: sdk 204 | version: "0.0.0" 205 | flutter_launcher_icons: 206 | dependency: "direct main" 207 | description: 208 | name: flutter_launcher_icons 209 | url: "https://pub.dartlang.org" 210 | source: hosted 211 | version: "0.9.2" 212 | flutter_test: 213 | dependency: "direct dev" 214 | description: flutter 215 | source: sdk 216 | version: "0.0.0" 217 | flutter_web_plugins: 218 | dependency: transitive 219 | description: flutter 220 | source: sdk 221 | version: "0.0.0" 222 | glob: 223 | dependency: transitive 224 | description: 225 | name: glob 226 | url: "https://pub.dartlang.org" 227 | source: hosted 228 | version: "2.0.1" 229 | http: 230 | dependency: transitive 231 | description: 232 | name: http 233 | url: "https://pub.dartlang.org" 234 | source: hosted 235 | version: "0.13.3" 236 | http_multi_server: 237 | dependency: transitive 238 | description: 239 | name: http_multi_server 240 | url: "https://pub.dartlang.org" 241 | source: hosted 242 | version: "3.0.1" 243 | http_parser: 244 | dependency: transitive 245 | description: 246 | name: http_parser 247 | url: "https://pub.dartlang.org" 248 | source: hosted 249 | version: "4.0.0" 250 | image: 251 | dependency: transitive 252 | description: 253 | name: image 254 | url: "https://pub.dartlang.org" 255 | source: hosted 256 | version: "3.0.2" 257 | intl: 258 | dependency: transitive 259 | description: 260 | name: intl 261 | url: "https://pub.dartlang.org" 262 | source: hosted 263 | version: "0.17.0" 264 | io: 265 | dependency: transitive 266 | description: 267 | name: io 268 | url: "https://pub.dartlang.org" 269 | source: hosted 270 | version: "1.0.3" 271 | js: 272 | dependency: transitive 273 | description: 274 | name: js 275 | url: "https://pub.dartlang.org" 276 | source: hosted 277 | version: "0.6.3" 278 | logging: 279 | dependency: transitive 280 | description: 281 | name: logging 282 | url: "https://pub.dartlang.org" 283 | source: hosted 284 | version: "1.0.1" 285 | matcher: 286 | dependency: transitive 287 | description: 288 | name: matcher 289 | url: "https://pub.dartlang.org" 290 | source: hosted 291 | version: "0.12.10" 292 | meta: 293 | dependency: transitive 294 | description: 295 | name: meta 296 | url: "https://pub.dartlang.org" 297 | source: hosted 298 | version: "1.3.0" 299 | mime: 300 | dependency: transitive 301 | description: 302 | name: mime 303 | url: "https://pub.dartlang.org" 304 | source: hosted 305 | version: "1.0.0" 306 | node_preamble: 307 | dependency: transitive 308 | description: 309 | name: node_preamble 310 | url: "https://pub.dartlang.org" 311 | source: hosted 312 | version: "2.0.1" 313 | package_config: 314 | dependency: transitive 315 | description: 316 | name: package_config 317 | url: "https://pub.dartlang.org" 318 | source: hosted 319 | version: "2.0.0" 320 | package_info: 321 | dependency: "direct main" 322 | description: 323 | name: package_info 324 | url: "https://pub.dartlang.org" 325 | source: hosted 326 | version: "2.0.2" 327 | path: 328 | dependency: transitive 329 | description: 330 | name: path 331 | url: "https://pub.dartlang.org" 332 | source: hosted 333 | version: "1.8.0" 334 | path_provider_linux: 335 | dependency: transitive 336 | description: 337 | name: path_provider_linux 338 | url: "https://pub.dartlang.org" 339 | source: hosted 340 | version: "2.0.2" 341 | path_provider_platform_interface: 342 | dependency: transitive 343 | description: 344 | name: path_provider_platform_interface 345 | url: "https://pub.dartlang.org" 346 | source: hosted 347 | version: "2.0.1" 348 | path_provider_windows: 349 | dependency: transitive 350 | description: 351 | name: path_provider_windows 352 | url: "https://pub.dartlang.org" 353 | source: hosted 354 | version: "2.0.3" 355 | pedantic: 356 | dependency: transitive 357 | description: 358 | name: pedantic 359 | url: "https://pub.dartlang.org" 360 | source: hosted 361 | version: "1.11.1" 362 | petitparser: 363 | dependency: transitive 364 | description: 365 | name: petitparser 366 | url: "https://pub.dartlang.org" 367 | source: hosted 368 | version: "4.1.0" 369 | platform: 370 | dependency: transitive 371 | description: 372 | name: platform 373 | url: "https://pub.dartlang.org" 374 | source: hosted 375 | version: "3.0.2" 376 | plugin_platform_interface: 377 | dependency: transitive 378 | description: 379 | name: plugin_platform_interface 380 | url: "https://pub.dartlang.org" 381 | source: hosted 382 | version: "2.0.1" 383 | poker: 384 | dependency: "direct main" 385 | description: 386 | name: poker 387 | url: "https://pub.dartlang.org" 388 | source: hosted 389 | version: "0.2.5" 390 | pool: 391 | dependency: transitive 392 | description: 393 | name: pool 394 | url: "https://pub.dartlang.org" 395 | source: hosted 396 | version: "1.5.0" 397 | process: 398 | dependency: transitive 399 | description: 400 | name: process 401 | url: "https://pub.dartlang.org" 402 | source: hosted 403 | version: "4.2.3" 404 | pub_semver: 405 | dependency: transitive 406 | description: 407 | name: pub_semver 408 | url: "https://pub.dartlang.org" 409 | source: hosted 410 | version: "2.0.0" 411 | sentry: 412 | dependency: "direct main" 413 | description: 414 | name: sentry 415 | url: "https://pub.dartlang.org" 416 | source: hosted 417 | version: "6.0.0" 418 | shared_preferences: 419 | dependency: "direct main" 420 | description: 421 | name: shared_preferences 422 | url: "https://pub.dartlang.org" 423 | source: hosted 424 | version: "2.0.7" 425 | shared_preferences_linux: 426 | dependency: transitive 427 | description: 428 | name: shared_preferences_linux 429 | url: "https://pub.dartlang.org" 430 | source: hosted 431 | version: "2.0.2" 432 | shared_preferences_macos: 433 | dependency: transitive 434 | description: 435 | name: shared_preferences_macos 436 | url: "https://pub.dartlang.org" 437 | source: hosted 438 | version: "2.0.2" 439 | shared_preferences_platform_interface: 440 | dependency: transitive 441 | description: 442 | name: shared_preferences_platform_interface 443 | url: "https://pub.dartlang.org" 444 | source: hosted 445 | version: "2.0.0" 446 | shared_preferences_web: 447 | dependency: transitive 448 | description: 449 | name: shared_preferences_web 450 | url: "https://pub.dartlang.org" 451 | source: hosted 452 | version: "2.0.2" 453 | shared_preferences_windows: 454 | dependency: transitive 455 | description: 456 | name: shared_preferences_windows 457 | url: "https://pub.dartlang.org" 458 | source: hosted 459 | version: "2.0.2" 460 | shelf: 461 | dependency: transitive 462 | description: 463 | name: shelf 464 | url: "https://pub.dartlang.org" 465 | source: hosted 466 | version: "1.2.0" 467 | shelf_packages_handler: 468 | dependency: transitive 469 | description: 470 | name: shelf_packages_handler 471 | url: "https://pub.dartlang.org" 472 | source: hosted 473 | version: "3.0.0" 474 | shelf_static: 475 | dependency: transitive 476 | description: 477 | name: shelf_static 478 | url: "https://pub.dartlang.org" 479 | source: hosted 480 | version: "1.1.0" 481 | shelf_web_socket: 482 | dependency: transitive 483 | description: 484 | name: shelf_web_socket 485 | url: "https://pub.dartlang.org" 486 | source: hosted 487 | version: "1.0.1" 488 | sky_engine: 489 | dependency: transitive 490 | description: flutter 491 | source: sdk 492 | version: "0.0.99" 493 | source_map_stack_trace: 494 | dependency: transitive 495 | description: 496 | name: source_map_stack_trace 497 | url: "https://pub.dartlang.org" 498 | source: hosted 499 | version: "2.1.0" 500 | source_maps: 501 | dependency: transitive 502 | description: 503 | name: source_maps 504 | url: "https://pub.dartlang.org" 505 | source: hosted 506 | version: "0.10.10" 507 | source_span: 508 | dependency: transitive 509 | description: 510 | name: source_span 511 | url: "https://pub.dartlang.org" 512 | source: hosted 513 | version: "1.8.1" 514 | stack_trace: 515 | dependency: transitive 516 | description: 517 | name: stack_trace 518 | url: "https://pub.dartlang.org" 519 | source: hosted 520 | version: "1.10.0" 521 | stream_channel: 522 | dependency: transitive 523 | description: 524 | name: stream_channel 525 | url: "https://pub.dartlang.org" 526 | source: hosted 527 | version: "2.1.0" 528 | string_scanner: 529 | dependency: transitive 530 | description: 531 | name: string_scanner 532 | url: "https://pub.dartlang.org" 533 | source: hosted 534 | version: "1.1.0" 535 | term_glyph: 536 | dependency: transitive 537 | description: 538 | name: term_glyph 539 | url: "https://pub.dartlang.org" 540 | source: hosted 541 | version: "1.2.0" 542 | test: 543 | dependency: "direct dev" 544 | description: 545 | name: test 546 | url: "https://pub.dartlang.org" 547 | source: hosted 548 | version: "1.16.8" 549 | test_api: 550 | dependency: transitive 551 | description: 552 | name: test_api 553 | url: "https://pub.dartlang.org" 554 | source: hosted 555 | version: "0.3.0" 556 | test_core: 557 | dependency: transitive 558 | description: 559 | name: test_core 560 | url: "https://pub.dartlang.org" 561 | source: hosted 562 | version: "0.3.19" 563 | typed_data: 564 | dependency: transitive 565 | description: 566 | name: typed_data 567 | url: "https://pub.dartlang.org" 568 | source: hosted 569 | version: "1.3.0" 570 | uuid: 571 | dependency: transitive 572 | description: 573 | name: uuid 574 | url: "https://pub.dartlang.org" 575 | source: hosted 576 | version: "3.0.4" 577 | vector_math: 578 | dependency: transitive 579 | description: 580 | name: vector_math 581 | url: "https://pub.dartlang.org" 582 | source: hosted 583 | version: "2.1.0" 584 | vm_service: 585 | dependency: transitive 586 | description: 587 | name: vm_service 588 | url: "https://pub.dartlang.org" 589 | source: hosted 590 | version: "6.2.0" 591 | watcher: 592 | dependency: transitive 593 | description: 594 | name: watcher 595 | url: "https://pub.dartlang.org" 596 | source: hosted 597 | version: "1.0.0" 598 | web_socket_channel: 599 | dependency: transitive 600 | description: 601 | name: web_socket_channel 602 | url: "https://pub.dartlang.org" 603 | source: hosted 604 | version: "2.1.0" 605 | webkit_inspection_protocol: 606 | dependency: transitive 607 | description: 608 | name: webkit_inspection_protocol 609 | url: "https://pub.dartlang.org" 610 | source: hosted 611 | version: "1.0.0" 612 | win32: 613 | dependency: transitive 614 | description: 615 | name: win32 616 | url: "https://pub.dartlang.org" 617 | source: hosted 618 | version: "2.2.8" 619 | xdg_directories: 620 | dependency: transitive 621 | description: 622 | name: xdg_directories 623 | url: "https://pub.dartlang.org" 624 | source: hosted 625 | version: "0.2.0" 626 | xml: 627 | dependency: transitive 628 | description: 629 | name: xml 630 | url: "https://pub.dartlang.org" 631 | source: hosted 632 | version: "5.1.2" 633 | yaml: 634 | dependency: transitive 635 | description: 636 | name: yaml 637 | url: "https://pub.dartlang.org" 638 | source: hosted 639 | version: "3.1.0" 640 | sdks: 641 | dart: ">=2.13.0 <3.0.0" 642 | flutter: ">=2.0.0" 643 | --------------------------------------------------------------------------------