├── .ruby-version ├── untranslated-localization.txt ├── ios ├── untranslated-localization.txt ├── Runner │ ├── Runner-Bridging-Header.h │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── AppIcon-dev.appiconset │ │ │ ├── 5.png │ │ │ └── Contents.json │ │ ├── AppIcon-prod.appiconset │ │ │ ├── 4.png │ │ │ └── Contents.json │ │ ├── LaunchImage.imageset │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ ├── README.md │ │ │ └── Contents.json │ │ └── LaunchBackground.imageset │ │ │ ├── background.png │ │ │ ├── darkbackground.png │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── Runner.entitlements │ ├── Base.lproj │ │ └── Main.storyboard │ └── Info.plist ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── fastlane │ ├── Appfile │ ├── Pluginfile │ ├── report.xml │ ├── README.md │ └── Fastfile ├── Runner.xcodeproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist ├── Gemfile ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist ├── RunnerTests │ └── RunnerTests.swift ├── .gitignore └── Podfile ├── linux ├── .gitignore ├── main.cc ├── flutter │ ├── generated_plugin_registrant.h │ ├── generated_plugin_registrant.cc │ └── generated_plugins.cmake └── my_application.h ├── README.md ├── web ├── favicon.png ├── dev │ ├── Icon-192.png │ ├── Icon-512.png │ ├── favicon.png │ └── manifest.json ├── prod │ ├── favicon.png │ ├── Icon-192.png │ ├── Icon-512.png │ └── manifest.json ├── icons │ ├── Icon-192.png │ └── Icon-512.png ├── splash │ └── img │ │ ├── dark-1x.png │ │ ├── dark-2x.png │ │ ├── dark-3x.png │ │ ├── dark-4x.png │ │ ├── light-1x.png │ │ ├── light-2x.png │ │ ├── light-3x.png │ │ └── light-4x.png ├── .well-known │ ├── apple-app-site-association │ └── assetlinks.json └── manifest.json ├── .fvm └── fvm_config.json ├── assets ├── images │ └── splash.png └── fonts │ ├── more-sugar-medium.ttf │ └── more-sugar-regular.ttf ├── .vscode ├── settings.json └── launch.json ├── macos ├── Runner │ ├── Configs │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ ├── Warnings.xcconfig │ │ └── AppInfo.xcconfig │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── AppIcon-dev.appiconset │ │ │ ├── 16-mac.png │ │ │ ├── 32-mac.png │ │ │ ├── 64-mac.png │ │ │ ├── 1024-mac.png │ │ │ ├── 128-mac.png │ │ │ ├── 256-mac.png │ │ │ ├── 512-mac.png │ │ │ └── Contents.json │ │ └── AppIcon-prod.appiconset │ │ │ ├── 1024-mac.png │ │ │ ├── 128-mac.png │ │ │ ├── 16-mac.png │ │ │ ├── 256-mac.png │ │ │ ├── 32-mac.png │ │ │ ├── 512-mac.png │ │ │ ├── 64-mac.png │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── MainFlutterWindow.swift │ ├── Release.entitlements │ ├── DebugProfile.entitlements │ └── Info.plist ├── .gitignore ├── Flutter │ ├── Flutter-Debug.xcconfig │ ├── Flutter-Release.xcconfig │ └── GeneratedPluginRegistrant.swift ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── Runner.xcodeproj │ └── project.xcworkspace │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── RunnerTests │ └── RunnerTests.swift └── Podfile ├── windows ├── runner │ ├── resources │ │ └── app_icon.ico │ ├── resource.h │ ├── utils.h │ ├── runner.exe.manifest │ ├── flutter_window.h │ ├── main.cpp │ ├── CMakeLists.txt │ ├── utils.cpp │ └── flutter_window.cpp ├── .gitignore └── flutter │ ├── generated_plugin_registrant.h │ ├── generated_plugins.cmake │ └── generated_plugin_registrant.cc ├── android ├── app │ └── src │ │ ├── dev │ │ ├── ic_launcher-playstore.png │ │ └── res │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.webp │ │ │ ├── ic_launcher_round.webp │ │ │ └── ic_launcher_foreground.webp │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.webp │ │ │ ├── ic_launcher_round.webp │ │ │ └── ic_launcher_foreground.webp │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.webp │ │ │ ├── ic_launcher_round.webp │ │ │ └── ic_launcher_foreground.webp │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.webp │ │ │ ├── ic_launcher_round.webp │ │ │ └── ic_launcher_foreground.webp │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.webp │ │ │ ├── ic_launcher_round.webp │ │ │ └── ic_launcher_foreground.webp │ │ │ └── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── prod │ │ ├── ic_launcher-playstore.png │ │ └── res │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.webp │ │ │ ├── ic_launcher_round.webp │ │ │ └── ic_launcher_foreground.webp │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.webp │ │ │ ├── ic_launcher_round.webp │ │ │ └── ic_launcher_foreground.webp │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.webp │ │ │ ├── ic_launcher_round.webp │ │ │ └── ic_launcher_foreground.webp │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.webp │ │ │ ├── ic_launcher_round.webp │ │ │ └── ic_launcher_foreground.webp │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.webp │ │ │ ├── ic_launcher_round.webp │ │ │ └── ic_launcher_foreground.webp │ │ │ └── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ └── main │ │ ├── res │ │ ├── drawable-hdpi │ │ │ ├── splash.png │ │ │ └── android12splash.png │ │ ├── drawable-mdpi │ │ │ ├── splash.png │ │ │ └── android12splash.png │ │ ├── drawable-xhdpi │ │ │ ├── splash.png │ │ │ └── android12splash.png │ │ ├── drawable │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ ├── drawable-v21 │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ ├── drawable-xxhdpi │ │ │ ├── splash.png │ │ │ └── android12splash.png │ │ ├── drawable-xxxhdpi │ │ │ ├── splash.png │ │ │ └── android12splash.png │ │ ├── drawable-night │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ ├── drawable-night-v21 │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ ├── drawable-night-hdpi │ │ │ └── android12splash.png │ │ ├── drawable-night-mdpi │ │ │ └── android12splash.png │ │ ├── drawable-night-xhdpi │ │ │ └── android12splash.png │ │ ├── drawable-night-xxhdpi │ │ │ └── android12splash.png │ │ ├── drawable-night-xxxhdpi │ │ │ └── android12splash.png │ │ ├── values-v31 │ │ │ └── styles.xml │ │ ├── values-night-v31 │ │ │ └── styles.xml │ │ ├── values │ │ │ └── styles.xml │ │ └── values-night │ │ │ └── styles.xml │ │ └── kotlin │ │ └── com │ │ └── skribla │ │ └── MainActivity.kt ├── fastlane │ ├── Appfile │ ├── Pluginfile │ ├── report.xml │ ├── README.md │ └── Fastfile ├── Gemfile ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── gradle.properties ├── .gitignore ├── build.gradle └── settings.gradle ├── lib ├── env │ ├── sample_creds.json │ └── env.dart ├── src │ ├── core │ │ ├── resource │ │ │ ├── app_keys.dart │ │ │ ├── firebase_paths.dart │ │ │ └── app_icons.dart │ │ ├── router │ │ │ └── routes.dart │ │ ├── util │ │ │ ├── types.dart │ │ │ ├── constants.dart │ │ │ ├── device_info.dart │ │ │ ├── result.dart │ │ │ ├── generated │ │ │ │ ├── device_info.g.dart │ │ │ │ └── feature_flags.g.dart │ │ │ ├── enums.dart │ │ │ ├── converters.dart │ │ │ └── feature_flags.dart │ │ ├── platform │ │ │ ├── web.dart │ │ │ └── others.dart │ │ ├── widgets │ │ │ ├── gradient_text.dart │ │ │ ├── shimmer_widget.dart │ │ │ ├── default_app_bar.dart │ │ │ ├── progress_bar.dart │ │ │ └── error_widget.dart │ │ ├── observers │ │ │ └── provider_watch.dart │ │ ├── screens │ │ │ ├── unavailable_screen.dart │ │ │ ├── update_screen.dart │ │ │ └── suspended_screen.dart │ │ ├── theme │ │ │ ├── app_theme.dart │ │ │ └── colors.dart │ │ └── service │ │ │ ├── logger.dart │ │ │ ├── haptics.dart │ │ │ └── analytics.dart │ └── app │ │ ├── settings │ │ ├── data │ │ │ └── repository │ │ │ │ └── settings_repository.dart │ │ └── presentation │ │ │ ├── provider │ │ │ ├── settings_state.dart │ │ │ ├── loc_provider.dart │ │ │ └── settings_provider.dart │ │ │ └── widgets │ │ │ └── custom_list_tile.dart │ │ ├── home │ │ └── presentation │ │ │ ├── provider │ │ │ └── home_state.dart │ │ │ └── widgets │ │ │ ├── start_action.dart │ │ │ └── logo_text.dart │ │ ├── history │ │ ├── presentation │ │ │ ├── provider │ │ │ │ └── history_state.dart │ │ │ └── widgets │ │ │ │ ├── light_painter.dart │ │ │ │ ├── exhibit_footer.dart │ │ │ │ └── shared_widget.dart │ │ └── data │ │ │ └── models │ │ │ ├── exhibit_model.dart │ │ │ └── generated │ │ │ └── exhibit_model.g.dart │ │ ├── auth │ │ ├── presentation │ │ │ └── provider │ │ │ │ └── auth_state.dart │ │ └── data │ │ │ └── models │ │ │ ├── generated │ │ │ └── user_model.g.dart │ │ │ └── user_model.dart │ │ ├── game │ │ ├── presentation │ │ │ ├── provider │ │ │ │ ├── game_state.dart │ │ │ │ └── timer_state.dart │ │ │ └── widgets │ │ │ │ ├── star_confetti.dart │ │ │ │ ├── report_reason_sheet.dart │ │ │ │ └── art_painter.dart │ │ └── data │ │ │ └── models │ │ │ ├── report_model.dart │ │ │ ├── line_model.dart │ │ │ ├── generated │ │ │ ├── report_model.g.dart │ │ │ ├── line_model.g.dart │ │ │ ├── word_model.g.dart │ │ │ ├── player_model.g.dart │ │ │ ├── message_model.g.dart │ │ │ └── game_model.g.dart │ │ │ ├── word_model.dart │ │ │ ├── player_model.dart │ │ │ └── message_model.dart │ │ ├── leaderboard │ │ ├── data │ │ │ └── models │ │ │ │ ├── leaderboard_model.dart │ │ │ │ └── generated │ │ │ │ └── leaderboard_model.g.dart │ │ └── presentation │ │ │ ├── provider │ │ │ └── leaderboard_state.dart │ │ │ └── widgets │ │ │ └── leaderboard_item.dart │ │ └── main_app.dart ├── main_dev.dart └── main_prod.dart ├── .firebaserc ├── l10n.yaml ├── release_notes.txt ├── flutter_native_splash.yaml ├── analysis_options.yaml ├── release_notes.sh ├── web_flavor_setup.sh ├── shorebird.yaml ├── bin └── scripts │ └── check_duplicates.dart ├── test └── widget_test.dart ├── .gitignore ├── .metadata ├── pubspec.yaml └── Makefile /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.3.4 2 | -------------------------------------------------------------------------------- /untranslated-localization.txt: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /ios/untranslated-localization.txt: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # skribla 2 | 3 | A new Flutter project. 4 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/web/favicon.png -------------------------------------------------------------------------------- /.fvm/fvm_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "flutterSdkVersion": "3.24.3", 3 | "flavors": {} 4 | } -------------------------------------------------------------------------------- /web/dev/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/web/dev/Icon-192.png -------------------------------------------------------------------------------- /web/dev/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/web/dev/Icon-512.png -------------------------------------------------------------------------------- /web/dev/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/web/dev/favicon.png -------------------------------------------------------------------------------- /web/prod/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/web/prod/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/prod/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/web/prod/Icon-192.png -------------------------------------------------------------------------------- /web/prod/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/web/prod/Icon-512.png -------------------------------------------------------------------------------- /assets/images/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/assets/images/splash.png -------------------------------------------------------------------------------- /web/splash/img/dark-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/web/splash/img/dark-1x.png -------------------------------------------------------------------------------- /web/splash/img/dark-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/web/splash/img/dark-2x.png -------------------------------------------------------------------------------- /web/splash/img/dark-3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/web/splash/img/dark-3x.png -------------------------------------------------------------------------------- /web/splash/img/dark-4x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/web/splash/img/dark-4x.png -------------------------------------------------------------------------------- /web/splash/img/light-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/web/splash/img/light-1x.png -------------------------------------------------------------------------------- /web/splash/img/light-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/web/splash/img/light-2x.png -------------------------------------------------------------------------------- /web/splash/img/light-3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/web/splash/img/light-3x.png -------------------------------------------------------------------------------- /web/splash/img/light-4x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/web/splash/img/light-4x.png -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "dart.lineLength": 100, 3 | "dart.flutterSdkPath": ".fvm/flutter_sdk" 4 | } -------------------------------------------------------------------------------- /assets/fonts/more-sugar-medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/assets/fonts/more-sugar-medium.ttf -------------------------------------------------------------------------------- /assets/fonts/more-sugar-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/assets/fonts/more-sugar-regular.ttf -------------------------------------------------------------------------------- /macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/dev/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/dev/ic_launcher-playstore.png -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /lib/env/sample_creds.json: -------------------------------------------------------------------------------- 1 | { 2 | "BASE_URL": "", 3 | "LUKE_HOG_ID": "", 4 | "GID_CLIENT_ID_WEB": "", 5 | "SENTRY_DNS": "" 6 | } -------------------------------------------------------------------------------- /android/app/src/prod/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/prod/ic_launcher-playstore.png -------------------------------------------------------------------------------- /android/fastlane/Appfile: -------------------------------------------------------------------------------- 1 | json_key_file("/Users/ifeanyionuoha/skribla/skribla-prod-play-store.json") 2 | package_name("com.skribla.android.prod") 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/main/res/drawable-hdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/main/res/drawable-mdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/main/res/drawable-xhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/main/res/drawable/background.png -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "skribla-prod", 4 | "skribla-dev": "skribla-dev" 5 | }, 6 | "targets": {}, 7 | "etags": {} 8 | } -------------------------------------------------------------------------------- /android/app/src/dev/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/dev/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /android/app/src/dev/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/dev/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/main/res/drawable-v21/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/main/res/drawable-xxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/main/res/drawable-xxxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/dev/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/dev/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /android/app/src/dev/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/dev/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /android/app/src/dev/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/dev/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/main/res/drawable-night/background.png -------------------------------------------------------------------------------- /android/app/src/prod/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/prod/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /android/app/src/prod/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/prod/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /android/app/src/prod/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/prod/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /android/app/src/prod/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/prod/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/5.png -------------------------------------------------------------------------------- /ios/fastlane/Appfile: -------------------------------------------------------------------------------- 1 | app_identifier("com.skribla.ios.prod") 2 | apple_id("onuifeanyi95@gmail.com") 3 | 4 | itc_team_id("125431871") 5 | team_id("YYA8WP4LNJ") 6 | -------------------------------------------------------------------------------- /android/app/src/prod/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/prod/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon-prod.appiconset/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/ios/Runner/Assets.xcassets/AppIcon-prod.appiconset/4.png -------------------------------------------------------------------------------- /android/app/src/dev/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/dev/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /android/app/src/dev/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/dev/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /android/app/src/dev/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/dev/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /android/app/src/dev/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/dev/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/main/res/drawable-hdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/main/res/drawable-mdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-v21/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/main/res/drawable-night-v21/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/main/res/drawable-xhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/main/res/drawable-xxhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/prod/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/prod/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /android/app/src/prod/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/prod/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /android/app/src/prod/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/prod/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /android/app/src/dev/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/dev/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/main/res/drawable-xxxhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/prod/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/prod/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /android/app/src/prod/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/prod/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon-dev.appiconset/16-mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/macos/Runner/Assets.xcassets/AppIcon-dev.appiconset/16-mac.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon-dev.appiconset/32-mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/macos/Runner/Assets.xcassets/AppIcon-dev.appiconset/32-mac.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon-dev.appiconset/64-mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/macos/Runner/Assets.xcassets/AppIcon-dev.appiconset/64-mac.png -------------------------------------------------------------------------------- /android/app/src/dev/res/mipmap-hdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/dev/res/mipmap-hdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /android/app/src/dev/res/mipmap-mdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/dev/res/mipmap-mdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /android/app/src/dev/res/mipmap-xhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/dev/res/mipmap-xhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /android/app/src/dev/res/mipmap-xxhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/dev/res/mipmap-xxhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-hdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/main/res/drawable-night-hdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-mdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/main/res/drawable-night-mdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-xhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/main/res/drawable-night-xhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/prod/res/mipmap-hdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/prod/res/mipmap-hdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /android/app/src/prod/res/mipmap-mdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/prod/res/mipmap-mdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /android/app/src/prod/res/mipmap-xhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/prod/res/mipmap-xhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon-dev.appiconset/1024-mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/macos/Runner/Assets.xcassets/AppIcon-dev.appiconset/1024-mac.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon-dev.appiconset/128-mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/macos/Runner/Assets.xcassets/AppIcon-dev.appiconset/128-mac.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon-dev.appiconset/256-mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/macos/Runner/Assets.xcassets/AppIcon-dev.appiconset/256-mac.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon-dev.appiconset/512-mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/macos/Runner/Assets.xcassets/AppIcon-dev.appiconset/512-mac.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon-prod.appiconset/1024-mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/macos/Runner/Assets.xcassets/AppIcon-prod.appiconset/1024-mac.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon-prod.appiconset/128-mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/macos/Runner/Assets.xcassets/AppIcon-prod.appiconset/128-mac.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon-prod.appiconset/16-mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/macos/Runner/Assets.xcassets/AppIcon-prod.appiconset/16-mac.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon-prod.appiconset/256-mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/macos/Runner/Assets.xcassets/AppIcon-prod.appiconset/256-mac.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon-prod.appiconset/32-mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/macos/Runner/Assets.xcassets/AppIcon-prod.appiconset/32-mac.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon-prod.appiconset/512-mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/macos/Runner/Assets.xcassets/AppIcon-prod.appiconset/512-mac.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon-prod.appiconset/64-mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/macos/Runner/Assets.xcassets/AppIcon-prod.appiconset/64-mac.png -------------------------------------------------------------------------------- /android/app/src/dev/res/mipmap-xxxhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/dev/res/mipmap-xxxhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-xxhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/prod/res/mipmap-xxhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/prod/res/mipmap-xxhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /android/app/src/prod/res/mipmap-xxxhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/android/app/src/prod/res/mipmap-xxxhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o-ifeanyi/skribla/HEAD/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/skribla/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.skribla.android.prod 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() 6 | -------------------------------------------------------------------------------- /l10n.yaml: -------------------------------------------------------------------------------- 1 | arb-dir: lib/l10n 2 | template-arb-file: app_en.arb 3 | output-localization-file: app_localizations.dart 4 | nullable-getter: false 5 | untranslated-messages-file: untranslated-localization.txt -------------------------------------------------------------------------------- /android/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "fastlane" 4 | 5 | plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') 6 | eval_gemfile(plugins_path) if File.exist?(plugins_path) 7 | -------------------------------------------------------------------------------- /linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "fastlane" 4 | gem "cocoapods", '1.15.2' 5 | 6 | plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') 7 | eval_gemfile(plugins_path) if File.exist?(plugins_path) 8 | -------------------------------------------------------------------------------- /ios/fastlane/Pluginfile: -------------------------------------------------------------------------------- 1 | # Autogenerated by fastlane 2 | # 3 | # Ensure this file is checked in to source control! 4 | 5 | gem 'fastlane-plugin-firebase_app_distribution' 6 | gem 'fastlane-plugin-shorebird' 7 | gem 'fastlane-plugin-flutter_version' 8 | -------------------------------------------------------------------------------- /lib/src/core/resource/app_keys.dart: -------------------------------------------------------------------------------- 1 | abstract class AppKeys { 2 | static const String theme = 'theme'; 3 | static const String haptics = 'haptics'; 4 | static const String version = 'version'; 5 | static const String flags = 'feature_flags'; 6 | } 7 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-all.zip 6 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | android.defaults.buildfeatures.buildconfig=true 5 | android.nonTransitiveRClass=false 6 | android.nonFinalResIds=false 7 | -------------------------------------------------------------------------------- /release_notes.txt: -------------------------------------------------------------------------------- 1 | ## What's Changed 2 | * refactor theme setup + minor fixes by o-ifeanyi 3 | * deploy new build by o-ifeanyi 4 | * add confetti for correct guess by o-ifeanyi 5 | 6 | **Full Changelog**: https://github.com/o-ifeanyi/skribla/compare/0.2.2+13...0.2.4+15 7 | -------------------------------------------------------------------------------- /macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @main 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /flutter_native_splash.yaml: -------------------------------------------------------------------------------- 1 | flutter_native_splash: 2 | color: "#f9f9fc" 3 | image: assets/images/splash.png 4 | color_dark: "#19181b" 5 | android_12: 6 | color: "#f9f9fc" 7 | image: assets/images/splash.png 8 | color_dark: "#19181b" 9 | fullscreen: true 10 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:very_good_analysis/analysis_options.5.1.0.yaml 2 | 3 | analyzer: 4 | exclude: 5 | - '**.freezed.dart' 6 | - '**.g.dart' 7 | 8 | linter: 9 | rules: 10 | public_member_api_docs: false 11 | lines_longer_than_80_chars: false 12 | -------------------------------------------------------------------------------- /android/fastlane/Pluginfile: -------------------------------------------------------------------------------- 1 | # Autogenerated by fastlane 2 | # 3 | # Ensure this file is checked in to source control! 4 | 5 | gem 'fastlane-plugin-flutter_version' 6 | gem 'fastlane-plugin-versioning_android' 7 | gem 'fastlane-plugin-shorebird' 8 | gem 'fastlane-plugin-firebase_app_distribution' 9 | -------------------------------------------------------------------------------- /lib/src/app/settings/data/repository/settings_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | 3 | final class SettingsRepository { 4 | const SettingsRepository({ 5 | required this.firebaseFirestore, 6 | }); 7 | final FirebaseFirestore firebaseFirestore; 8 | } 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/dev/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/prod/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "5.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon-prod.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "4.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /android/app/src/dev/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/prod/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/env/env.dart: -------------------------------------------------------------------------------- 1 | class Env { 2 | static const String baseUrl = String.fromEnvironment('BASE_URL'); 3 | static const String lukeHogId = String.fromEnvironment('LUKE_HOG_ID'); 4 | static const String gIdClientIdWWeb = String.fromEnvironment('GID_CLIENT_ID_WEB'); 5 | static const String sentryDNS = String.fromEnvironment('SENTRY_DNS'); 6 | } 7 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/project.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 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /macos/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /release_notes.sh: -------------------------------------------------------------------------------- 1 | git fetch --prune --unshallow --tags 2 | from=$(git describe --tags --abbrev=0) 3 | version=$(cat pubspec.yaml | grep -o 'version:[^:]*' | cut -f2 -d":" | xargs) 4 | changelog=$(git log $from..HEAD --pretty=format:"* %s by %an") 5 | echo "## What's Changed\n$changelog\n\n**Full Changelog**: https://github.com/o-ifeanyi/skribla/compare/$from...$version" > release_notes.txt -------------------------------------------------------------------------------- /windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /lib/src/app/home/presentation/provider/home_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'generated/home_state.freezed.dart'; 4 | 5 | enum HomeStatus { idle, creatingGame, findingGame, joiningGame } 6 | 7 | @freezed 8 | class HomeState with _$HomeState { 9 | const factory HomeState({ 10 | @Default(HomeStatus.idle) HomeStatus status, 11 | }) = _HomeState; 12 | } 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | rootProject.buildDir = "../build" 9 | subprojects { 10 | project.buildDir = "${rootProject.buildDir}/${project.name}" 11 | } 12 | subprojects { 13 | project.evaluationDependsOn(":app") 14 | } 15 | 16 | tasks.register("clean", Delete) { 17 | delete rootProject.buildDir 18 | } 19 | -------------------------------------------------------------------------------- /lib/src/app/history/presentation/provider/history_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'generated/history_state.freezed.dart'; 4 | 5 | enum HistoryStatus { idle, gettingHistory } 6 | 7 | @freezed 8 | class HistoryState with _$HistoryState { 9 | const factory HistoryState({ 10 | @Default(HistoryStatus.idle) HistoryStatus status, 11 | @Default(false) bool sharing, 12 | }) = _HistoryState; 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | 4 | @main 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 | -------------------------------------------------------------------------------- /linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/src/core/router/routes.dart: -------------------------------------------------------------------------------- 1 | abstract class Routes { 2 | static const String home = '/'; 3 | static const String join = 'join/:id'; 4 | static const String game = 'game/:id'; 5 | static const String history = 'history'; 6 | static const String leaderboard = 'leaderboard'; 7 | static const String settings = 'settings'; 8 | static const String update = '/update'; 9 | static const String unavailable = '/unavailable'; 10 | static const String suspended = '/suspended'; 11 | } 12 | -------------------------------------------------------------------------------- /web_flavor_setup.sh: -------------------------------------------------------------------------------- 1 | if [ "$#" -ne 1 ]; then 2 | echo "Expected flavor: sh $0 " 3 | exit 1 4 | fi 5 | 6 | flavor=$1 7 | 8 | cp "./web/$flavor/favicon.png" "./web/favicon.png" 9 | echo "Successfully copied favicon" 10 | cp "./web/$flavor/Icon-192.png" "./web/icons/Icon-192.png" 11 | cp "./web/$flavor/Icon-512.png" "./web/icons/Icon-512.png" 12 | echo "Successfully copied icons" 13 | cp "./web/$flavor/manifest.json" "./web/manifest.json" 14 | echo "Successfully copied manifest.json" 15 | -------------------------------------------------------------------------------- /lib/src/app/auth/presentation/provider/auth_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'package:skribla/src/app/auth/data/models/user_model.dart'; 3 | 4 | part 'generated/auth_state.freezed.dart'; 5 | 6 | enum AuthStatus { idle, signingIn, deletingAccount } 7 | 8 | @freezed 9 | class AuthState with _$AuthState { 10 | const factory AuthState({ 11 | @Default(AuthStatus.idle) AuthStatus status, 12 | @Default(null) UserModel? user, 13 | }) = _AuthState; 14 | } 15 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "background.png", 5 | "idiom" : "universal" 6 | }, 7 | { 8 | "appearances" : [ 9 | { 10 | "appearance" : "luminosity", 11 | "value" : "dark" 12 | } 13 | ], 14 | "filename" : "darkbackground.png", 15 | "idiom" : "universal" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /lib/src/core/resource/firebase_paths.dart: -------------------------------------------------------------------------------- 1 | import 'package:skribla/src/core/util/enums.dart'; 2 | 3 | abstract class FirebasePaths { 4 | static const String users = 'users'; 5 | static const String games = 'games'; 6 | static const String words = 'words'; 7 | static const String reports = 'reports'; 8 | static String exhibits(String id) => 'games/$id/exhibits'; 9 | static String messages(String id) => 'games/$id/messages'; 10 | static String leaderboard(LeaderboardType type) => 'leaderboard/${type.name}/users'; 11 | } 12 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "LaunchImage.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "LaunchImage@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "LaunchImage@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/src/core/util/types.dart: -------------------------------------------------------------------------------- 1 | import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; 2 | import 'package:skribla/src/app/game/data/models/game_model.dart'; 3 | import 'package:skribla/src/app/leaderboard/data/models/leaderboard_model.dart'; 4 | 5 | typedef CachedData = ({T data, DateTime expiry}); 6 | typedef HistoryController = PagingController; 7 | typedef LeaderboardController = PagingController; 8 | typedef LeaderboardPosition = ({int position, LeaderboardModel model}); 9 | -------------------------------------------------------------------------------- /lib/src/core/platform/web.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: avoid_web_libraries_in_flutter 2 | 3 | import 'dart:html' as html; 4 | import 'dart:typed_data'; 5 | 6 | Future shareImage(ByteData byteData, String fileName) async { 7 | final url = html.Url.createObjectUrlFromBlob( 8 | html.Blob([byteData.buffer.asUint8List()], 'image/png'), 9 | ); 10 | 11 | html.AnchorElement(href: url) 12 | ..setAttribute('download', fileName) 13 | ..click(); 14 | 15 | html.Url.revokeObjectUrl(url); 16 | } 17 | 18 | String get localeName => html.window.navigator.language; 19 | -------------------------------------------------------------------------------- /lib/src/app/settings/presentation/provider/settings_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | 4 | part 'generated/settings_state.freezed.dart'; 5 | 6 | enum SettingsStatus { idle } 7 | 8 | @freezed 9 | class SettingsState with _$SettingsState { 10 | const factory SettingsState({ 11 | @Default(SettingsStatus.idle) SettingsStatus status, 12 | @Default(ThemeMode.system) ThemeMode theme, 13 | @Default(true) bool hapticsOn, 14 | @Default('') String version, 15 | }) = _SettingsState; 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/core/util/constants.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | abstract class Constants { 4 | static const points = 10; 5 | static const email = 'skriblaapp@gmail.com'; 6 | static final colors = [...Colors.primaries]; 7 | static final allColors = [...Colors.primaries, ...Colors.accents]; 8 | static const privacy = 'https://skribla.com/privacy'; 9 | static const terms = 'https://skribla.com/terms'; 10 | static const website = 'https://skribla.com/'; 11 | static const playstore = 'https://skribla.com/'; 12 | static const appstore = 'https://skribla.com/'; 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Runner.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.applesignin 6 | 7 | Default 8 | 9 | com.apple.developer.associated-domains 10 | 11 | $(ASSOCIATED_DOMAINS) 12 | 13 | keychain-access-groups 14 | 15 | $(AppIdentifierPrefix)com.google.GIDSignIn 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /lib/src/app/game/presentation/provider/game_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | import 'package:skribla/src/app/game/data/models/game_model.dart'; 4 | 5 | part 'generated/game_state.freezed.dart'; 6 | 7 | enum GameStatus { idle, sendingMessage } 8 | 9 | @freezed 10 | class GameState with _$GameState { 11 | const factory GameState({ 12 | @Default(GameStatus.idle) GameStatus status, 13 | @Default(Colors.red) Color color, 14 | @Default(2) int stroke, 15 | @Default(null) GameModel? game, 16 | }) = _GameState; 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/app/game/data/models/report_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'generated/report_model.g.dart'; 4 | 5 | @JsonSerializable() 6 | class ReportModel { 7 | const ReportModel( 8 | this.uid, 9 | this.gameId, 10 | this.reason, 11 | this.createdAt, 12 | ); 13 | 14 | factory ReportModel.fromJson(Map json) => _$ReportModelFromJson(json); 15 | 16 | Map toJson() => _$ReportModelToJson(this); 17 | 18 | final String uid; 19 | final String gameId; 20 | final String reason; 21 | final DateTime createdAt; 22 | } 23 | -------------------------------------------------------------------------------- /lib/src/app/leaderboard/data/models/leaderboard_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'generated/leaderboard_model.freezed.dart'; 4 | part 'generated/leaderboard_model.g.dart'; 5 | 6 | @freezed 7 | class LeaderboardModel with _$LeaderboardModel { 8 | const factory LeaderboardModel({ 9 | required String uid, 10 | required DateTime updatedAt, 11 | required DateTime createdAt, 12 | @Default('') String name, 13 | @Default(0) int points, 14 | }) = _LeaderboardModel; 15 | 16 | factory LeaderboardModel.fromJson(Map json) => _$LeaderboardModelFromJson(json); 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/app/leaderboard/presentation/provider/leaderboard_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'package:skribla/src/app/leaderboard/data/models/leaderboard_model.dart'; 3 | import 'package:skribla/src/core/util/enums.dart'; 4 | 5 | part 'generated/leaderboard_state.freezed.dart'; 6 | 7 | @freezed 8 | class LeaderboardState with _$LeaderboardState { 9 | const factory LeaderboardState({ 10 | @Default(LeaderboardStatus.idle) LeaderboardStatus status, 11 | @Default(LeaderboardType.monthly) LeaderboardType type, 12 | @Default([]) List topThree, 13 | }) = _LeaderboardState; 14 | } 15 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = Skribla 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.skribla.macos.prod 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2024 Skribla. All rights reserved. 15 | -------------------------------------------------------------------------------- /lib/src/core/util/device_info.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'generated/device_info.g.dart'; 4 | 5 | @JsonSerializable() 6 | class DeviceInfo { 7 | const DeviceInfo({ 8 | required this.platform, 9 | this.systemName, 10 | this.appVersion, 11 | this.systemVersion, 12 | this.buildNumber, 13 | }); 14 | factory DeviceInfo.fromJson(Map json) => _$DeviceInfoFromJson(json); 15 | 16 | Map toJson() => _$DeviceInfoToJson(this); 17 | 18 | final String platform; 19 | final String? systemName; 20 | final String? systemVersion; 21 | final String? appVersion; 22 | final String? buildNumber; 23 | } 24 | -------------------------------------------------------------------------------- /lib/src/core/util/result.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'package:skribla/src/core/util/enums.dart'; 3 | 4 | part 'generated/result.freezed.dart'; 5 | 6 | @freezed 7 | sealed class Result with _$Result { 8 | const factory Result.success(T value) = _SuccessResult; 9 | const factory Result.error(CustomError error) = _ErrorResult; 10 | } 11 | 12 | class CustomError implements Exception { 13 | const CustomError({ 14 | required this.message, 15 | this.reason = ErrorReason.unknown, 16 | }); 17 | final String message; 18 | final ErrorReason reason; 19 | 20 | @override 21 | String toString() => 'Message: $message Reason: ${reason.value}'; 22 | } 23 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.applesignin 6 | 7 | Default 8 | 9 | com.apple.developer.associated-domains 10 | 11 | $(ASSOCIATED_DOMAINS) 12 | 13 | com.apple.security.app-sandbox 14 | 15 | com.apple.security.network.client 16 | 17 | keychain-access-groups 18 | 19 | $(AppIdentifierPrefix)com.google.GIDSignIn 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | -------------------------------------------------------------------------------- /lib/src/core/widgets/gradient_text.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class GradientText extends StatelessWidget { 4 | const GradientText( 5 | this.text, { 6 | required this.gradient, 7 | super.key, 8 | this.style, 9 | }); 10 | 11 | final String text; 12 | final TextStyle? style; 13 | final Gradient gradient; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return ShaderMask( 18 | blendMode: BlendMode.srcIn, 19 | shaderCallback: (bounds) => gradient.createShader( 20 | Rect.fromLTWH(0, 0, bounds.width, bounds.height), 21 | ), 22 | child: Text( 23 | text, 24 | style: style, 25 | textAlign: TextAlign.center, 26 | ), 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/src/app/game/data/models/line_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/painting.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | import 'package:skribla/src/core/util/converters.dart'; 4 | 5 | part 'generated/line_model.g.dart'; 6 | 7 | @JsonSerializable( 8 | converters: [OffsetConverter(), SizeConverter(), ColorConverter()], 9 | ) 10 | class LineModel { 11 | const LineModel( 12 | this.path, 13 | this.size, 14 | this.color, 15 | this.stroke, 16 | ); 17 | 18 | factory LineModel.fromJson(Map json) => _$LineModelFromJson(json); 19 | 20 | Map toJson() => _$LineModelToJson(this); 21 | 22 | final List path; 23 | final Size size; 24 | final Color color; 25 | final int stroke; 26 | } 27 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | 12 | void fl_register_plugins(FlPluginRegistry* registry) { 13 | g_autoptr(FlPluginRegistrar) sentry_flutter_registrar = 14 | fl_plugin_registry_get_registrar_for_plugin(registry, "SentryFlutterPlugin"); 15 | sentry_flutter_plugin_register_with_registrar(sentry_flutter_registrar); 16 | g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = 17 | fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); 18 | url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); 19 | } 20 | -------------------------------------------------------------------------------- /macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.applesignin 6 | 7 | Default 8 | 9 | com.apple.developer.associated-domains 10 | 11 | $(ASSOCIATED_DOMAINS) 12 | 13 | com.apple.security.app-sandbox 14 | 15 | com.apple.security.cs.allow-jit 16 | 17 | com.apple.security.network.client 18 | 19 | com.apple.security.network.server 20 | 21 | keychain-access-groups 22 | 23 | $(AppIdentifierPrefix)com.google.GIDSignIn 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /web/.well-known/apple-app-site-association: -------------------------------------------------------------------------------- 1 | { 2 | "applinks": { 3 | "apps": [], 4 | "details": [ 5 | { 6 | "appIDs": [ 7 | "YYA8WP4LNJ.com.skribla.ios.prod" 8 | ], 9 | "components": [ 10 | { 11 | "/": "*", 12 | "comment": "Matches all url paths." 13 | } 14 | ] 15 | }, 16 | { 17 | "appIDs": [ 18 | "YYA8WP4LNJ.com.skribla.ios.dev" 19 | ], 20 | "components": [ 21 | { 22 | "/": "*", 23 | "comment": "Matches all url paths." 24 | } 25 | ] 26 | } 27 | ] 28 | } 29 | } -------------------------------------------------------------------------------- /lib/src/app/game/data/models/generated/report_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of '../report_model.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | ReportModel _$ReportModelFromJson(Map json) => ReportModel( 10 | json['uid'] as String, 11 | json['game_id'] as String, 12 | json['reason'] as String, 13 | DateTime.parse(json['created_at'] as String), 14 | ); 15 | 16 | Map _$ReportModelToJson(ReportModel instance) => { 17 | 'uid': instance.uid, 18 | 'game_id': instance.gameId, 19 | 'reason': instance.reason, 20 | 'created_at': instance.createdAt.toIso8601String(), 21 | }; 22 | -------------------------------------------------------------------------------- /lib/src/core/platform/others.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:typed_data'; 3 | 4 | import 'package:path_provider/path_provider.dart'; 5 | import 'package:share_plus/share_plus.dart'; 6 | 7 | Future shareImage(ByteData byteData, String fileName) async { 8 | final dir = Platform.isAndroid 9 | ? await getExternalStorageDirectory() 10 | : await getApplicationSupportDirectory(); 11 | 12 | if (dir == null) return; 13 | 14 | // delete previous screenshots 15 | final files = dir.listSync(); 16 | for (final file in files) { 17 | if (file.path.contains('screenshot')) { 18 | file.deleteSync(); 19 | } 20 | } 21 | 22 | final file = await File('${dir.path}/$fileName').create(); 23 | 24 | await file.writeAsBytes(byteData.buffer.asUint8List()); 25 | 26 | await Share.shareXFiles([XFile(file.path)]); 27 | } 28 | 29 | String get localeName => Platform.localeName; 30 | -------------------------------------------------------------------------------- /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 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | sentry_flutter 7 | url_launcher_linux 8 | ) 9 | 10 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 11 | ) 12 | 13 | set(PLUGIN_BUNDLED_LIBRARIES) 14 | 15 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 16 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 17 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 18 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 19 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 20 | endforeach(plugin) 21 | 22 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 23 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 24 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 25 | endforeach(ffi_plugin) 26 | -------------------------------------------------------------------------------- /web/.well-known/assetlinks.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "relation": [ 4 | "delegate_permission/common.handle_all_urls" 5 | ], 6 | "target": { 7 | "namespace": "android_app", 8 | "package_name": "com.skribla.android.prod", 9 | "sha256_cert_fingerprints": [ 10 | "3A:6F:B8:6D:A2:71:9D:21:29:C5:9D:D4:CC:06:3C:11:83:AD:4F:44:A8:98:F1:2E:1E:0C:F8:B3:49:34:A5:D0" 11 | ] 12 | } 13 | }, 14 | { 15 | "relation": [ 16 | "delegate_permission/common.handle_all_urls" 17 | ], 18 | "target": { 19 | "namespace": "android_app", 20 | "package_name": "com.skribla.android.dev", 21 | "sha256_cert_fingerprints": [ 22 | "64:00:16:E5:C8:40:9C:C6:09:6C:B4:44:CF:E1:6D:E8:D8:08:C6:0C:73:73:50:4C:66:51:D2:EF:09:35:87:95" 23 | ] 24 | } 25 | } 26 | ] -------------------------------------------------------------------------------- /lib/src/app/game/data/models/word_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'package:skribla/src/core/platform/others.dart' 3 | if (dart.library.html) 'package:skribla/src/core/platform/web.dart' as platform; 4 | 5 | part 'generated/word_model.freezed.dart'; 6 | part 'generated/word_model.g.dart'; 7 | 8 | @freezed 9 | class WordModel with _$WordModel { 10 | const factory WordModel({ 11 | required String id, 12 | required DateTime createdAt, 13 | required String text, 14 | @Default(0) int index, 15 | @Default({}) Map loc, 16 | @Default(true) bool available, 17 | }) = _WordModel; 18 | 19 | const WordModel._(); 20 | 21 | factory WordModel.fromJson(Map json) => _$WordModelFromJson(json); 22 | 23 | String get locText { 24 | final language = platform.localeName.split('_')[0]; 25 | return loc[language] ?? text; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/src/app/history/data/models/exhibit_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'package:skribla/src/app/game/data/models/line_model.dart'; 3 | import 'package:skribla/src/app/game/data/models/player_model.dart'; 4 | import 'package:skribla/src/app/game/data/models/word_model.dart'; 5 | 6 | part 'generated/exhibit_model.g.dart'; 7 | 8 | @JsonSerializable() 9 | class ExhibitModel { 10 | const ExhibitModel({ 11 | required this.id, 12 | required this.player, 13 | required this.word, 14 | required this.art, 15 | required this.createdAt, 16 | }); 17 | 18 | factory ExhibitModel.fromJson(Map json) => _$ExhibitModelFromJson(json); 19 | 20 | Map toJson() => _$ExhibitModelToJson(this); 21 | 22 | final String id; 23 | final PlayerModel player; 24 | final WordModel word; 25 | final List art; 26 | final DateTime createdAt; 27 | } 28 | -------------------------------------------------------------------------------- /lib/src/core/observers/provider_watch.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 2 | import 'package:skribla/src/core/service/logger.dart'; 3 | 4 | const _logger = Logger('Provider Watch'); 5 | 6 | class ProviderWatch extends ProviderObserver { 7 | @override 8 | void didAddProvider( 9 | ProviderBase provider, 10 | Object? value, 11 | ProviderContainer container, 12 | ) { 13 | _logger.watch('$provider initialized'); 14 | } 15 | 16 | @override 17 | void didDisposeProvider( 18 | ProviderBase provider, 19 | ProviderContainer container, 20 | ) { 21 | _logger.watch('$provider disposed'); 22 | } 23 | 24 | @override 25 | void providerDidFail( 26 | ProviderBase provider, 27 | Object error, 28 | StackTrace stackTrace, 29 | ProviderContainer container, 30 | ) { 31 | _logger.watch('$provider threw $error at $stackTrace'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /shorebird.yaml: -------------------------------------------------------------------------------- 1 | # This file is used to configure the Shorebird updater used by your app. 2 | # Learn more at https://docs.shorebird.dev 3 | # This file does not contain any sensitive information and should be checked into version control. 4 | 5 | # Your app_id is the unique identifier assigned to your app. 6 | # It is used to identify your app when requesting patches from Shorebird's servers. 7 | # It is not a secret and can be shared publicly. 8 | app_id: 682c13ac-59b6-407a-97f4-6617de7fcb0f 9 | flavors: 10 | dev: 682c13ac-59b6-407a-97f4-6617de7fcb0f 11 | prod: 857087de-f061-4946-81cb-ebe7b8e58226 12 | 13 | # auto_update controls if Shorebird should automatically update in the background on launch. 14 | # If auto_update: false, you will need to use package:shorebird_code_push to trigger updates. 15 | # https://pub.dev/packages/shorebird_code_push 16 | # Uncomment the following line to disable automatic updates. 17 | # auto_update: false 18 | -------------------------------------------------------------------------------- /lib/src/app/settings/presentation/widgets/custom_list_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:skribla/src/core/util/config.dart'; 3 | import 'package:skribla/src/core/util/extension.dart'; 4 | 5 | class CustomListTile extends StatelessWidget { 6 | const CustomListTile({ 7 | required this.title, 8 | required this.icon, 9 | this.onTap, 10 | this.trailing, 11 | super.key, 12 | }); 13 | 14 | final String title; 15 | final IconData icon; 16 | final VoidCallback? onTap; 17 | final Widget? trailing; 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return ListTile( 22 | shape: RoundedRectangleBorder(borderRadius: Config.radius8), 23 | tileColor: context.theme.inputDecorationTheme.fillColor, 24 | leading: Icon(icon), 25 | title: Text(title, style: context.textTheme.bodyMedium), 26 | onTap: onTap, 27 | trailing: trailing, 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | cloud_firestore 7 | firebase_auth 8 | firebase_core 9 | sentry_flutter 10 | share_plus 11 | url_launcher_windows 12 | ) 13 | 14 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 15 | ) 16 | 17 | set(PLUGIN_BUNDLED_LIBRARIES) 18 | 19 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 20 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 21 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 22 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 23 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 24 | endforeach(plugin) 25 | 26 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 27 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 28 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 29 | endforeach(ffi_plugin) 30 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | }() 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 21 | id "com.android.application" version '8.2.1' apply false 22 | // START: FlutterFire Configuration 23 | id "com.google.gms.google-services" version "4.3.15" apply false 24 | // END: FlutterFire Configuration 25 | id "org.jetbrains.kotlin.android" version "1.9.10" apply false 26 | } 27 | 28 | include ":app" 29 | -------------------------------------------------------------------------------- /lib/src/app/game/presentation/provider/timer_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'package:skribla/src/core/util/enums.dart'; 3 | 4 | part 'generated/timer_state.freezed.dart'; 5 | 6 | @freezed 7 | class TimerState with _$TimerState { 8 | const factory TimerState({ 9 | @Default(TimerType.idle) TimerType timerType, 10 | @Default(Duration.zero) Duration coolDuration, 11 | @Default(Duration.zero) Duration skipDuration, 12 | @Default(Duration.zero) Duration turnDuration, 13 | @Default(Duration.zero) Duration completeDuration, 14 | }) = _TimerState; 15 | 16 | const TimerState._(); 17 | 18 | bool get showTimer => timerType != TimerType.idle; 19 | Duration get timerDuration => switch (timerType) { 20 | TimerType.cool => coolDuration, 21 | TimerType.skip => skipDuration, 22 | TimerType.turn => turnDuration, 23 | TimerType.complete => completeDuration, 24 | TimerType.idle => Duration.zero, 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /lib/src/app/game/data/models/player_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'package:skribla/src/app/game/data/models/word_model.dart'; 3 | 4 | part 'generated/player_model.freezed.dart'; 5 | part 'generated/player_model.g.dart'; 6 | 7 | @freezed 8 | class PlayerModel with _$PlayerModel { 9 | const factory PlayerModel({ 10 | required String uid, 11 | required String name, 12 | required DateTime createdAt, 13 | @Default(0) int points, 14 | @Default([]) List words, 15 | @Default([]) List blockedUsers, 16 | }) = _PlayerModel; 17 | 18 | const PlayerModel._(); 19 | factory PlayerModel.fromJson(Map json) => _$PlayerModelFromJson(json); 20 | 21 | WordModel? get nextWord => words.where((word) => word.available).firstOrNull; 22 | 23 | @override 24 | // ignore: avoid_equals_and_hash_code_on_mutable_classes, hash_and_equals 25 | bool operator ==(Object other) => other is PlayerModel && uid == other.uid; 26 | } 27 | -------------------------------------------------------------------------------- /lib/src/core/util/generated/device_info.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of '../device_info.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | DeviceInfo _$DeviceInfoFromJson(Map json) => DeviceInfo( 10 | platform: json['platform'] as String, 11 | systemName: json['system_name'] as String?, 12 | appVersion: json['app_version'] as String?, 13 | systemVersion: json['system_version'] as String?, 14 | buildNumber: json['build_number'] as String?, 15 | ); 16 | 17 | Map _$DeviceInfoToJson(DeviceInfo instance) => { 18 | 'platform': instance.platform, 19 | 'system_name': instance.systemName, 20 | 'system_version': instance.systemVersion, 21 | 'app_version': instance.appVersion, 22 | 'build_number': instance.buildNumber, 23 | }; 24 | -------------------------------------------------------------------------------- /lib/src/app/history/presentation/widgets/light_painter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class LightPainter extends CustomPainter { 4 | @override 5 | void paint(Canvas canvas, Size size) { 6 | final paint = Paint() 7 | ..shader = const LinearGradient( 8 | begin: Alignment.topCenter, 9 | end: Alignment.bottomCenter, 10 | stops: [0.1, 0.6, 1.0], 11 | colors: [Colors.white38, Colors.white12, Colors.transparent], 12 | ).createShader( 13 | Rect.fromPoints( 14 | Offset(size.width / 2, 0), 15 | Offset(size.width / 2, size.height), 16 | ), 17 | ); 18 | 19 | final path = Path() 20 | ..moveTo(size.width / 2 - 10, 20) 21 | ..lineTo(size.width / 2 - 180, size.height) 22 | ..lineTo(size.width / 2 + 180, size.height) 23 | ..lineTo(size.width / 2 + 10, 20) 24 | ..close(); 25 | 26 | canvas.drawPath(path, paint); 27 | } 28 | 29 | @override 30 | bool shouldRepaint(CustomPainter oldDelegate) => false; 31 | } 32 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Skribla", 3 | "short_name": "Skribla", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "Draw, guess, and have fun.", 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 | "src": "icons/Icon-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /web/prod/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Skribla", 3 | "short_name": "Skribla", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "Draw, guess, and have fun.", 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 | "src": "icons/Icon-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /web/dev/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Skribla Dev", 3 | "short_name": "Skribla Dev", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "Draw, guess, and have fun.", 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 | "src": "icons/Icon-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /lib/src/app/home/presentation/widgets/start_action.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:skribla/src/core/util/config.dart'; 3 | import 'package:skribla/src/core/util/extension.dart'; 4 | 5 | class StartAction extends StatelessWidget { 6 | const StartAction({ 7 | required this.icon, 8 | required this.text, 9 | this.onTap, 10 | super.key, 11 | }); 12 | 13 | final IconData icon; 14 | final String text; 15 | final VoidCallback? onTap; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return GestureDetector( 20 | onTap: onTap, 21 | child: Column( 22 | children: [ 23 | CircleAvatar( 24 | radius: 30, 25 | backgroundColor: context.theme.inputDecorationTheme.fillColor, 26 | child: Icon( 27 | icon, 28 | size: 24, 29 | ), 30 | ), 31 | Config.vBox12, 32 | Text( 33 | text, 34 | textAlign: TextAlign.center, 35 | ), 36 | ], 37 | ), 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/src/app/game/data/models/generated/line_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of '../line_model.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | LineModel _$LineModelFromJson(Map json) => LineModel( 10 | (json['path'] as List) 11 | .map((e) => const OffsetConverter().fromJson(e as Map)) 12 | .toList(), 13 | const SizeConverter().fromJson(json['size'] as Map), 14 | const ColorConverter().fromJson(json['color'] as String), 15 | (json['stroke'] as num).toInt(), 16 | ); 17 | 18 | Map _$LineModelToJson(LineModel instance) => { 19 | 'path': instance.path.map(const OffsetConverter().toJson).toList(), 20 | 'size': const SizeConverter().toJson(instance.size), 21 | 'color': const ColorConverter().toJson(instance.color), 22 | 'stroke': instance.stroke, 23 | }; 24 | -------------------------------------------------------------------------------- /bin/scripts/check_duplicates.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: avoid_print, avoid_dynamic_calls 2 | 3 | import 'dart:convert'; 4 | import 'dart:io'; 5 | 6 | Future main() async { 7 | final file = File('bin/resource/words.json'); 8 | final duplicates = await checkDuplicates(file); 9 | if (duplicates.isNotEmpty) { 10 | print('Duplicates entries found:'); 11 | for (final entry in duplicates) { 12 | print('- $entry'); 13 | } 14 | } else { 15 | print('No duplicates found'); 16 | } 17 | } 18 | 19 | Future> checkDuplicates(File file) async { 20 | final jsonString = await file.readAsString(); 21 | final data = json.decode(jsonString) as List; 22 | 23 | final textSet = {}; 24 | final duplicates = []; 25 | 26 | print('Searching for duplicates in ${file.path} with ${data.length} entries'); 27 | 28 | for (final item in data) { 29 | final text = (item['text'] as String).toLowerCase(); 30 | if (textSet.contains(text)) { 31 | duplicates.add(text); 32 | } else { 33 | textSet.add(text); 34 | } 35 | } 36 | 37 | return duplicates; 38 | } 39 | -------------------------------------------------------------------------------- /lib/src/app/leaderboard/data/models/generated/leaderboard_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of '../leaderboard_model.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$LeaderboardModelImpl _$$LeaderboardModelImplFromJson(Map json) => 10 | _$LeaderboardModelImpl( 11 | uid: json['uid'] as String, 12 | updatedAt: DateTime.parse(json['updated_at'] as String), 13 | createdAt: DateTime.parse(json['created_at'] as String), 14 | name: json['name'] as String? ?? '', 15 | points: (json['points'] as num?)?.toInt() ?? 0, 16 | ); 17 | 18 | Map _$$LeaderboardModelImplToJson(_$LeaderboardModelImpl instance) => 19 | { 20 | 'uid': instance.uid, 21 | 'updated_at': instance.updatedAt.toIso8601String(), 22 | 'created_at': instance.createdAt.toIso8601String(), 23 | 'name': instance.name, 24 | 'points': instance.points, 25 | }; 26 | -------------------------------------------------------------------------------- /lib/src/core/util/enums.dart: -------------------------------------------------------------------------------- 1 | enum UserStatus { suspended, anonymous, verified } 2 | 3 | enum AuthOptions { apple, google } 4 | 5 | enum GameStatus { open, private, closed, complete } 6 | 7 | enum TimerType { cool, skip, turn, complete, idle } 8 | 9 | enum LeaderboardType { monthly, alltime } 10 | 11 | enum LeaderboardStatus { idle, gettingUserPosition } 12 | 13 | enum ButtonType { filled, outlined, elevated, text } 14 | 15 | enum PageType { grid, list } 16 | 17 | enum SafetyOption { report, block, unknown } 18 | 19 | enum ErrorReason { 20 | unknown('unknown'), 21 | noPoints('No points on the board'); 22 | 23 | const ErrorReason(this.value); 24 | final String value; 25 | } 26 | 27 | enum Event { 28 | appOpen('app_open'), 29 | playGame('play_game'), 30 | createGame('create_game'), 31 | joinGame('join_game'), 32 | copyLink('copy_link'), 33 | viewLeaderboard('view_leaderboard'), 34 | viewHistory('view_history'), 35 | viewSettings('view_settings'), 36 | shareArt('share_art'), 37 | gameEnd('game_end'), 38 | signUp('sign_up'); 39 | 40 | const Event(this.value); 41 | final String value; 42 | } 43 | -------------------------------------------------------------------------------- /lib/src/app/home/presentation/widgets/logo_text.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:skribla/src/core/util/extension.dart'; 3 | import 'package:skribla/src/core/widgets/gradient_text.dart'; 4 | 5 | class LogoText extends StatelessWidget { 6 | const LogoText({super.key}); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Column( 11 | crossAxisAlignment: CrossAxisAlignment.stretch, 12 | mainAxisSize: MainAxisSize.min, 13 | children: [ 14 | GradientText( 15 | 'Skribla', 16 | style: context.textTheme.displayLarge, 17 | gradient: LinearGradient( 18 | stops: const [0.2, 0.5, 0.8], 19 | colors: [ 20 | context.colorScheme.primaryContainer, 21 | context.colorScheme.primary, 22 | context.colorScheme.primaryContainer, 23 | ], 24 | ), 25 | ), 26 | Text( 27 | context.loc.logoSubtitle, 28 | textAlign: TextAlign.center, 29 | style: context.textTheme.bodyLarge, 30 | ), 31 | ], 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/src/app/history/data/models/generated/exhibit_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of '../exhibit_model.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | ExhibitModel _$ExhibitModelFromJson(Map json) => ExhibitModel( 10 | id: json['id'] as String, 11 | player: PlayerModel.fromJson(json['player'] as Map), 12 | word: WordModel.fromJson(json['word'] as Map), 13 | art: (json['art'] as List) 14 | .map((e) => LineModel.fromJson(e as Map)) 15 | .toList(), 16 | createdAt: DateTime.parse(json['created_at'] as String), 17 | ); 18 | 19 | Map _$ExhibitModelToJson(ExhibitModel instance) => { 20 | 'id': instance.id, 21 | 'player': instance.player.toJson(), 22 | 'word': instance.word.toJson(), 23 | 'art': instance.art.map((e) => e.toJson()).toList(), 24 | 'created_at': instance.createdAt.toIso8601String(), 25 | }; 26 | -------------------------------------------------------------------------------- /lib/src/app/game/data/models/generated/word_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of '../word_model.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$WordModelImpl _$$WordModelImplFromJson(Map json) => _$WordModelImpl( 10 | id: json['id'] as String, 11 | createdAt: DateTime.parse(json['created_at'] as String), 12 | text: json['text'] as String, 13 | index: (json['index'] as num?)?.toInt() ?? 0, 14 | loc: (json['loc'] as Map?)?.map( 15 | (k, e) => MapEntry(k, e as String), 16 | ) ?? 17 | const {}, 18 | available: json['available'] as bool? ?? true, 19 | ); 20 | 21 | Map _$$WordModelImplToJson(_$WordModelImpl instance) => { 22 | 'id': instance.id, 23 | 'created_at': instance.createdAt.toIso8601String(), 24 | 'text': instance.text, 25 | 'index': instance.index, 26 | 'loc': instance.loc, 27 | 'available': instance.available, 28 | }; 29 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility in the flutter_test package. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:skribla/src/app/main_app.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(const MainApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /lib/src/core/widgets/shimmer_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shimmer/shimmer.dart'; 3 | import 'package:skribla/src/core/util/config.dart'; 4 | import 'package:skribla/src/core/util/extension.dart'; 5 | 6 | class ShimmerWidget extends StatelessWidget { 7 | const ShimmerWidget({ 8 | super.key, 9 | this.height, 10 | this.width, 11 | this.borderRadius, 12 | this.child, 13 | }); 14 | final double? height; 15 | final double? width; 16 | final BorderRadiusGeometry? borderRadius; 17 | final Widget? child; 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return ClipRRect( 22 | borderRadius: borderRadius ?? Config.radius8, 23 | child: Shimmer.fromColors( 24 | highlightColor: context.colorScheme.surface, 25 | baseColor: 26 | context.theme.inputDecorationTheme.fillColor ?? context.colorScheme.surfaceContainer, 27 | child: child ?? 28 | Container( 29 | height: height, 30 | width: width, 31 | decoration: BoxDecoration( 32 | color: context.colorScheme.surface, 33 | ), 34 | ), 35 | ), 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/src/app/settings/presentation/provider/loc_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_gen/gen_l10n/app_localizations.dart'; 3 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 4 | 5 | /// observer used to notify the caller when the locale changes 6 | class _LocaleObserver extends WidgetsBindingObserver { 7 | _LocaleObserver(this._didChangeLocales); 8 | final void Function(List? locales) _didChangeLocales; 9 | @override 10 | void didChangeLocales(List? locales) { 11 | _didChangeLocales(locales); 12 | } 13 | } 14 | 15 | final locProvider = Provider((ref) { 16 | final binding = WidgetsBinding.instance; 17 | // 1. initialize from the initial locale 18 | ref.state = lookupAppLocalizations(binding.platformDispatcher.locale); 19 | // 2. create an observer to update the state 20 | final observer = _LocaleObserver((locales) { 21 | ref.state = lookupAppLocalizations(binding.platformDispatcher.locale); 22 | }); 23 | // 3. register the observer and dispose it when no longer needed 24 | binding.addObserver(observer); 25 | ref.onDispose(() => binding.removeObserver(observer)); 26 | // 4. return the state 27 | return ref.state; 28 | }); 29 | -------------------------------------------------------------------------------- /ios/fastlane/report.xml: -------------------------------------------------------------------------------- 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 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /android/fastlane/report.xml: -------------------------------------------------------------------------------- 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 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | 8 | { 9 | "name": "Skribla (prod)", 10 | "request": "launch", 11 | "type": "dart", 12 | "program": "lib/main_prod.dart", 13 | "args": [ 14 | "--flavor", 15 | "prod", 16 | "--target", 17 | "lib/main_prod.dart", 18 | "--dart-define-from-file", 19 | "/Users/ifeanyionuoha/skribla/prod_creds.json" 20 | ] 21 | }, 22 | { 23 | "name": "Skribla (dev)", 24 | "request": "launch", 25 | "type": "dart", 26 | "program": "lib/main_dev.dart", 27 | "args": [ 28 | "--flavor", 29 | "dev", 30 | "--target", 31 | "lib/main_dev.dart", 32 | "--dart-define-from-file", 33 | "/Users/ifeanyionuoha/skribla/dev_creds.json" 34 | ] 35 | } 36 | ] 37 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | .fvm/flutter_sdk 34 | .firebase 35 | firebase.json 36 | /android/build/ 37 | /android/app/src/dev/google-services.json 38 | /android/app/src/prod/google-services.json 39 | /ios/build/ 40 | /ios/Runner/dev/GoogleService-Info.plist 41 | /ios/Runner/prod/GoogleService-Info.plist 42 | /macos/Runner/dev/GoogleService-Info.plist 43 | /macos/Runner/prod/GoogleService-Info.plist 44 | /lib/firebase_options** 45 | 46 | # Symbolication related 47 | app.*.symbols 48 | 49 | # Obfuscation related 50 | app.*.map.json 51 | 52 | # Android Studio will place build artifacts here 53 | /android/app/debug 54 | /android/app/profile 55 | /android/app/release 56 | -------------------------------------------------------------------------------- /ios/fastlane/README.md: -------------------------------------------------------------------------------- 1 | fastlane documentation 2 | ---- 3 | 4 | # Installation 5 | 6 | Make sure you have the latest version of the Xcode command line tools installed: 7 | 8 | ```sh 9 | xcode-select --install 10 | ``` 11 | 12 | For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane) 13 | 14 | # Available Actions 15 | 16 | ## iOS 17 | 18 | ### ios set_full_version 19 | 20 | ```sh 21 | [bundle exec] fastlane ios set_full_version 22 | ``` 23 | 24 | Set Info.plist Version and Build Number 25 | 26 | ### ios dev 27 | 28 | ```sh 29 | [bundle exec] fastlane ios dev 30 | ``` 31 | 32 | Deploy a dev build to Firebase App Distribution 33 | 34 | ### ios prod 35 | 36 | ```sh 37 | [bundle exec] fastlane ios prod 38 | ``` 39 | 40 | Deploy a prod build to TestFlight 41 | 42 | ### ios patch 43 | 44 | ```sh 45 | [bundle exec] fastlane ios patch 46 | ``` 47 | 48 | Patch latest prod build on App Store 49 | 50 | ---- 51 | 52 | This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run. 53 | 54 | More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools). 55 | 56 | The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools). 57 | -------------------------------------------------------------------------------- /lib/src/core/screens/unavailable_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:skribla/src/core/resource/app_icons.dart'; 3 | import 'package:skribla/src/core/util/config.dart'; 4 | import 'package:skribla/src/core/util/extension.dart'; 5 | 6 | class UnavailableScreen extends StatelessWidget { 7 | const UnavailableScreen({super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Scaffold( 12 | body: Padding( 13 | padding: Config.all(15), 14 | child: Column( 15 | mainAxisAlignment: MainAxisAlignment.center, 16 | crossAxisAlignment: CrossAxisAlignment.stretch, 17 | children: [ 18 | Icon( 19 | AppIcons.cloudSlash, 20 | size: Config.height * 0.2, 21 | color: context.colorScheme.primary, 22 | ), 23 | Text( 24 | context.loc.unavailableTitle, 25 | textAlign: TextAlign.center, 26 | style: context.textTheme.titleSmall, 27 | ), 28 | Config.vBox12, 29 | Text( 30 | context.loc.unavailableSubtitle, 31 | textAlign: TextAlign.center, 32 | ), 33 | ], 34 | ), 35 | ), 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | void RegisterPlugins(flutter::PluginRegistry* registry) { 17 | CloudFirestorePluginCApiRegisterWithRegistrar( 18 | registry->GetRegistrarForPlugin("CloudFirestorePluginCApi")); 19 | FirebaseAuthPluginCApiRegisterWithRegistrar( 20 | registry->GetRegistrarForPlugin("FirebaseAuthPluginCApi")); 21 | FirebaseCorePluginCApiRegisterWithRegistrar( 22 | registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); 23 | SentryFlutterPluginRegisterWithRegistrar( 24 | registry->GetRegistrarForPlugin("SentryFlutterPlugin")); 25 | SharePlusWindowsPluginCApiRegisterWithRegistrar( 26 | registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); 27 | UrlLauncherWindowsRegisterWithRegistrar( 28 | registry->GetRegistrarForPlugin("UrlLauncherWindows")); 29 | } 30 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-v31/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 18 | 21 | 22 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night-v31/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 18 | 21 | 22 | -------------------------------------------------------------------------------- /lib/src/core/util/converters.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:json_annotation/json_annotation.dart'; 4 | 5 | class OffsetConverter implements JsonConverter> { 6 | const OffsetConverter(); 7 | 8 | @override 9 | Offset fromJson(Map json) => Offset( 10 | double.tryParse('${json['dx']}') ?? 0, 11 | double.tryParse('${json['dy']}') ?? 0, 12 | ); 13 | 14 | @override 15 | Map toJson(Offset offset) => { 16 | 'dx': offset.dx, 17 | 'dy': offset.dy, 18 | }; 19 | } 20 | 21 | class SizeConverter implements JsonConverter> { 22 | const SizeConverter(); 23 | 24 | @override 25 | Size fromJson(Map json) => Size( 26 | double.tryParse('${json['width']}') ?? 0, 27 | double.tryParse('${json['height']}') ?? 0, 28 | ); 29 | 30 | @override 31 | Map toJson(Size size) => { 32 | 'width': size.width, 33 | 'height': size.height, 34 | }; 35 | } 36 | 37 | class ColorConverter implements JsonConverter { 38 | const ColorConverter(); 39 | 40 | @override 41 | Color fromJson(String json) => Color(int.parse(json, radix: 16)); 42 | 43 | @override 44 | String toJson(Color color) => color.value.toRadixString(16); 45 | } 46 | -------------------------------------------------------------------------------- /android/fastlane/README.md: -------------------------------------------------------------------------------- 1 | fastlane documentation 2 | ---- 3 | 4 | # Installation 5 | 6 | Make sure you have the latest version of the Xcode command line tools installed: 7 | 8 | ```sh 9 | xcode-select --install 10 | ``` 11 | 12 | For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane) 13 | 14 | # Available Actions 15 | 16 | ## Android 17 | 18 | ### android set_full_version 19 | 20 | ```sh 21 | [bundle exec] fastlane android set_full_version 22 | ``` 23 | 24 | Set Gradle Version and Build Number 25 | 26 | ### android dev 27 | 28 | ```sh 29 | [bundle exec] fastlane android dev 30 | ``` 31 | 32 | Deploy a dev build to Firebase App Distribution 33 | 34 | ### android prod 35 | 36 | ```sh 37 | [bundle exec] fastlane android prod 38 | ``` 39 | 40 | Deploy a prod build to Google Play Internal Test 41 | 42 | ### android patch 43 | 44 | ```sh 45 | [bundle exec] fastlane android patch 46 | ``` 47 | 48 | Patch latest prod build on Google Play 49 | 50 | ---- 51 | 52 | This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run. 53 | 54 | More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools). 55 | 56 | The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools). 57 | -------------------------------------------------------------------------------- /lib/src/app/game/data/models/generated/player_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of '../player_model.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$PlayerModelImpl _$$PlayerModelImplFromJson(Map json) => _$PlayerModelImpl( 10 | uid: json['uid'] as String, 11 | name: json['name'] as String, 12 | createdAt: DateTime.parse(json['created_at'] as String), 13 | points: (json['points'] as num?)?.toInt() ?? 0, 14 | words: (json['words'] as List?) 15 | ?.map((e) => WordModel.fromJson(e as Map)) 16 | .toList() ?? 17 | const [], 18 | blockedUsers: 19 | (json['blocked_users'] as List?)?.map((e) => e as String).toList() ?? const [], 20 | ); 21 | 22 | Map _$$PlayerModelImplToJson(_$PlayerModelImpl instance) => { 23 | 'uid': instance.uid, 24 | 'name': instance.name, 25 | 'created_at': instance.createdAt.toIso8601String(), 26 | 'points': instance.points, 27 | 'words': instance.words.map((e) => e.toJson()).toList(), 28 | 'blocked_users': instance.blockedUsers, 29 | }; 30 | -------------------------------------------------------------------------------- /lib/src/app/game/data/models/message_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'package:skribla/src/core/platform/others.dart' 3 | if (dart.library.html) 'package:skribla/src/core/platform/web.dart' as platform; 4 | 5 | part 'generated/message_model.g.dart'; 6 | 7 | enum MessageType { 8 | text('text'), 9 | correctGuess('correct_guess'), 10 | wordReveal('word_reveal'); 11 | 12 | const MessageType(this.value); 13 | final String value; 14 | } 15 | 16 | @JsonSerializable() 17 | class MessageModel { 18 | const MessageModel({ 19 | required this.id, 20 | required this.uid, 21 | required this.text, 22 | required this.name, 23 | required this.messageType, 24 | required this.createdAt, 25 | this.loc = const {}, // only available for wordReveal 26 | }); 27 | 28 | factory MessageModel.fromJson(Map json) => _$MessageModelFromJson(json); 29 | 30 | Map toJson() => _$MessageModelToJson(this); 31 | 32 | final String id; 33 | final String uid; 34 | final String text; 35 | final String name; 36 | final MessageType messageType; 37 | final DateTime createdAt; 38 | final Map loc; 39 | 40 | String get locText { 41 | final language = platform.localeName.split('_')[0]; 42 | return loc[language] ?? text; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /lib/src/app/leaderboard/presentation/widgets/leaderboard_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:skribla/src/core/util/extension.dart'; 3 | import 'package:skribla/src/core/util/types.dart'; 4 | 5 | class LeaderboardItem extends StatelessWidget { 6 | const LeaderboardItem({ 7 | required this.data, 8 | this.name, 9 | this.avatarColor, 10 | super.key, 11 | }); 12 | 13 | final String? name; 14 | final LeaderboardPosition data; 15 | final Color? avatarColor; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return ListTile( 20 | contentPadding: EdgeInsets.zero, 21 | leading: CircleAvatar( 22 | backgroundColor: avatarColor ?? context.theme.inputDecorationTheme.fillColor, 23 | child: FittedBox( 24 | child: Padding( 25 | padding: const EdgeInsets.symmetric(horizontal: 4), 26 | child: Text( 27 | '${data.position}', 28 | style: context.textTheme.bodyLarge, 29 | ), 30 | ), 31 | ), 32 | ), 33 | title: Text(name ?? ''), 34 | subtitle: Text( 35 | context.loc.lastUpdatedAt(data.model.updatedAt.formatEDMHM), 36 | style: context.textTheme.bodySmall, 37 | ), 38 | trailing: Text( 39 | context.loc.npts(data.model.points), 40 | style: context.textTheme.bodySmall, 41 | ), 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "utils.h" 7 | 8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 9 | _In_ wchar_t *command_line, _In_ int show_command) { 10 | // Attach to console when present (e.g., 'flutter run') or create a 11 | // new console when running with a debugger. 12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 13 | CreateAndAttachConsole(); 14 | } 15 | 16 | // Initialize COM, so that it is available for use in the library and/or 17 | // plugins. 18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 19 | 20 | flutter::DartProject project(L"data"); 21 | 22 | std::vector command_line_arguments = 23 | GetCommandLineArguments(); 24 | 25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 26 | 27 | FlutterWindow window(project); 28 | Win32Window::Point origin(10, 10); 29 | Win32Window::Size size(1280, 720); 30 | if (!window.Create(L"skribla", origin, size)) { 31 | return EXIT_FAILURE; 32 | } 33 | window.SetQuitOnClose(true); 34 | 35 | ::MSG msg; 36 | while (::GetMessage(&msg, nullptr, 0, 0)) { 37 | ::TranslateMessage(&msg); 38 | ::DispatchMessage(&msg); 39 | } 40 | 41 | ::CoUninitialize(); 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /lib/src/app/game/data/models/generated/message_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of '../message_model.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | MessageModel _$MessageModelFromJson(Map json) => MessageModel( 10 | id: json['id'] as String, 11 | uid: json['uid'] as String, 12 | text: json['text'] as String, 13 | name: json['name'] as String, 14 | messageType: $enumDecode(_$MessageTypeEnumMap, json['message_type']), 15 | createdAt: DateTime.parse(json['created_at'] as String), 16 | loc: (json['loc'] as Map?)?.map( 17 | (k, e) => MapEntry(k, e as String), 18 | ) ?? 19 | const {}, 20 | ); 21 | 22 | Map _$MessageModelToJson(MessageModel instance) => { 23 | 'id': instance.id, 24 | 'uid': instance.uid, 25 | 'text': instance.text, 26 | 'name': instance.name, 27 | 'message_type': _$MessageTypeEnumMap[instance.messageType]!, 28 | 'created_at': instance.createdAt.toIso8601String(), 29 | 'loc': instance.loc, 30 | }; 31 | 32 | const _$MessageTypeEnumMap = { 33 | MessageType.text: 'text', 34 | MessageType.correctGuess: 'correctGuess', 35 | MessageType.wordReveal: 'wordReveal', 36 | }; 37 | -------------------------------------------------------------------------------- /lib/src/core/theme/app_theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flex_color_scheme/flex_color_scheme.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:skribla/src/core/theme/colors.dart'; 4 | import 'package:skribla/src/core/util/config.dart'; 5 | 6 | abstract class AppTheme { 7 | static ThemeData get lightTheme => FlexThemeData.light( 8 | colorScheme: flexSchemeLight, 9 | surfaceMode: FlexSurfaceMode.levelSurfacesLowScaffold, 10 | blendLevel: 7, 11 | subThemesData: const FlexSubThemesData( 12 | blendOnLevel: 10, 13 | blendOnColors: false, 14 | useTextTheme: true, 15 | useM2StyleDividerInM3: true, 16 | ), 17 | visualDensity: FlexColorScheme.comfortablePlatformDensity, 18 | useMaterial3: true, 19 | swapLegacyOnMaterial3: true, 20 | fontFamily: 'MoreSugarRegular', 21 | textTheme: Config.textTheme, 22 | ); 23 | 24 | static ThemeData get darkTheme => FlexThemeData.dark( 25 | colorScheme: flexSchemeDark, 26 | surfaceMode: FlexSurfaceMode.levelSurfacesLowScaffold, 27 | blendLevel: 13, 28 | subThemesData: const FlexSubThemesData( 29 | blendOnLevel: 20, 30 | useTextTheme: true, 31 | useM2StyleDividerInM3: true, 32 | ), 33 | visualDensity: FlexColorScheme.comfortablePlatformDensity, 34 | useMaterial3: true, 35 | swapLegacyOnMaterial3: true, 36 | fontFamily: 'MoreSugarRegular', 37 | textTheme: Config.textTheme, 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /lib/main_dev.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_core/firebase_core.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:flutter/material.dart' hide Router; 4 | import 'package:flutter/services.dart'; 5 | import 'package:flutter_native_splash/flutter_native_splash.dart'; 6 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 7 | import 'package:sentry_flutter/sentry_flutter.dart'; 8 | import 'package:skribla/env/env.dart'; 9 | import 'package:skribla/firebase_options_dev.dart'; 10 | import 'package:skribla/src/app/main_app.dart'; 11 | import 'package:skribla/src/core/observers/provider_watch.dart'; 12 | import 'package:url_strategy/url_strategy.dart'; 13 | 14 | void main() { 15 | SentryFlutter.init( 16 | (options) { 17 | options 18 | ..dsn = kReleaseMode ? Env.sentryDNS : '' 19 | ..environment = 'dev' 20 | ..tracesSampleRate = 1.0 21 | ..profilesSampleRate = 1.0; 22 | }, 23 | appRunner: () async { 24 | final binding = WidgetsFlutterBinding.ensureInitialized(); 25 | FlutterNativeSplash.preserve(widgetsBinding: binding); 26 | setPathUrlStrategy(); 27 | await SystemChrome.setPreferredOrientations([ 28 | DeviceOrientation.portraitUp, 29 | DeviceOrientation.portraitDown, 30 | ]); 31 | await Firebase.initializeApp( 32 | options: DefaultFirebaseOptions.currentPlatform, 33 | ); 34 | runApp( 35 | ProviderScope( 36 | observers: [ProviderWatch()], 37 | child: const MainApp(), 38 | ), 39 | ); 40 | }, 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /lib/main_prod.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_core/firebase_core.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:flutter/material.dart' hide Router; 4 | import 'package:flutter/services.dart'; 5 | import 'package:flutter_native_splash/flutter_native_splash.dart'; 6 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 7 | import 'package:sentry_flutter/sentry_flutter.dart'; 8 | import 'package:skribla/env/env.dart'; 9 | import 'package:skribla/firebase_options_prod.dart'; 10 | import 'package:skribla/src/app/main_app.dart'; 11 | import 'package:skribla/src/core/observers/provider_watch.dart'; 12 | import 'package:url_strategy/url_strategy.dart'; 13 | 14 | void main() { 15 | SentryFlutter.init( 16 | (options) { 17 | options 18 | ..dsn = kReleaseMode ? Env.sentryDNS : '' 19 | ..environment = 'prod' 20 | ..tracesSampleRate = 1.0 21 | ..profilesSampleRate = 1.0; 22 | }, 23 | appRunner: () async { 24 | final binding = WidgetsFlutterBinding.ensureInitialized(); 25 | FlutterNativeSplash.preserve(widgetsBinding: binding); 26 | setPathUrlStrategy(); 27 | await SystemChrome.setPreferredOrientations([ 28 | DeviceOrientation.portraitUp, 29 | DeviceOrientation.portraitDown, 30 | ]); 31 | await Firebase.initializeApp( 32 | options: DefaultFirebaseOptions.currentPlatform, 33 | ); 34 | runApp( 35 | ProviderScope( 36 | observers: [ProviderWatch()], 37 | child: const MainApp(), 38 | ), 39 | ); 40 | }, 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | platform :ios, '13.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 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | target 'RunnerTests' do 36 | inherit! :search_paths 37 | end 38 | end 39 | 40 | post_install do |installer| 41 | installer.pods_project.targets.each do |target| 42 | flutter_additional_ios_build_settings(target) 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/src/app/auth/data/models/generated/user_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of '../user_model.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$UserModelImpl _$$UserModelImplFromJson(Map json) => _$UserModelImpl( 10 | uid: json['uid'] as String, 11 | createdAt: DateTime.parse(json['created_at'] as String), 12 | name: json['name'] as String? ?? '', 13 | email: json['email'] as String? ?? '', 14 | reportCount: (json['report_count'] as num?)?.toInt() ?? 0, 15 | blockedUsers: 16 | (json['blocked_users'] as List?)?.map((e) => e as String).toList() ?? const [], 17 | lastWordIndex: (json['last_word_index'] as num?)?.toInt() ?? null, 18 | status: $enumDecodeNullable(_$UserStatusEnumMap, json['status']) ?? UserStatus.anonymous, 19 | ); 20 | 21 | Map _$$UserModelImplToJson(_$UserModelImpl instance) => { 22 | 'uid': instance.uid, 23 | 'created_at': instance.createdAt.toIso8601String(), 24 | 'name': instance.name, 25 | 'email': instance.email, 26 | 'report_count': instance.reportCount, 27 | 'blocked_users': instance.blockedUsers, 28 | 'last_word_index': instance.lastWordIndex, 29 | 'status': _$UserStatusEnumMap[instance.status]!, 30 | }; 31 | 32 | const _$UserStatusEnumMap = { 33 | UserStatus.suspended: 'suspended', 34 | UserStatus.anonymous: 'anonymous', 35 | UserStatus.verified: 'verified', 36 | }; 37 | -------------------------------------------------------------------------------- /lib/src/app/auth/data/models/user_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:firebase_auth/firebase_auth.dart'; 4 | import 'package:freezed_annotation/freezed_annotation.dart'; 5 | import 'package:skribla/src/app/game/data/models/player_model.dart'; 6 | import 'package:skribla/src/core/util/enums.dart'; 7 | 8 | part 'generated/user_model.freezed.dart'; 9 | part 'generated/user_model.g.dart'; 10 | 11 | @freezed 12 | class UserModel with _$UserModel { 13 | const factory UserModel({ 14 | required String uid, 15 | required DateTime createdAt, 16 | @Default('') String name, 17 | @Default('') String email, 18 | @Default(0) int reportCount, 19 | @Default([]) List blockedUsers, 20 | @Default(null) int? lastWordIndex, 21 | @Default(UserStatus.anonymous) UserStatus status, 22 | }) = _UserModel; 23 | 24 | const UserModel._(); 25 | 26 | factory UserModel.fromJson(Map json) => _$UserModelFromJson(json); 27 | 28 | factory UserModel.fromCredential(UserCredential credential) { 29 | final user = credential.user!; 30 | final random = List.generate(10, (index) => index) 31 | ..shuffle(Random(DateTime.now().millisecondsSinceEpoch)); 32 | return UserModel( 33 | uid: user.uid, 34 | email: user.email ?? '', 35 | name: user.displayName ?? 'anon_${random.take(4).join()}', 36 | status: (user.email ?? '').isEmpty ? UserStatus.anonymous : UserStatus.verified, 37 | createdAt: DateTime.now(), 38 | ); 39 | } 40 | 41 | PlayerModel toPlayer() { 42 | return PlayerModel( 43 | uid: uid, 44 | name: name, 45 | createdAt: createdAt, 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/src/app/game/presentation/widgets/star_confetti.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:confetti/confetti.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | class StarConfetti extends StatelessWidget { 7 | const StarConfetti({required this.confettiController, super.key}); 8 | 9 | final ConfettiController confettiController; 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return ConfettiWidget( 14 | confettiController: confettiController, 15 | numberOfParticles: 20, 16 | blastDirectionality: BlastDirectionality.explosive, 17 | colors: Colors.primaries, 18 | createParticlePath: drawStar, 19 | ); 20 | } 21 | } 22 | 23 | Path drawStar(Size size) { 24 | // Method to convert degree to radians 25 | double degToRad(double deg) => deg * (pi / 180.0); 26 | 27 | const numberOfPoints = 5; 28 | final halfWidth = size.width / 2; 29 | final externalRadius = halfWidth; 30 | final internalRadius = halfWidth / 2.5; 31 | final degreesPerStep = degToRad(360 / numberOfPoints); 32 | final halfDegreesPerStep = degreesPerStep / 2; 33 | final path = Path(); 34 | final fullAngle = degToRad(360); 35 | path.moveTo(size.width, halfWidth); 36 | 37 | for (var step = 0.0; step < fullAngle; step += degreesPerStep) { 38 | path 39 | ..lineTo( 40 | halfWidth + externalRadius * cos(step), 41 | halfWidth + externalRadius * sin(step), 42 | ) 43 | ..lineTo( 44 | halfWidth + internalRadius * cos(step + halfDegreesPerStep), 45 | halfWidth + internalRadius * sin(step + halfDegreesPerStep), 46 | ); 47 | } 48 | path.close(); 49 | return path; 50 | } 51 | -------------------------------------------------------------------------------- /lib/src/core/util/feature_flags.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:freezed_annotation/freezed_annotation.dart'; 5 | 6 | part 'generated/feature_flags.g.dart'; 7 | 8 | @JsonSerializable() 9 | class FeatureFlags { 10 | const FeatureFlags({ 11 | this.majorVersion = 0, 12 | this.minorVersion = 0, 13 | this.webDown = false, 14 | this.iosDown = false, 15 | this.androidDown = false, 16 | this.macDown = false, 17 | this.drawDelayMilliseconds = 100, 18 | this.coolDurationSeconds = 5, 19 | this.skipDurationSeconds = 7, 20 | this.turnDurationSeconds = 20, 21 | this.completeDurationSeconds = 3, 22 | this.totalWordsCount = 100, 23 | this.wordsPerGame = 3, 24 | }); 25 | factory FeatureFlags.fromJson(Map json) => _$FeatureFlagsFromJson(json); 26 | 27 | Map toJson() => _$FeatureFlagsToJson(this); 28 | 29 | final int majorVersion; 30 | final int minorVersion; 31 | final bool webDown; 32 | final bool iosDown; 33 | final bool androidDown; 34 | final bool macDown; 35 | final int drawDelayMilliseconds; 36 | final int coolDurationSeconds; 37 | final int skipDurationSeconds; 38 | final int turnDurationSeconds; 39 | final int completeDurationSeconds; 40 | final int totalWordsCount; 41 | final int wordsPerGame; 42 | 43 | bool get plaformDown { 44 | if (kIsWeb) { 45 | return webDown; 46 | } else if (Platform.isIOS) { 47 | return iosDown; 48 | } else if (Platform.isAndroid) { 49 | return androidDown; 50 | } else if (Platform.isMacOS) { 51 | return macDown; 52 | } 53 | return false; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.15' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def flutter_root 13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) 14 | unless File.exist?(generated_xcode_build_settings_path) 15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" 16 | end 17 | 18 | File.foreach(generated_xcode_build_settings_path) do |line| 19 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 20 | return matches[1].strip if matches 21 | end 22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" 23 | end 24 | 25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 26 | 27 | flutter_macos_podfile_setup 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | use_modular_headers! 32 | 33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 34 | target 'RunnerTests' do 35 | inherit! :search_paths 36 | end 37 | end 38 | 39 | post_install do |installer| 40 | installer.pods_project.targets.each do |target| 41 | flutter_additional_macos_build_settings(target) 42 | target.build_configurations.each do |config| 43 | config.build_settings['MACOSX_DEPLOYMENT_TARGET'] = '10.15' 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | LSApplicationCategoryType 6 | public.app-category.word-games 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIconFile 12 | 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | $(PRODUCT_NAME) 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | $(FLUTTER_BUILD_NAME) 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSHumanReadableCopyright 28 | $(PRODUCT_COPYRIGHT) 29 | NSMainNibFile 30 | MainMenu 31 | NSPrincipalClass 32 | NSApplication 33 | GIDClientID 34 | $(GID_CLIENT_ID) 35 | CFBundleURLTypes 36 | 37 | 38 | CFBundleTypeRole 39 | Editor 40 | CFBundleURLSchemes 41 | 42 | $(GID_REVERSED_CLIENT_ID) 43 | 44 | 45 | 46 | CFBundleDisplayName 47 | $(APP_DISPLAY_NAME) 48 | 49 | 50 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon-dev.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "1024-mac.png", 5 | "idiom" : "ios-marketing", 6 | "scale" : "1x", 7 | "size" : "1024x1024" 8 | }, 9 | { 10 | "filename" : "16-mac.png", 11 | "idiom" : "mac", 12 | "scale" : "1x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "32-mac.png", 17 | "idiom" : "mac", 18 | "scale" : "2x", 19 | "size" : "16x16" 20 | }, 21 | { 22 | "filename" : "32-mac.png", 23 | "idiom" : "mac", 24 | "scale" : "1x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "64-mac.png", 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "32x32" 32 | }, 33 | { 34 | "filename" : "128-mac.png", 35 | "idiom" : "mac", 36 | "scale" : "1x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "256-mac.png", 41 | "idiom" : "mac", 42 | "scale" : "2x", 43 | "size" : "128x128" 44 | }, 45 | { 46 | "filename" : "256-mac.png", 47 | "idiom" : "mac", 48 | "scale" : "1x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "512-mac.png", 53 | "idiom" : "mac", 54 | "scale" : "2x", 55 | "size" : "256x256" 56 | }, 57 | { 58 | "filename" : "512-mac.png", 59 | "idiom" : "mac", 60 | "scale" : "1x", 61 | "size" : "512x512" 62 | }, 63 | { 64 | "filename" : "1024-mac.png", 65 | "idiom" : "mac", 66 | "scale" : "2x", 67 | "size" : "512x512" 68 | } 69 | ], 70 | "info" : { 71 | "author" : "xcode", 72 | "version" : 1 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon-prod.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "1024-mac.png", 5 | "idiom" : "ios-marketing", 6 | "scale" : "1x", 7 | "size" : "1024x1024" 8 | }, 9 | { 10 | "filename" : "16-mac.png", 11 | "idiom" : "mac", 12 | "scale" : "1x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "32-mac.png", 17 | "idiom" : "mac", 18 | "scale" : "2x", 19 | "size" : "16x16" 20 | }, 21 | { 22 | "filename" : "32-mac.png", 23 | "idiom" : "mac", 24 | "scale" : "1x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "64-mac.png", 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "32x32" 32 | }, 33 | { 34 | "filename" : "128-mac.png", 35 | "idiom" : "mac", 36 | "scale" : "1x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "256-mac.png", 41 | "idiom" : "mac", 42 | "scale" : "2x", 43 | "size" : "128x128" 44 | }, 45 | { 46 | "filename" : "256-mac.png", 47 | "idiom" : "mac", 48 | "scale" : "1x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "512-mac.png", 53 | "idiom" : "mac", 54 | "scale" : "2x", 55 | "size" : "256x256" 56 | }, 57 | { 58 | "filename" : "512-mac.png", 59 | "idiom" : "mac", 60 | "scale" : "1x", 61 | "size" : "512x512" 62 | }, 63 | { 64 | "filename" : "1024-mac.png", 65 | "idiom" : "mac", 66 | "scale" : "2x", 67 | "size" : "512x512" 68 | } 69 | ], 70 | "info" : { 71 | "author" : "xcode", 72 | "version" : 1 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/src/app/history/presentation/widgets/exhibit_footer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:skribla/src/app/history/data/models/exhibit_model.dart'; 3 | import 'package:skribla/src/core/util/config.dart'; 4 | import 'package:skribla/src/core/util/extension.dart'; 5 | import 'package:skribla/src/core/widgets/app_button.dart'; 6 | 7 | class ExhibitFooter extends StatelessWidget { 8 | const ExhibitFooter({ 9 | required this.exhibit, 10 | required this.onShare, 11 | super.key, 12 | }); 13 | 14 | final ExhibitModel exhibit; 15 | final VoidCallback onShare; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return Column( 20 | crossAxisAlignment: CrossAxisAlignment.stretch, 21 | mainAxisSize: MainAxisSize.min, 22 | children: [ 23 | Config.vBox8, 24 | Padding( 25 | padding: Config.symmetric(h: 15), 26 | child: Text.rich( 27 | TextSpan( 28 | text: exhibit.word.locText, 29 | children: [ 30 | TextSpan( 31 | text: ' ${context.loc.by} ', 32 | style: TextStyle( 33 | color: context.colorScheme.secondary, 34 | fontStyle: FontStyle.italic, 35 | ), 36 | ), 37 | TextSpan(text: exhibit.player.name), 38 | ], 39 | ), 40 | style: context.textTheme.titleSmall, 41 | textAlign: TextAlign.center, 42 | ), 43 | ), 44 | Config.vBox12, 45 | AppButton( 46 | hPadding: 15, 47 | text: context.loc.shareBtnTxt, 48 | onPressed: onShare, 49 | ), 50 | Config.vBox30, 51 | ], 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/src/core/resource/app_icons.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:phosphor_flutter/phosphor_flutter.dart'; 4 | 5 | abstract class AppIcons { 6 | static IconData get minusCircle => PhosphorIconsBold.minusCircle; 7 | static IconData get plusCircle => PhosphorIconsBold.plusCircle; 8 | static IconData get eraser => PhosphorIconsBold.eraser; 9 | static IconData get highlighter => PhosphorIconsBold.highlighter; 10 | static IconData get clockCounterClockwise => PhosphorIconsBold.clockCounterClockwise; 11 | static IconData get check => PhosphorIconsBold.check; 12 | static IconData get copy => PhosphorIconsBold.copy; 13 | static IconData get paperPlaneRight => PhosphorIconsBold.paperPlaneRight; 14 | static IconData get x => PhosphorIconsBold.x; 15 | static IconData get trophy => PhosphorIconsBold.trophy; 16 | static IconData get gear => PhosphorIconsBold.gear; 17 | static IconData get appleLogo => PhosphorIconsBold.appleLogo; 18 | static IconData get googleLogo => PhosphorIconsBold.googleLogo; 19 | static IconData get signOut => PhosphorIconsBold.signOut; 20 | static IconData get trashSimple => PhosphorIconsBold.trashSimple; 21 | static IconData get star => PhosphorIconsBold.star; 22 | static IconData get envelopeSimple => PhosphorIconsBold.envelopeSimple; 23 | static IconData get shield => PhosphorIconsBold.shield; 24 | static IconData get listChecks => PhosphorIconsBold.listChecks; 25 | static IconData get arrowUpRight => PhosphorIconsBold.arrowUpRight; 26 | static IconData get vibrate => PhosphorIconsBold.vibrate; 27 | static IconData get cloudSlash => PhosphorIconsBold.cloudSlash; 28 | static IconData get downloadSimple => PhosphorIconsBold.downloadSimple; 29 | static IconData get warning => PhosphorIconsBold.warning; 30 | } 31 | -------------------------------------------------------------------------------- /lib/src/core/widgets/default_app_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:go_router/go_router.dart'; 3 | import 'package:skribla/src/core/resource/app_icons.dart'; 4 | import 'package:skribla/src/core/util/config.dart'; 5 | import 'package:skribla/src/core/util/extension.dart'; 6 | 7 | class DefaultAppBar extends StatelessWidget implements PreferredSizeWidget { 8 | const DefaultAppBar({ 9 | super.key, 10 | this.title, 11 | this.titleStyle, 12 | this.leading, 13 | this.actions = const [], 14 | this.automaticallyImplyLeading = true, 15 | }); 16 | 17 | final Widget? title; 18 | final TextStyle? titleStyle; 19 | final Widget? leading; 20 | final List actions; 21 | final bool automaticallyImplyLeading; 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return AppBar( 26 | automaticallyImplyLeading: automaticallyImplyLeading, 27 | backgroundColor: context.theme.scaffoldBackgroundColor, 28 | scrolledUnderElevation: 0, 29 | elevation: 0, 30 | centerTitle: true, 31 | leading: !automaticallyImplyLeading 32 | ? const SizedBox.shrink() 33 | : Padding( 34 | padding: const EdgeInsets.fromLTRB(20, 0, 0, 0), 35 | child: GestureDetector( 36 | onTap: context.pop, 37 | child: CircleAvatar( 38 | backgroundColor: context.theme.inputDecorationTheme.fillColor, 39 | child: Icon(AppIcons.x, size: 18), 40 | ), 41 | ), 42 | ), 43 | title: title, 44 | actions: [ 45 | ...actions, 46 | if (actions.isNotEmpty) Config.hBox16, 47 | ], 48 | ); 49 | } 50 | 51 | @override 52 | Size get preferredSize => const Size.fromHeight(kToolbarHeight); 53 | } 54 | -------------------------------------------------------------------------------- /lib/src/core/widgets/progress_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:skribla/src/core/util/config.dart'; 3 | import 'package:skribla/src/core/util/extension.dart'; 4 | 5 | class ProgressBar extends StatefulWidget { 6 | const ProgressBar({ 7 | required this.duration, 8 | super.key, 9 | this.height, 10 | }); 11 | 12 | final Duration duration; 13 | final double? height; 14 | 15 | @override 16 | State createState() => _ProgressBarState(); 17 | } 18 | 19 | class _ProgressBarState extends State { 20 | bool _animate = false; 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | WidgetsBinding.instance.addPostFrameCallback((_) async { 26 | setState(() { 27 | _animate = true; 28 | }); 29 | }); 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return LayoutBuilder( 35 | builder: (context, constraints) { 36 | return Padding( 37 | padding: const EdgeInsets.all(10), 38 | child: ClipRRect( 39 | borderRadius: Config.radius8, 40 | child: Stack( 41 | children: [ 42 | Container( 43 | height: Config.h(widget.height ?? 8), 44 | color: context.colorScheme.primaryContainer, 45 | ), 46 | AnimatedContainer( 47 | height: Config.h(widget.height ?? 8), 48 | duration: widget.duration, 49 | width: _animate ? 0 : constraints.maxWidth, 50 | child: ColoredBox( 51 | color: context.colorScheme.primary, 52 | ), 53 | ), 54 | ], 55 | ), 56 | ), 57 | ); 58 | }, 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import cloud_firestore 9 | import device_info_plus 10 | import firebase_auth 11 | import firebase_core 12 | import google_sign_in_ios 13 | import in_app_review 14 | import package_info_plus 15 | import path_provider_foundation 16 | import sentry_flutter 17 | import share_plus 18 | import shared_preferences_foundation 19 | import sign_in_with_apple 20 | import url_launcher_macos 21 | 22 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 23 | FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin")) 24 | DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) 25 | FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin")) 26 | FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) 27 | FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin")) 28 | InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin")) 29 | FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) 30 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 31 | SentryFlutterPlugin.register(with: registry.registrar(forPlugin: "SentryFlutterPlugin")) 32 | SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) 33 | SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) 34 | SignInWithApplePlugin.register(with: registry.registrar(forPlugin: "SignInWithApplePlugin")) 35 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) 36 | } 37 | -------------------------------------------------------------------------------- /.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: "5dcb86f68f239346676ceb1ed1ea385bd215fba1" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 17 | base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 18 | - platform: android 19 | create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 20 | base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 21 | - platform: ios 22 | create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 23 | base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 24 | - platform: linux 25 | create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 26 | base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 27 | - platform: macos 28 | create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 29 | base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 30 | - platform: web 31 | create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 32 | base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 33 | - platform: windows 34 | create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 35 | base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: skribla 2 | description: "A new Flutter project." 3 | publish_to: 'none' 4 | version: 0.2.4+15 5 | 6 | environment: 7 | sdk: '>=3.5.0 <4.0.0' 8 | 9 | dependencies: 10 | cloud_firestore: ^5.3.0 11 | confetti: ^0.7.0 12 | device_info_plus: ^10.1.2 13 | firebase_auth: ^5.2.0 14 | firebase_core: ^3.4.0 15 | flex_color_scheme: ^7.3.1 16 | flutter: 17 | sdk: flutter 18 | flutter_localizations: 19 | sdk: flutter 20 | flutter_native_splash: ^2.4.1 21 | flutter_riverpod: ^2.5.1 22 | flutter_screenutil: ^5.9.3 23 | freezed_annotation: ^2.4.3 24 | go_router: ^14.2.1 25 | google_sign_in: ^6.2.1 26 | in_app_review: ^2.0.9 27 | infinite_scroll_pagination: ^4.0.0 28 | intl: ^0.19.0 29 | json_annotation: ^4.9.0 30 | lukehog: ^0.4.1+2 31 | package_info_plus: ^8.0.1 32 | path_provider: ^2.1.4 33 | phosphor_flutter: ^2.1.0 34 | sentry_flutter: ^8.7.0 35 | share_plus: ^10.0.0 36 | shared_preferences: ^2.2.3 37 | shimmer: ^3.0.0 38 | sign_in_with_apple: ^6.1.2 39 | toastification: ^2.1.0 40 | url_launcher: ^6.3.0 41 | url_strategy: ^0.3.0 42 | 43 | # Fix google signin for macos 44 | # https://github.com/google/GoogleSignIn-iOS/issues/165#issuecomment-2206754381 45 | dependency_overrides: 46 | google_sign_in_ios: 5.7.5 47 | 48 | dev_dependencies: 49 | build_runner: ^2.4.11 50 | flutter_test: 51 | sdk: flutter 52 | freezed: ^2.5.2 53 | json_serializable: ^6.8.0 54 | very_good_analysis: ^6.0.0 55 | 56 | flutter: 57 | uses-material-design: true 58 | generate: true 59 | assets: 60 | - assets/images/ 61 | - assets/fonts/ 62 | - shorebird.yaml 63 | fonts: 64 | - family: MoreSugarMedium 65 | fonts: 66 | - asset: assets/fonts/more-sugar-medium.ttf 67 | weight: 800 68 | - family: MoreSugarRegular 69 | fonts: 70 | - asset: assets/fonts/more-sugar-regular.ttf 71 | weight: 400 72 | -------------------------------------------------------------------------------- /lib/src/core/service/logger.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:sentry_flutter/sentry_flutter.dart'; 3 | 4 | /// A class for logging messages with different levels of severity. 5 | /// 6 | /// This class provides a way to log messages with different levels of severity, such as info, request, build, watch, and error. 7 | /// It also integrates with Sentry for error tracking in release mode. 8 | /// 9 | /// Attributes: 10 | /// - `tag`: A string identifier for the logger instance. 11 | /// 12 | /// Methods: 13 | /// - `log`: Logs a message with no specific level of severity. 14 | /// - `info`, `request`, `watch`: Log messages with specific levels of severity. 15 | /// - `error`: Logs an error message and sends it to Sentry in release mode. 16 | /// - `_print`: A private method for printing messages to the console or sending them to Sentry. 17 | /// 18 | /// Usage: 19 | /// This class is typically used to log messages throughout the application. For example, to log an error, you would create an instance of this class and call the `error` method. 20 | /// The `error` method will log the error message to the console and send it to Sentry if the app is running in release mode. 21 | /// Similarly, other methods can be used to log messages with different levels of severity. 22 | class Logger { 23 | const Logger(this.tag); 24 | final String tag; 25 | 26 | static void log(Object x) => _print('[📝 Logger]: $x'); 27 | 28 | void info(Object x) => _print('[🔊 $tag]: $x'); 29 | 30 | void request(Object x) => _print('[🚀 $tag]: $x'); 31 | 32 | void watch(Object x) => _print('[👀 $tag]: $x'); 33 | 34 | void error(Object x, {StackTrace? stack}) { 35 | _print('[🐞 $tag]: $x${stack != null ? '\nStack: $stack' : ''}'); 36 | if (kReleaseMode) Sentry.captureException(x, stackTrace: stack); 37 | } 38 | 39 | static void _print(String text) { 40 | if (kDebugMode) debugPrint(text); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} WIN32 10 | "flutter_window.cpp" 11 | "main.cpp" 12 | "utils.cpp" 13 | "win32_window.cpp" 14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 15 | "Runner.rc" 16 | "runner.exe.manifest" 17 | ) 18 | 19 | # Apply the standard set of build settings. This can be removed for applications 20 | # that need different build settings. 21 | apply_standard_settings(${BINARY_NAME}) 22 | 23 | # Add preprocessor definitions for the build version. 24 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") 25 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") 26 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") 27 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") 28 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") 29 | 30 | # Disable Windows macros that collide with C++ standard library functions. 31 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 32 | 33 | # Add dependency libraries and include directories. Add any application-specific 34 | # dependencies here. 35 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 36 | target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") 37 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 38 | 39 | # Run the Flutter tool portions of the build. This must not be removed. 40 | add_dependencies(${BINARY_NAME} flutter_assemble) 41 | -------------------------------------------------------------------------------- /windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | unsigned int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr) 51 | -1; // remove the trailing null character 52 | int input_length = (int)wcslen(utf16_string); 53 | std::string utf8_string; 54 | if (target_length == 0 || target_length > utf8_string.max_size()) { 55 | return utf8_string; 56 | } 57 | utf8_string.resize(target_length); 58 | int converted_length = ::WideCharToMultiByte( 59 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 60 | input_length, utf8_string.data(), target_length, nullptr, nullptr); 61 | if (converted_length == 0) { 62 | return std::string(); 63 | } 64 | return utf8_string; 65 | } 66 | -------------------------------------------------------------------------------- /lib/src/core/util/generated/feature_flags.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of '../feature_flags.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | FeatureFlags _$FeatureFlagsFromJson(Map json) => FeatureFlags( 10 | majorVersion: (json['major_version'] as num?)?.toInt() ?? 0, 11 | minorVersion: (json['minor_version'] as num?)?.toInt() ?? 0, 12 | webDown: json['web_down'] as bool? ?? false, 13 | iosDown: json['ios_down'] as bool? ?? false, 14 | androidDown: json['android_down'] as bool? ?? false, 15 | macDown: json['mac_down'] as bool? ?? false, 16 | drawDelayMilliseconds: (json['draw_delay_milliseconds'] as num?)?.toInt() ?? 100, 17 | coolDurationSeconds: (json['cool_duration_seconds'] as num?)?.toInt() ?? 5, 18 | skipDurationSeconds: (json['skip_duration_seconds'] as num?)?.toInt() ?? 7, 19 | turnDurationSeconds: (json['turn_duration_seconds'] as num?)?.toInt() ?? 20, 20 | completeDurationSeconds: (json['complete_duration_seconds'] as num?)?.toInt() ?? 3, 21 | totalWordsCount: (json['total_words_count'] as num?)?.toInt() ?? 100, 22 | wordsPerGame: (json['words_per_game'] as num?)?.toInt() ?? 3, 23 | ); 24 | 25 | Map _$FeatureFlagsToJson(FeatureFlags instance) => { 26 | 'major_version': instance.majorVersion, 27 | 'minor_version': instance.minorVersion, 28 | 'web_down': instance.webDown, 29 | 'ios_down': instance.iosDown, 30 | 'android_down': instance.androidDown, 31 | 'mac_down': instance.macDown, 32 | 'draw_delay_milliseconds': instance.drawDelayMilliseconds, 33 | 'cool_duration_seconds': instance.coolDurationSeconds, 34 | 'skip_duration_seconds': instance.skipDurationSeconds, 35 | 'turn_duration_seconds': instance.turnDurationSeconds, 36 | 'complete_duration_seconds': instance.completeDurationSeconds, 37 | 'total_words_count': instance.totalWordsCount, 38 | 'words_per_game': instance.wordsPerGame, 39 | }; 40 | -------------------------------------------------------------------------------- /lib/src/core/widgets/error_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/gestures.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 4 | import 'package:skribla/src/core/di/di.dart'; 5 | import 'package:skribla/src/core/service/support.dart'; 6 | import 'package:skribla/src/core/util/config.dart'; 7 | import 'package:skribla/src/core/util/constants.dart'; 8 | import 'package:skribla/src/core/util/extension.dart'; 9 | import 'package:skribla/src/core/widgets/app_button.dart'; 10 | 11 | class ErrorWidget extends StatelessWidget { 12 | const ErrorWidget({ 13 | this.topSpacer, 14 | this.retry, 15 | super.key, 16 | }); 17 | 18 | final double? topSpacer; 19 | final VoidCallback? retry; 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return Column( 24 | children: [ 25 | SizedBox(height: topSpacer ?? Config.height * 0.3), 26 | Text( 27 | context.loc.errorTitle, 28 | textAlign: TextAlign.center, 29 | style: context.textTheme.titleSmall, 30 | ), 31 | Config.vBox12, 32 | Consumer( 33 | builder: (context, ref, child) { 34 | final user = ref.watch(authProvider.select((it) => it.user)); 35 | 36 | return Text.rich( 37 | TextSpan( 38 | text: '${context.loc.errorSubtitle} ', 39 | children: [ 40 | TextSpan( 41 | text: Constants.email, 42 | style: TextStyle( 43 | decoration: TextDecoration.underline, 44 | color: context.colorScheme.primary, 45 | ), 46 | recognizer: TapGestureRecognizer() 47 | ..onTap = () => Support.instance.contactSupport(user), 48 | ), 49 | ], 50 | ), 51 | textAlign: TextAlign.center, 52 | ); 53 | }, 54 | ), 55 | if (retry != null) ...[ 56 | Config.vBox12, 57 | AppButton( 58 | text: context.loc.retry, 59 | onPressed: retry, 60 | ), 61 | ], 62 | ], 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /ios/fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | # This file contains the fastlane.tools configuration 2 | # You can find the documentation at https://docs.fastlane.tools 3 | # 4 | # For a list of all available actions, check out 5 | # 6 | # https://docs.fastlane.tools/actions 7 | # 8 | # For a list of all available plugins, check out 9 | # 10 | # https://docs.fastlane.tools/plugins/available-plugins 11 | # 12 | 13 | # Uncomment the line if you want fastlane to automatically update itself 14 | # update_fastlane 15 | 16 | default_platform(:ios) 17 | 18 | platform :ios do 19 | 20 | desc "Set Info.plist Version and Build Number" 21 | lane :set_full_version do 22 | version = flutter_version() 23 | 24 | increment_version_number(version_number: version['version_name']) 25 | 26 | increment_build_number(build_number: version['version_code']) 27 | end 28 | 29 | desc "Deploy a dev build to Firebase App Distribution" 30 | lane :dev do 31 | set_full_version() 32 | 33 | sh("flutter build ipa --release --flavor=dev --export-method=ad-hoc --target=lib/main_dev.dart --dart-define-from-file=/Users/ifeanyionuoha/skribla/dev_creds.json") 34 | 35 | firebase_app_distribution( 36 | app: '1:1056704511056:ios:3c65b99d3d4ab0e526e555', 37 | service_credentials_file: "/Users/ifeanyionuoha/skribla/skribla-dev-firebase.json", 38 | groups: 'dev_testers', 39 | release_notes: File.read("../../release_notes.txt"), 40 | ipa_path: "../build/ios/ipa/skribla.ipa" 41 | ) 42 | end 43 | 44 | desc "Deploy a prod build to TestFlight" 45 | lane :prod do 46 | set_full_version() 47 | 48 | shorebird_release( 49 | platform: "ios", 50 | args: "--flavor=prod --target=lib/main_prod.dart --dart-define-from-file=/Users/ifeanyionuoha/skribla/prod_creds.json" 51 | ) 52 | upload_to_testflight( 53 | api_key_path: "/Users/ifeanyionuoha/skribla/skribla-prod-app-store.json", 54 | ipa: '../build/ios/ipa/skribla.ipa', 55 | skip_waiting_for_build_processing: true, 56 | ) 57 | end 58 | 59 | desc "Patch latest prod build on App Store" 60 | lane :patch do 61 | shorebird_patch( 62 | platform: "ios", 63 | args: "--flavor=prod --target=lib/main_prod.dart --dart-define-from-file=/Users/ifeanyionuoha/skribla/prod_creds.json" 64 | ) 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/src/app/game/presentation/widgets/report_reason_sheet.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:go_router/go_router.dart'; 4 | import 'package:skribla/src/core/util/config.dart'; 5 | import 'package:skribla/src/core/util/extension.dart'; 6 | import 'package:skribla/src/core/widgets/app_button.dart'; 7 | import 'package:skribla/src/core/widgets/input_field.dart'; 8 | 9 | class ReportReasonSheet extends StatefulWidget { 10 | const ReportReasonSheet({ 11 | this.username = '', 12 | super.key, 13 | }); 14 | 15 | final String username; 16 | 17 | @override 18 | State createState() => _ReportReasonSheetState(); 19 | } 20 | 21 | class _ReportReasonSheetState extends State { 22 | final _msgCtrl = TextEditingController(); 23 | 24 | void _continue() { 25 | context.pop(_msgCtrl.text.trim()); 26 | } 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | return Padding( 31 | padding: context.padding.copyWith( 32 | top: Config.h(15), 33 | left: Config.w(15), 34 | right: Config.w(15), 35 | bottom: context.viewInsets.bottom + (kIsWeb ? 8 : 30), 36 | ), 37 | child: Column( 38 | crossAxisAlignment: CrossAxisAlignment.stretch, 39 | mainAxisSize: MainAxisSize.min, 40 | children: [ 41 | Text( 42 | context.loc.report(widget.username).trim(), 43 | style: context.textTheme.bodyLarge, 44 | textAlign: TextAlign.center, 45 | ), 46 | Config.vBox12, 47 | InputField( 48 | controller: _msgCtrl, 49 | hint: context.loc.reasonForReporting, 50 | maxLines: null, 51 | textInputAction: TextInputAction.send, 52 | onFieldSubmitted: (_) => _continue(), 53 | ), 54 | Config.vBox24, 55 | ValueListenableBuilder( 56 | valueListenable: _msgCtrl, 57 | builder: (context, ctrl, child) { 58 | return AppButton( 59 | text: context.loc.continueBtnTxt, 60 | onPressed: ctrl.text.trim().isEmpty ? null : _continue, 61 | ); 62 | }, 63 | ), 64 | ], 65 | ), 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /android/fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | # This file contains the fastlane.tools configuration 2 | # You can find the documentation at https://docs.fastlane.tools 3 | # 4 | # For a list of all available actions, check out 5 | # 6 | # https://docs.fastlane.tools/actions 7 | # 8 | # For a list of all available plugins, check out 9 | # 10 | # https://docs.fastlane.tools/plugins/available-plugins 11 | # 12 | 13 | # Uncomment the line if you want fastlane to automatically update itself 14 | # update_fastlane 15 | 16 | default_platform(:android) 17 | 18 | platform :android do 19 | 20 | desc "Set Gradle Version and Build Number" 21 | lane :set_full_version do 22 | version = flutter_version() 23 | android_set_version_name(version_name: version['version_name']) 24 | android_set_version_code(version_code: version['version_code']) 25 | end 26 | 27 | desc "Deploy a dev build to Firebase App Distribution" 28 | lane :dev do 29 | set_full_version() 30 | 31 | sh("flutter build apk --release --flavor=dev --target=lib/main_dev.dart --dart-define-from-file=/Users/ifeanyionuoha/skribla/dev_creds.json") 32 | 33 | firebase_app_distribution( 34 | app: '1:1056704511056:android:b1a81f5adcdab0d926e555', 35 | service_credentials_file: "/Users/ifeanyionuoha/skribla/skribla-dev-firebase.json", 36 | groups: 'dev_testers', 37 | android_artifact_type: 'APK', 38 | release_notes: File.read("../../release_notes.txt"), 39 | android_artifact_path: "../build/app/outputs/apk/dev/release/app-dev-release.apk" 40 | ) 41 | end 42 | 43 | desc "Deploy a prod build to Google Play Internal Test" 44 | lane :prod do 45 | set_full_version() 46 | 47 | shorebird_release( 48 | platform: "android", 49 | args: "--flavor=prod --target=lib/main_prod.dart --dart-define-from-file=/Users/ifeanyionuoha/skribla/prod_creds.json" 50 | ) 51 | 52 | upload_to_play_store( 53 | aab: "../build/app/outputs/bundle/prodRelease/app-prod-release.aab", 54 | track: "internal", 55 | ) 56 | end 57 | 58 | desc "Patch latest prod build on Google Play" 59 | lane :patch do 60 | shorebird_patch( 61 | platform: "android", 62 | args: "--flavor=prod --target=lib/main_prod.dart --dart-define-from-file=/Users/ifeanyionuoha/skribla/prod_creds.json" 63 | ) 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /lib/src/core/screens/update_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:go_router/go_router.dart'; 3 | import 'package:skribla/src/core/resource/app_icons.dart'; 4 | import 'package:skribla/src/core/router/routes.dart'; 5 | import 'package:skribla/src/core/service/support.dart'; 6 | import 'package:skribla/src/core/util/config.dart'; 7 | import 'package:skribla/src/core/util/enums.dart'; 8 | import 'package:skribla/src/core/util/extension.dart'; 9 | import 'package:skribla/src/core/widgets/app_button.dart'; 10 | 11 | class UpdateScreen extends StatelessWidget { 12 | const UpdateScreen({ 13 | this.forced = false, 14 | super.key, 15 | }); 16 | 17 | final bool forced; 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return Scaffold( 22 | body: Padding( 23 | padding: Config.all(15), 24 | child: Column( 25 | mainAxisAlignment: MainAxisAlignment.center, 26 | crossAxisAlignment: CrossAxisAlignment.stretch, 27 | children: [ 28 | Icon( 29 | AppIcons.downloadSimple, 30 | size: Config.height * 0.2, 31 | color: context.colorScheme.primary, 32 | ), 33 | Text( 34 | context.loc.updateTitle, 35 | textAlign: TextAlign.center, 36 | style: context.textTheme.titleSmall, 37 | ), 38 | Config.vBox12, 39 | Text( 40 | context.loc.updateSubtitle, 41 | textAlign: TextAlign.center, 42 | ), 43 | ], 44 | ), 45 | ), 46 | bottomSheet: Column( 47 | crossAxisAlignment: CrossAxisAlignment.stretch, 48 | mainAxisSize: MainAxisSize.min, 49 | children: [ 50 | Config.vBox12, 51 | AppButton( 52 | hPadding: 15, 53 | text: context.loc.update, 54 | onPressed: Support.instance.openStore, 55 | ), 56 | if (!forced) ...[ 57 | Config.vBox12, 58 | AppButton( 59 | hPadding: 15, 60 | type: ButtonType.outlined, 61 | text: context.loc.maybeLater, 62 | onPressed: () => context.go(Routes.home), 63 | ), 64 | ], 65 | Config.vBox30, 66 | ], 67 | ), 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/src/core/screens/suspended_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/gestures.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 4 | import 'package:skribla/src/core/di/di.dart'; 5 | import 'package:skribla/src/core/resource/app_icons.dart'; 6 | import 'package:skribla/src/core/service/support.dart'; 7 | import 'package:skribla/src/core/util/config.dart'; 8 | import 'package:skribla/src/core/util/constants.dart'; 9 | import 'package:skribla/src/core/util/extension.dart'; 10 | 11 | class SuspendedScreen extends StatelessWidget { 12 | const SuspendedScreen({super.key}); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Scaffold( 17 | body: Padding( 18 | padding: Config.all(15), 19 | child: Column( 20 | mainAxisAlignment: MainAxisAlignment.center, 21 | crossAxisAlignment: CrossAxisAlignment.stretch, 22 | children: [ 23 | Icon( 24 | AppIcons.warning, 25 | size: Config.height * 0.2, 26 | color: context.colorScheme.primary, 27 | ), 28 | Text( 29 | context.loc.suspendedTitle, 30 | textAlign: TextAlign.center, 31 | style: context.textTheme.titleSmall, 32 | ), 33 | Config.vBox12, 34 | Consumer( 35 | builder: (context, ref, child) { 36 | final user = ref.watch(authProvider.select((it) => it.user)); 37 | return Text.rich( 38 | TextSpan( 39 | text: '${context.loc.suspendedSubtitle} ', 40 | children: [ 41 | TextSpan( 42 | text: Constants.email, 43 | style: TextStyle( 44 | decoration: TextDecoration.underline, 45 | color: context.colorScheme.primary, 46 | ), 47 | recognizer: TapGestureRecognizer() 48 | ..onTap = () => Support.instance.contactSupport(user), 49 | ), 50 | ], 51 | ), 52 | textAlign: TextAlign.center, 53 | ); 54 | }, 55 | ), 56 | ], 57 | ), 58 | ), 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CADisableMinimumFrameDurationOnPhone 6 | 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleDisplayName 10 | $(APP_DISPLAY_NAME) 11 | CFBundleExecutable 12 | $(EXECUTABLE_NAME) 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | skribla 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | 0.2.4 23 | CFBundleSignature 24 | ???? 25 | CFBundleURLTypes 26 | 27 | 28 | CFBundleTypeRole 29 | Editor 30 | CFBundleURLSchemes 31 | 32 | $(GID_REVERSED_CLIENT_ID) 33 | 34 | 35 | 36 | CFBundleVersion 37 | 15 38 | GIDClientID 39 | $(GID_CLIENT_ID) 40 | LSRequiresIPhoneOS 41 | 42 | UIApplicationSupportsIndirectInputEvents 43 | 44 | UILaunchStoryboardName 45 | LaunchScreen 46 | UIMainStoryboardFile 47 | Main 48 | UISupportedInterfaceOrientations 49 | 50 | UIInterfaceOrientationPortrait 51 | UIInterfaceOrientationLandscapeLeft 52 | UIInterfaceOrientationLandscapeRight 53 | 54 | UISupportedInterfaceOrientations~ipad 55 | 56 | UIInterfaceOrientationPortrait 57 | UIInterfaceOrientationPortraitUpsideDown 58 | UIInterfaceOrientationLandscapeLeft 59 | UIInterfaceOrientationLandscapeRight 60 | 61 | FlutterDeepLinkingEnabled 62 | 63 | UIStatusBarHidden 64 | 65 | UIViewControllerBasedStatusBarAppearance 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project) 8 | : project_(project) {} 9 | 10 | FlutterWindow::~FlutterWindow() {} 11 | 12 | bool FlutterWindow::OnCreate() { 13 | if (!Win32Window::OnCreate()) { 14 | return false; 15 | } 16 | 17 | RECT frame = GetClientArea(); 18 | 19 | // The size here must match the window dimensions to avoid unnecessary surface 20 | // creation / destruction in the startup path. 21 | flutter_controller_ = std::make_unique( 22 | frame.right - frame.left, frame.bottom - frame.top, project_); 23 | // Ensure that basic setup of the controller was successful. 24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 25 | return false; 26 | } 27 | RegisterPlugins(flutter_controller_->engine()); 28 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 29 | 30 | flutter_controller_->engine()->SetNextFrameCallback([&]() { 31 | this->Show(); 32 | }); 33 | 34 | // Flutter can complete the first frame before the "show window" callback is 35 | // registered. The following call ensures a frame is pending to ensure the 36 | // window is shown. It is a no-op if the first frame hasn't completed yet. 37 | flutter_controller_->ForceRedraw(); 38 | 39 | return true; 40 | } 41 | 42 | void FlutterWindow::OnDestroy() { 43 | if (flutter_controller_) { 44 | flutter_controller_ = nullptr; 45 | } 46 | 47 | Win32Window::OnDestroy(); 48 | } 49 | 50 | LRESULT 51 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 52 | WPARAM const wparam, 53 | LPARAM const lparam) noexcept { 54 | // Give Flutter, including plugins, an opportunity to handle window messages. 55 | if (flutter_controller_) { 56 | std::optional result = 57 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 58 | lparam); 59 | if (result) { 60 | return *result; 61 | } 62 | } 63 | 64 | switch (message) { 65 | case WM_FONTCHANGE: 66 | flutter_controller_->engine()->ReloadSystemFonts(); 67 | break; 68 | } 69 | 70 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 71 | } 72 | -------------------------------------------------------------------------------- /lib/src/core/service/haptics.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import 'package:shared_preferences/shared_preferences.dart'; 3 | import 'package:skribla/src/core/resource/app_keys.dart'; 4 | 5 | /// A singleton class for managing haptic feedback. 6 | /// 7 | /// This class is responsible for providing a centralized way to manage haptic feedback throughout the application. 8 | /// It uses the Flutter's HapticFeedback class to generate haptic feedback and provides methods for different types of feedback. 9 | /// The feedback types include light, medium, and heavy impacts, as well as a selection click. 10 | /// The class also checks if haptic feedback is enabled in the app's settings before generating feedback. 11 | /// 12 | /// Key features: 13 | /// - Provides a singleton instance for accessing haptic feedback functionality throughout the app. 14 | /// - Offers methods for different types of haptic feedback, including light, medium, and heavy impacts, as well as a selection click. 15 | /// - Checks if haptic feedback is enabled in the app's settings before generating feedback. 16 | /// 17 | /// Usage: 18 | /// This class is typically used to generate haptic feedback in response to user interactions, such as button presses or other tactile events. 19 | /// For example, when a button is pressed, the `lightImpact` method can be called to generate a light haptic feedback. 20 | /// Similarly, other methods can be used to generate different types of feedback based on the context. 21 | /// 22 | 23 | final class Haptics { 24 | Haptics._internal(); 25 | static final _singleton = Haptics._internal(); 26 | 27 | static Haptics get instance => _singleton; 28 | 29 | SharedPreferences? sharedPreferences; 30 | 31 | Future init() async { 32 | sharedPreferences = await SharedPreferences.getInstance(); 33 | } 34 | 35 | void lightImpact() { 36 | if (sharedPreferences?.getBool(AppKeys.haptics) ?? true) { 37 | HapticFeedback.lightImpact(); 38 | } 39 | } 40 | 41 | void mediumImpact() { 42 | if (sharedPreferences?.getBool(AppKeys.haptics) ?? true) { 43 | HapticFeedback.mediumImpact(); 44 | } 45 | } 46 | 47 | void heavyImpact() { 48 | if (sharedPreferences?.getBool(AppKeys.haptics) ?? true) { 49 | HapticFeedback.heavyImpact(); 50 | } 51 | } 52 | 53 | void selectionClick() { 54 | if (sharedPreferences?.getBool(AppKeys.haptics) ?? true) { 55 | HapticFeedback.selectionClick(); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/src/core/theme/colors.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | // Light and dark ColorSchemes made by FlexColorScheme v7.3.1. 4 | // These ColorScheme objects require Flutter 3.7 or later. 5 | const ColorScheme flexSchemeLight = ColorScheme( 6 | brightness: Brightness.light, 7 | primary: Color(0xff4527a0), 8 | onPrimary: Color(0xffffffff), 9 | primaryContainer: Color(0xffd1c4e9), 10 | onPrimaryContainer: Color(0xff111013), 11 | secondary: Color(0xff00b0ff), 12 | onSecondary: Color(0xff000000), 13 | secondaryContainer: Color(0xff9fcbf1), 14 | onSecondaryContainer: Color(0xff0e1114), 15 | tertiary: Color(0xff0091ea), 16 | onTertiary: Color(0xffffffff), 17 | tertiaryContainer: Color(0xffcfe4ff), 18 | onTertiaryContainer: Color(0xff111314), 19 | error: Color(0xffb00020), 20 | onError: Color(0xffffffff), 21 | errorContainer: Color(0xfffcd8df), 22 | onErrorContainer: Color(0xff141213), 23 | surface: Color(0xfff9f9fc), 24 | onSurface: Color(0xff090909), 25 | onSurfaceVariant: Color(0xff111112), 26 | outline: Color(0xff7c7c7c), 27 | outlineVariant: Color(0xffc8c8c8), 28 | shadow: Color(0xff000000), 29 | scrim: Color(0xff000000), 30 | inverseSurface: Color(0xff121114), 31 | onInverseSurface: Color(0xfff5f5f5), 32 | inversePrimary: Color(0xffd1c0ff), 33 | surfaceTint: Color(0xff4527a0), 34 | ); 35 | 36 | const ColorScheme flexSchemeDark = ColorScheme( 37 | brightness: Brightness.dark, 38 | primary: Color(0xffb39ddb), 39 | onPrimary: Color(0xff121014), 40 | primaryContainer: Color(0xff7e57c2), 41 | onPrimaryContainer: Color(0xfff3edfe), 42 | secondary: Color(0xff40c4ff), 43 | onSecondary: Color(0xff091314), 44 | secondaryContainer: Color(0xff0179b6), 45 | onSecondaryContainer: Color(0xffdff2fc), 46 | tertiary: Color(0xff80d8ff), 47 | onTertiary: Color(0xff0e1414), 48 | tertiaryContainer: Color(0xff00497b), 49 | onTertiaryContainer: Color(0xffdfebf3), 50 | error: Color(0xffcf6679), 51 | onError: Color(0xff140c0d), 52 | errorContainer: Color(0xffb1384e), 53 | onErrorContainer: Color(0xfffbe8ec), 54 | surface: Color(0xff19181b), 55 | onSurface: Color(0xffececed), 56 | onSurfaceVariant: Color(0xffe0e0e1), 57 | outline: Color(0xff76767d), 58 | outlineVariant: Color(0xff2c2c2e), 59 | shadow: Color(0xff000000), 60 | scrim: Color(0xff000000), 61 | inverseSurface: Color(0xfffbfafd), 62 | onInverseSurface: Color(0xff131313), 63 | inversePrimary: Color(0xff5b526d), 64 | surfaceTint: Color(0xffb39ddb), 65 | ); 66 | -------------------------------------------------------------------------------- /lib/src/app/settings/presentation/provider/settings_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 3 | import 'package:shared_preferences/shared_preferences.dart'; 4 | import 'package:skribla/src/app/settings/data/repository/settings_repository.dart'; 5 | import 'package:skribla/src/app/settings/presentation/provider/settings_state.dart'; 6 | import 'package:skribla/src/core/resource/app_keys.dart'; 7 | import 'package:skribla/src/core/service/haptics.dart'; 8 | 9 | /// A provider class for managing application settings. 10 | /// 11 | /// This class extends [StateNotifier] and manages the state of type [SettingsState]. 12 | /// It provides functionality to toggle haptic feedback and store/retrieve settings 13 | /// using [SharedPreferences]. 14 | /// 15 | /// The class interacts with [SettingsRepository] for potential future expansion 16 | /// of settings-related operations. 17 | /// 18 | /// Key features: 19 | /// - Stores and retrieves settings using SharedPreferences 20 | /// - Provides the current app theme 21 | /// - Provides the current app version 22 | /// 23 | /// Usage: 24 | /// This provider is typically used in conjunction with Riverpod to manage 25 | /// the state of application settings throughout the app. 26 | 27 | class SettingsProvider extends StateNotifier { 28 | SettingsProvider({ 29 | required this.sharedPreferences, 30 | required this.settingsRepository, 31 | }) : super( 32 | SettingsState( 33 | theme: ThemeMode.values.firstWhere( 34 | (e) => e.name == sharedPreferences?.getString('currentTheme'), 35 | orElse: () => ThemeMode.system, 36 | ), 37 | hapticsOn: sharedPreferences?.getBool(AppKeys.haptics) ?? true, 38 | version: sharedPreferences?.getString(AppKeys.version) ?? '', 39 | ), 40 | ); 41 | 42 | final SharedPreferences? sharedPreferences; 43 | final SettingsRepository settingsRepository; 44 | 45 | // ignore: avoid_positional_boolean_parameters 46 | Future toggleHaptics(bool val) async { 47 | await sharedPreferences?.setBool(AppKeys.haptics, val); 48 | state = state.copyWith(hapticsOn: val); 49 | if (val) { 50 | Haptics.instance.mediumImpact(); 51 | } 52 | } 53 | 54 | void setTheme(ThemeMode? mode) { 55 | if (mode == null) return; 56 | state = state.copyWith(theme: mode); 57 | sharedPreferences?.setString(AppKeys.theme, mode.name); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/src/app/game/data/models/generated/game_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of '../game_model.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$GameModelImpl _$$GameModelImplFromJson(Map json) => _$GameModelImpl( 10 | id: json['id'] as String, 11 | createdAt: DateTime.parse(json['created_at'] as String), 12 | currentPlayer: PlayerModel.fromJson(json['current_player'] as Map), 13 | currentWord: WordModel.fromJson(json['current_word'] as Map), 14 | status: $enumDecodeNullable(_$GameStatusEnumMap, json['status']) ?? GameStatus.open, 15 | uids: (json['uids'] as List?)?.map((e) => e as String).toList() ?? const [], 16 | correctGuess: 17 | (json['correct_guess'] as List?)?.map((e) => e as String).toList() ?? const [], 18 | players: (json['players'] as List?) 19 | ?.map((e) => PlayerModel.fromJson(e as Map)) 20 | .toList() ?? 21 | const [], 22 | online: (json['online'] as List?)?.map((e) => e as String).toList() ?? const [], 23 | currentArt: (json['current_art'] as List?) 24 | ?.map((e) => LineModel.fromJson(e as Map)) 25 | .toList() ?? 26 | const [], 27 | numOfPlayers: (json['num_of_players'] as num?)?.toInt() ?? 4, 28 | numOfArts: (json['num_of_arts'] as num?)?.toInt() ?? 0, 29 | ); 30 | 31 | Map _$$GameModelImplToJson(_$GameModelImpl instance) => { 32 | 'id': instance.id, 33 | 'created_at': instance.createdAt.toIso8601String(), 34 | 'current_player': instance.currentPlayer.toJson(), 35 | 'current_word': instance.currentWord.toJson(), 36 | 'status': _$GameStatusEnumMap[instance.status]!, 37 | 'uids': instance.uids, 38 | 'correct_guess': instance.correctGuess, 39 | 'players': instance.players.map((e) => e.toJson()).toList(), 40 | 'online': instance.online, 41 | 'current_art': instance.currentArt.map((e) => e.toJson()).toList(), 42 | 'num_of_players': instance.numOfPlayers, 43 | 'num_of_arts': instance.numOfArts, 44 | }; 45 | 46 | const _$GameStatusEnumMap = { 47 | GameStatus.open: 'open', 48 | GameStatus.private: 'private', 49 | GameStatus.closed: 'closed', 50 | GameStatus.complete: 'complete', 51 | }; 52 | -------------------------------------------------------------------------------- /lib/src/app/game/presentation/widgets/art_painter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:skribla/src/app/game/data/models/line_model.dart'; 3 | import 'package:skribla/src/core/util/extension.dart'; 4 | 5 | class ArtPainter extends CustomPainter { 6 | ArtPainter({required this.art, this.repaint = true}); 7 | final List art; 8 | final bool repaint; 9 | 10 | @override 11 | void paint(Canvas canvas, Size size) { 12 | final paint = Paint()..strokeCap = StrokeCap.round; 13 | 14 | for (var i = 0; i < art.length; ++i) { 15 | final line = art[i]; 16 | // Calculate the scaling factors 17 | final scaleX = size.width / line.size.width; 18 | final scaleY = size.height / line.size.height; 19 | 20 | for (var j = 0; j < line.path.lastIndex; ++j) { 21 | paint 22 | ..color = line.color 23 | ..strokeWidth = line.stroke.toDouble(); 24 | 25 | // Scale the points 26 | final p1 = Offset( 27 | line.path[j].dx * scaleX, 28 | line.path[j].dy * scaleY, 29 | ); 30 | final p2 = Offset( 31 | line.path[j + 1].dx * scaleX, 32 | line.path[j + 1].dy * scaleY, 33 | ); 34 | 35 | canvas.drawLine(p1, p2, paint); 36 | } 37 | } 38 | } 39 | 40 | @override 41 | bool shouldRepaint(ArtPainter oldDelegate) => repaint; 42 | } 43 | 44 | class AnimatedArtPainter extends CustomPainter { 45 | AnimatedArtPainter({required this.art, required this.progress}); 46 | final List art; 47 | final double progress; 48 | 49 | @override 50 | void paint(Canvas canvas, Size size) { 51 | final paint = Paint()..strokeCap = StrokeCap.round; 52 | 53 | for (var i = 0; i < art.length; ++i) { 54 | final line = art[i]; 55 | // Calculate the scaling factors 56 | final scaleX = size.width / line.size.width; 57 | final scaleY = size.height / line.size.height; 58 | 59 | for (var j = 0; j < line.path.lastIndex * progress; ++j) { 60 | paint 61 | ..color = line.color 62 | ..strokeWidth = line.stroke.toDouble(); 63 | 64 | // Scale the points 65 | final p1 = Offset( 66 | line.path[j].dx * scaleX, 67 | line.path[j].dy * scaleY, 68 | ); 69 | final p2 = Offset( 70 | line.path[j + 1].dx * scaleX, 71 | line.path[j + 1].dy * scaleY, 72 | ); 73 | 74 | canvas.drawLine(p1, p2, paint); 75 | } 76 | } 77 | } 78 | 79 | @override 80 | bool shouldRepaint(AnimatedArtPainter oldDelegate) => true; 81 | } 82 | -------------------------------------------------------------------------------- /lib/src/core/service/analytics.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:lukehog/lukehog.dart'; 3 | import 'package:skribla/env/env.dart'; 4 | import 'package:skribla/src/core/service/logger.dart'; 5 | import 'package:skribla/src/core/util/enums.dart'; 6 | 7 | /// A singleton class for managing analytics in the application. 8 | /// 9 | /// This class is responsible for capturing and sending analytics events to a third-party service. 10 | /// It uses the Lukehog library for analytics tracking and provides methods for initializing the analytics service, 11 | /// capturing events, and sending properties along with the events. 12 | /// 13 | /// Key features: 14 | /// - Initializes the analytics service with a Lukehog ID 15 | /// - Captures analytics events with optional properties 16 | /// - Provides a singleton instance for accessing analytics functionality throughout the app 17 | /// 18 | /// Usage: 19 | /// This class is typically used to capture analytics events throughout the application. 20 | /// For example, when the app is opened, the `init` method is called to initialize the analytics service, 21 | /// and then the `capture` method is used to capture the `appOpen` event. Similarly, other events can be captured 22 | /// using the `capture` method with the corresponding event type and optional properties. 23 | /// 24 | 25 | final class Analytics { 26 | Analytics._internal(); 27 | static final _singleton = Analytics._internal(); 28 | static const _logger = Logger('Analytics'); 29 | 30 | static Analytics get instance => _singleton; 31 | 32 | late Lukehog analytics; 33 | 34 | void init() { 35 | try { 36 | if (kDebugMode) return; 37 | analytics = Lukehog(Env.lukeHogId); 38 | capture(Event.appOpen); 39 | } catch (e, s) { 40 | _logger.error('capture $e', stack: s); 41 | } 42 | } 43 | 44 | /// Captures an analytics event with optional properties. 45 | /// 46 | /// This method is used to capture an analytics event with optional properties. 47 | /// If the app is in debug mode, the event is logged to the console instead of being sent to the analytics service. 48 | /// The event is captured using the Lukehog library, and the event type and optional properties are passed as parameters. 49 | /// 50 | /// Parameters: 51 | /// - `event`: The type of the event to be captured. 52 | /// - `properties`: Optional properties to be sent along with the event. 53 | /// 54 | void capture(Event event, {Map properties = const {}}) { 55 | try { 56 | if (kDebugMode) { 57 | _logger.info('Capturing ${event.value}'); 58 | return; 59 | } 60 | analytics.capture(event.value, properties: properties); 61 | } catch (e, s) { 62 | _logger.error('capture $e', stack: s); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/src/app/history/presentation/widgets/shared_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 3 | import 'package:skribla/src/app/game/presentation/widgets/art_painter.dart'; 4 | import 'package:skribla/src/app/history/data/models/exhibit_model.dart'; 5 | import 'package:skribla/src/core/di/di.dart'; 6 | import 'package:skribla/src/core/util/config.dart'; 7 | import 'package:skribla/src/core/util/extension.dart'; 8 | 9 | class SharedWidget extends ConsumerWidget { 10 | const SharedWidget({ 11 | required this.screenshotKey, 12 | required this.exhibit, 13 | super.key, 14 | }); 15 | 16 | final GlobalKey screenshotKey; 17 | final ExhibitModel exhibit; 18 | 19 | @override 20 | Widget build(BuildContext context, WidgetRef ref) { 21 | final sharing = ref.watch(historyProvider.select((it) => it.sharing)); 22 | 23 | return AnimatedCrossFade( 24 | crossFadeState: sharing ? CrossFadeState.showSecond : CrossFadeState.showFirst, 25 | duration: Config.duration300, 26 | firstChild: const SizedBox.shrink(), 27 | secondChild: RepaintBoundary( 28 | key: screenshotKey, 29 | child: Material( 30 | color: context.colorScheme.surface, 31 | borderRadius: Config.radius8, 32 | child: Padding( 33 | padding: const EdgeInsets.all(20), 34 | child: Column( 35 | mainAxisSize: MainAxisSize.min, 36 | children: [ 37 | Container( 38 | decoration: BoxDecoration( 39 | color: context.theme.inputDecorationTheme.fillColor, 40 | borderRadius: Config.radius8, 41 | ), 42 | child: CustomPaint( 43 | size: const Size.square(300), 44 | painter: AnimatedArtPainter( 45 | art: exhibit.art, 46 | progress: 1, 47 | ), 48 | ), 49 | ), 50 | Config.vBox12, 51 | Text.rich( 52 | TextSpan( 53 | text: exhibit.word.text, 54 | children: [ 55 | TextSpan( 56 | text: ' ${context.loc.by} ', 57 | style: TextStyle( 58 | color: context.colorScheme.secondary, 59 | fontStyle: FontStyle.italic, 60 | ), 61 | ), 62 | TextSpan(text: exhibit.player.name), 63 | ], 64 | ), 65 | style: context.textTheme.titleSmall, 66 | textAlign: TextAlign.center, 67 | ), 68 | ], 69 | ), 70 | ), 71 | ), 72 | ), 73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/src/app/main_app.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui' show PointerDeviceKind; 2 | 3 | import 'package:flutter/material.dart' hide Router; 4 | import 'package:flutter_gen/gen_l10n/app_localizations.dart'; 5 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 6 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 7 | import 'package:skribla/src/core/di/di.dart'; 8 | import 'package:skribla/src/core/router/router.dart'; 9 | import 'package:skribla/src/core/service/analytics.dart'; 10 | import 'package:skribla/src/core/service/haptics.dart'; 11 | import 'package:skribla/src/core/service/toast.dart'; 12 | import 'package:skribla/src/core/theme/app_theme.dart'; 13 | import 'package:skribla/src/core/util/extension.dart'; 14 | 15 | class MainApp extends StatefulWidget { 16 | const MainApp({super.key}); 17 | 18 | @override 19 | State createState() => _MainAppState(); 20 | } 21 | 22 | class _MainAppState extends State { 23 | @override 24 | void initState() { 25 | super.initState(); 26 | Analytics.instance.init(); 27 | Haptics.instance.init(); 28 | } 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | return ScreenUtilInit( 33 | designSize: const Size(393, 852), 34 | minTextAdapt: true, 35 | fontSizeResolver: (fontSize, instance) => fontSize.toDouble(), 36 | child: Consumer( 37 | builder: (context, ref, child) { 38 | final routerConfig = Router.instance.goRouter; 39 | final themeMode = ref.watch(settingsProvider.select((it) => it.theme)); 40 | 41 | return MaterialApp.router( 42 | title: 'Skribla', 43 | scrollBehavior: const MaterialScrollBehavior().copyWith( 44 | dragDevices: { 45 | PointerDeviceKind.mouse, 46 | PointerDeviceKind.touch, 47 | PointerDeviceKind.stylus, 48 | PointerDeviceKind.trackpad, 49 | PointerDeviceKind.unknown, 50 | }, 51 | ), 52 | darkTheme: AppTheme.darkTheme, 53 | themeMode: themeMode, 54 | theme: AppTheme.lightTheme, 55 | routerConfig: routerConfig, 56 | locale: const Locale('en'), 57 | localizationsDelegates: AppLocalizations.localizationsDelegates, 58 | supportedLocales: AppLocalizations.supportedLocales, 59 | builder: (BuildContext context, Widget? child) { 60 | final scale = context.mediaQuery.textScaler.clamp( 61 | minScaleFactor: 0.5, 62 | maxScaleFactor: 1, 63 | ); 64 | return MediaQuery( 65 | data: context.mediaQuery.copyWith( 66 | textScaler: scale, 67 | ), 68 | child: ToastProvider(child: child!), 69 | ); 70 | }, 71 | ); 72 | }, 73 | ), 74 | ); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | clean: 2 | flutter clean 3 | cd ios && rm -rf Podfile.lock 4 | cd ios && rm -rf Pods 5 | cd macos && rm -rf Podfile.lock 6 | cd macos && rm -rf Pods 7 | flutter pub get 8 | cd ios && pod install 9 | cd macos && pod install 10 | 11 | gen: 12 | dart run build_runner build --delete-conflicting-outputs 13 | flutter gen-l10n 14 | dart format --line-length 100 --set-exit-if-changed lib 15 | flutter pub get 16 | 17 | loc: 18 | flutter gen-l10n 19 | 20 | splash: 21 | dart run flutter_native_splash:create 22 | 23 | format: 24 | dart format --line-length 100 --set-exit-if-changed lib 25 | 26 | functions: 27 | firebase deploy --only functions --project=skribla-$(flavor) 28 | 29 | configure_dev: 30 | flutterfire configure --project=skribla-dev \ 31 | --out=lib/firebase_options_dev.dart \ 32 | --ios-bundle-id=com.skribla.ios.dev \ 33 | --macos-bundle-id=com.skribla.macos.dev \ 34 | --android-package-name=com.skribla.android.dev \ 35 | --web-app-id=1:1056704511056:web:c268b2cdb298bbf326e555 36 | 37 | configure_prod: 38 | flutterfire configure --project=skribla-prod \ 39 | --out=lib/firebase_options_prod.dart \ 40 | --ios-bundle-id=com.skribla.ios.prod \ 41 | --macos-bundle-id=com.skribla.macos.prod \ 42 | --android-package-name=com.skribla.android.prod \ 43 | --web-app-id=1:676660663299:web:6912a4b78f5b8cbcb82a36 44 | 45 | deeplink: 46 | adb shell am start -a android.intent.action.VIEW \ 47 | -c android.intent.category.BROWSABLE \ 48 | -d https://dev.skribla.com \ 49 | com.skribla.android.dev 50 | 51 | deploy_web: 52 | sh web_flavor_setup.sh $(flavor) 53 | flutter build web --release \ 54 | --target lib/main_$(flavor).dart \ 55 | --web-renderer canvaskit \ 56 | --dart-define-from-file /Users/ifeanyionuoha/skribla/$(flavor)_creds.json 57 | firebase deploy --only hosting --project=skribla-$(flavor) 58 | 59 | patch_mobile: 60 | cd ios && bundle exec fastlane patch 61 | cd android && bundle exec fastlane patch 62 | 63 | gh_release: 64 | @version=$$(cat pubspec.yaml | grep -o 'version:[^:]*' | cut -f2 -d":" | xargs); \ 65 | echo "Creating GitHub release for: $${version}"; \ 66 | gh release create "$${version}" --generate-notes 67 | 68 | deploy: 69 | sh release_notes.sh 70 | sh web_flavor_setup.sh $(flavor) 71 | 72 | flutter build web --release \ 73 | --target lib/main_$(flavor).dart \ 74 | --web-renderer canvaskit \ 75 | --dart-define-from-file /Users/ifeanyionuoha/skribla/$(flavor)_creds.json 76 | 77 | firebase deploy --only hosting --project=skribla-$(flavor) 78 | 79 | cd ios && bundle exec fastlane $(flavor) 80 | 81 | flutter build macos --config-only \ 82 | --flavor $(flavor) \ 83 | --target lib/main_$(flavor).dart \ 84 | --dart-define-from-file /Users/ifeanyionuoha/skribla/$(flavor)_creds.json 85 | 86 | cd android && bundle exec fastlane $(flavor) 87 | 88 | 89 | auth: 90 | firebase login --reauth 91 | 92 | .PHONY: clean gen loc splash format functions configure_dev configure_prod deeplink deploy_web patch_mobile deploy auth --------------------------------------------------------------------------------