├── .github └── FUNDING.yml ├── .gitignore ├── .metadata ├── .vscode └── launch.json ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── ic_launcher-playstore.png │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── swahilib │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets ├── fonts │ └── Trebuchet-MS.ttf ├── icons │ └── app_icon.png ├── images │ ├── app_icon.png │ └── empty.png └── sound │ └── slow_spring_board.mp3 ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── 1024.png │ │ │ ├── 120 1.png │ │ │ ├── 120.png │ │ │ ├── 152.png │ │ │ ├── 167.png │ │ │ ├── 180.png │ │ │ ├── 20.png │ │ │ ├── 29 1.png │ │ │ ├── 29.png │ │ │ ├── 40 1.png │ │ │ ├── 40 2.png │ │ │ ├── 40.png │ │ │ ├── 58 1.png │ │ │ ├── 58.png │ │ │ ├── 60.png │ │ │ ├── 76.png │ │ │ ├── 80 1.png │ │ │ ├── 80.png │ │ │ ├── 87.png │ │ │ └── Contents.json │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ ├── Runner-Bridging-Header.h │ └── Runner.entitlements └── RunnerTests │ └── RunnerTests.swift ├── keys.example ├── lib ├── app.dart ├── core │ ├── auth │ │ ├── auth_bloc.dart │ │ ├── auth_event.dart │ │ └── auth_state.dart │ ├── di │ │ └── injectable.dart │ ├── exceptions │ │ └── app_exception.dart │ ├── network │ │ └── api_util.dart │ └── utils │ │ ├── app_util.dart │ │ ├── constants │ │ ├── api_constants.dart │ │ ├── app_assets.dart │ │ ├── app_constants.dart │ │ └── pref_constants.dart │ │ ├── date_util.dart │ │ ├── env │ │ ├── environments.dart │ │ └── flavor_config.dart │ │ ├── input_util.dart │ │ ├── pagination_handler.dart │ │ └── web │ │ ├── app_configurator.dart │ │ └── app_configurator_web.dart ├── data │ ├── models │ │ ├── app_update.dart │ │ ├── basic_model.dart │ │ ├── history.dart │ │ ├── idiom.dart │ │ ├── models.dart │ │ ├── proverb.dart │ │ ├── saying.dart │ │ ├── search.dart │ │ └── word.dart │ ├── repository │ │ ├── auth_repository.dart │ │ ├── db │ │ │ ├── database_repository.dart │ │ │ └── database_repository_impl.dart │ │ └── pref_repository.dart │ └── sources │ │ ├── local │ │ ├── app_database.dart │ │ └── dao │ │ │ ├── histories_dao.dart │ │ │ ├── idioms_dao.dart │ │ │ ├── proverbs_dao.dart │ │ │ ├── sayings_dao.dart │ │ │ ├── searches_dao.dart │ │ │ └── words_dao.dart │ │ └── remote │ │ └── home_client.dart ├── domain │ ├── data_init_repository.dart │ └── home_repository.dart ├── main.dart └── presentation │ ├── blocs │ ├── datainit │ │ ├── data_init_bloc.dart │ │ ├── data_init_event.dart │ │ └── data_init_state.dart │ ├── home │ │ ├── home_bloc.dart │ │ ├── home_event.dart │ │ └── home_state.dart │ ├── settings │ │ ├── settings_bloc.dart │ │ ├── settings_event.dart │ │ └── settings_state.dart │ └── viewer │ │ ├── viewer_bloc.dart │ │ ├── viewer_event.dart │ │ └── viewer_state.dart │ ├── navigator │ ├── main_navigator.dart │ └── route_names.dart │ ├── screens │ ├── datainit │ │ ├── common │ │ │ └── data_init_utils.dart │ │ ├── data_init_screen.dart │ │ └── widgets │ │ │ └── data_init_widgets.dart │ ├── home │ │ ├── common │ │ │ └── home_utils.dart │ │ ├── home_screen.dart │ │ ├── search │ │ │ └── word_search.dart │ │ └── widgets │ │ │ ├── home_appbar.dart │ │ │ ├── home_body.dart │ │ │ ├── home_drawer.dart │ │ │ ├── lists │ │ │ ├── idioms_list.dart │ │ │ ├── proverbs_list.dart │ │ │ ├── sayings_list.dart │ │ │ └── words_lists.dart │ │ │ └── update_now.dart │ ├── settings │ │ └── settings_screen.dart │ ├── splash │ │ └── splash_screen.dart │ └── viewer │ │ ├── models │ │ └── presentor_model.dart │ │ ├── widgets │ │ └── word_details.dart │ │ ├── word_screen.dart │ │ └── word_view.dart │ ├── theme │ ├── bloc │ │ ├── theme_bloc.dart │ │ ├── theme_event.dart │ │ └── theme_state.dart │ ├── theme_colors.dart │ ├── theme_data.dart │ ├── theme_fonts.dart │ └── theme_styles.dart │ └── widgets │ ├── action │ ├── app_dialog.dart │ ├── app_nav_icon.dart │ ├── base_buttons.dart │ └── fab_widget.dart │ ├── features │ ├── custom_button.dart │ ├── custom_drop_down.dart │ ├── large_card.dart │ ├── small_card.dart │ └── tag_item.dart │ ├── general │ ├── labels.dart │ └── text_scale_factor.dart │ ├── inputs │ ├── form_input.dart │ ├── password_input.dart │ └── radio_input.dart │ ├── progress │ ├── custom_snackbar.dart │ ├── general_progress.dart │ └── skeleton.dart │ └── text_scale_factor.dart ├── linux ├── .gitignore ├── CMakeLists.txt ├── flutter │ ├── CMakeLists.txt │ ├── ephemeral │ │ └── .plugin_symlinks │ │ │ ├── app_links_linux │ │ │ ├── gtk │ │ │ ├── package_info_plus │ │ │ ├── path_provider_linux │ │ │ ├── sentry_flutter │ │ │ ├── share_plus │ │ │ ├── shared_preferences_linux │ │ │ └── url_launcher_linux │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake ├── main.cc ├── my_application.cc └── my_application.h ├── macos ├── .gitignore ├── Flutter │ ├── Flutter-Debug.xcconfig │ ├── Flutter-Release.xcconfig │ ├── GeneratedPluginRegistrant.swift │ └── ephemeral │ │ ├── Flutter-Generated.xcconfig │ │ └── flutter_export_environment.sh ├── Podfile ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── app_icon_1024.png │ │ │ ├── app_icon_128.png │ │ │ ├── app_icon_16.png │ │ │ ├── app_icon_256.png │ │ │ ├── app_icon_32.png │ │ │ ├── app_icon_512.png │ │ │ └── app_icon_64.png │ ├── Base.lproj │ │ └── MainMenu.xib │ ├── Configs │ │ ├── AppInfo.xcconfig │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── Warnings.xcconfig │ ├── DebugProfile.entitlements │ ├── Info.plist │ ├── MainFlutterWindow.swift │ └── Release.entitlements └── RunnerTests │ └── RunnerTests.swift ├── pubspec.lock ├── pubspec.yaml ├── test └── widget_test.dart ├── updates.json ├── web ├── favicon.png ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png ├── index.html └── manifest.json └── windows ├── .gitignore ├── CMakeLists.txt ├── flutter ├── CMakeLists.txt ├── ephemeral │ └── .plugin_symlinks │ │ ├── app_links │ │ ├── connectivity_plus │ │ ├── package_info_plus │ │ ├── path_provider_windows │ │ ├── sentry_flutter │ │ ├── share_plus │ │ ├── shared_preferences_windows │ │ └── url_launcher_windows ├── generated_plugin_registrant.cc ├── generated_plugin_registrant.h └── generated_plugins.cmake └── runner ├── CMakeLists.txt ├── Runner.rc ├── flutter_window.cpp ├── flutter_window.h ├── main.cpp ├── resource.h ├── resources └── app_icon.ico ├── runner.exe.manifest ├── utils.cpp ├── utils.h ├── win32_window.cpp └── win32_window.h /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [SiroDaves] 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .build/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | .swiftpm/ 13 | migrate_working_dir/ 14 | 15 | # IntelliJ related 16 | *.iml 17 | *.ipr 18 | *.iws 19 | .idea/ 20 | 21 | # The .vscode folder contains launch configuration and tasks you configure in 22 | # VS Code which you may wish to be included in version control, so this line 23 | # is commented out by default. 24 | #.vscode/ 25 | 26 | # Flutter/Dart/Pub related 27 | **/doc/api/ 28 | **/ios/Flutter/.last_build_id 29 | .dart_tool/ 30 | .flutter-plugins 31 | .flutter-plugins-dependencies 32 | .pub-cache/ 33 | .pub/ 34 | /build/ 35 | firebase_options.dart 36 | *.config.dart 37 | *.freezed.dart 38 | *.g.dart 39 | *.lock 40 | 41 | # Symbolication related 42 | app.*.symbols 43 | 44 | # Obfuscation related 45 | app.*.map.json 46 | 47 | # Android Studio will place build artifacts here 48 | /android/app/debug 49 | /android/app/profile 50 | /android/app/release 51 | google-services.json 52 | 53 | keys*.json 54 | 55 | # FVM Version Cache 56 | .fvm/ 57 | .fvmrc 58 | .vscode/settings.json 59 | .flutter* 60 | .metadata* -------------------------------------------------------------------------------- /.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: "603104015dd692ea3403755b55d07813d5cf8965" 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: 603104015dd692ea3403755b55d07813d5cf8965 17 | base_revision: 603104015dd692ea3403755b55d07813d5cf8965 18 | - platform: android 19 | create_revision: 603104015dd692ea3403755b55d07813d5cf8965 20 | base_revision: 603104015dd692ea3403755b55d07813d5cf8965 21 | - platform: ios 22 | create_revision: 603104015dd692ea3403755b55d07813d5cf8965 23 | base_revision: 603104015dd692ea3403755b55d07813d5cf8965 24 | - platform: linux 25 | create_revision: 603104015dd692ea3403755b55d07813d5cf8965 26 | base_revision: 603104015dd692ea3403755b55d07813d5cf8965 27 | - platform: macos 28 | create_revision: 603104015dd692ea3403755b55d07813d5cf8965 29 | base_revision: 603104015dd692ea3403755b55d07813d5cf8965 30 | - platform: web 31 | create_revision: 603104015dd692ea3403755b55d07813d5cf8965 32 | base_revision: 603104015dd692ea3403755b55d07813d5cf8965 33 | - platform: windows 34 | create_revision: 603104015dd692ea3403755b55d07813d5cf8965 35 | base_revision: 603104015dd692ea3403755b55d07813d5cf8965 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 | -------------------------------------------------------------------------------- /.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 | "name": "swa", 9 | "request": "launch", 10 | "type": "dart", 11 | "program": "lib/main.dart", 12 | "args": [ 13 | "production", 14 | "--target", 15 | "lib/main.dart", 16 | "--dart-define-from-file", 17 | "keys-prod.json" 18 | ], 19 | }, 20 | { 21 | "name": "swali", 22 | "request": "launch", 23 | "type": "dart", 24 | "program": "lib/main.dart", 25 | "args": [ 26 | "--flavor", 27 | "develop", 28 | "--target", 29 | "lib/main.dart", 30 | "--dart-define-from-file", 31 | "keys-prod.json" 32 | ], 33 | }, 34 | ] 35 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwahiLib - Kamusi ya Kiswahili 2 | The Kamusi ya Kiswahili app has been rebranded to SwahiLib. If you are looking for the old app you can find it in the [old-app branch](https://github.com/oyonde/SwahiLib/tree/old-app) 3 | 4 | 5 | 6 | Get it on Google Play 7 | 8 | 9 | 10 | Get it on AppStore 11 | 12 | 13 | Swahilib - Kamusi ya Kiswahili for Android, iOS 14 | 15 | ## Getting Started 16 | 17 | Follow this guide to set up and run the SwahiLib App: 18 | 19 | ## Getting Started 20 | 21 | Follow this guide to set up and run SwahiLib: 22 | 23 | ### Setting Up SwahiLib: 24 | 25 | 1. **Install Flutter and Dependencies:** Ensure Flutter is installed on your system. Download the Flutter SDK from the official website and set up your preferred IDE (e.g., Android Studio or Visual Studio Code) with the Flutter plugin. 26 | 27 | 2. **Clone the Repository:** Clone SwahiLib repository from GitHub using Git: 28 | 29 | ```bash 30 | git clone git@github.com:SiroDaves/SwahiLibApp.git 31 | ``` 32 | 33 | 3. **Install Packages:** Navigate to the project directory and run: 34 | 35 | ```bash 36 | flutter pub get 37 | ``` 38 | 39 | ### Running SwahiLib: 40 | 41 | 1. **Device Setup:** Connect an emulator or physical device to your development environment. Check connected devices: 42 | 43 | ```bash 44 | flutter devices 45 | ``` 46 | 47 | 2. **Update Dependencies:** 48 | 49 | ```bash 50 | flutter pub get 51 | ``` 52 | 53 | 3. **Update Code Generated Files:** 54 | 55 | ```bash 56 | dart run build_runner build --delete-conflicting-outputs 57 | ``` 58 | 59 | 4. **Update Localization Strings:** 60 | 61 | ```bash 62 | flutter gen-l10n 63 | ``` 64 | 5. **Running SwahiLib:** 65 | ```bash 66 | flutter run --flavor develop -t lib/main_dev.dart --dart-define-from-file keys-dev.json 67 | ``` 68 | 69 | ### Building SwahiLib 70 | 71 | 1. **Android:** 72 | 73 | - **Production (For Play Store):** 74 | 75 | ```bash 76 | flutter build appbundle --flavor production -t lib/main.dart --dart-define-from-file keys-prod.json --no-tree-shake-icons 77 | ``` 78 | 79 | 2. **iOS:** 80 | 81 | - **Production (For Play Store):** 82 | 83 | ```bash 84 | flutter build ipa -t lib/main.dart --dart-define-from-file keys-prod.json --no-tree-shake-icons 85 | ``` 86 | To upload to the App Store either: 87 | - Drag and drop the "build/ios/ipa/*.ipa" bundle into the Apple Transporter macOS app https://apps.apple.com/us/app/transporter/id1450874784 88 | - Run "xcrun altool --upload-app --type ios -f build/ios/ipa/*.ipa --apiKey your_api_key --apiIssuer your_issuer_id". 89 | See "man altool" for details about how to authenticate with the App Store Connect API key. 90 | 91 | --- 92 | 93 | Congratulations! You've successfully set up and run or built SwahiLib. Explore the codebase, make modifications, and contribute to creating a seamless experience for the users. Happy coding! -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at https://dart.dev/lints. 17 | # 18 | # Instead of disabling a lint rule for the entire project in the 19 | # section below, it can also be suppressed for a single line of code 20 | # or a specific dart file by using the `// ignore: name_of_lint` and 21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 22 | # producing the lint. 23 | rules: 24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 26 | 27 | # Additional information about this file can be found at 28 | # https://dart.dev/guides/language/analysis-options 29 | -------------------------------------------------------------------------------- /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/to/reference-keystore 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | id "dev.flutter.flutter-gradle-plugin" 5 | } 6 | 7 | def keystoreProperties = new Properties() 8 | def keystorePropertiesFile = rootProject.file('keystore/key.properties') 9 | if (keystorePropertiesFile.exists()) { 10 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 11 | } 12 | 13 | android { 14 | namespace = "com.swahilib" 15 | compileSdk = 34 16 | ndkVersion = flutter.ndkVersion 17 | 18 | compileOptions { 19 | sourceCompatibility = JavaVersion.VERSION_1_8 20 | targetCompatibility = JavaVersion.VERSION_1_8 21 | } 22 | 23 | kotlinOptions { 24 | jvmTarget = JavaVersion.VERSION_1_8 25 | } 26 | 27 | defaultConfig { 28 | applicationId "com.swahilib" 29 | minSdkVersion 24 30 | targetSdkVersion 34 31 | compileSdkVersion 34 32 | multiDexEnabled true 33 | versionCode 125 34 | versionName "1.0.125" 35 | } 36 | 37 | signingConfigs { 38 | release { 39 | keyAlias keystoreProperties['keyAlias'] 40 | keyPassword keystoreProperties['keyPassword'] 41 | storePassword keystoreProperties['storePassword'] 42 | storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null 43 | } 44 | } 45 | 46 | flavorDimensions "flavors" 47 | productFlavors { 48 | production { 49 | dimension "flavors" 50 | applicationIdSuffix "" 51 | manifestPlaceholders = [appName: "Swahilib"] 52 | } 53 | develop { 54 | dimension "flavors" 55 | applicationIdSuffix ".dev" 56 | manifestPlaceholders = [appName: "Dev Swahilib"] 57 | } 58 | } 59 | 60 | buildTypes { 61 | debug { 62 | signingConfig signingConfigs.debug 63 | } 64 | release { 65 | minifyEnabled true 66 | signingConfig signingConfigs.release 67 | proguardFiles getDefaultProguardFile('proguard-android.txt') 68 | } 69 | } 70 | } 71 | 72 | flutter { 73 | source = "../.." 74 | } 75 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 16 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /android/app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/android/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/swahilib/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.swahilib 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() 6 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Mar 01 22:43:49 EAT 2025 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /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 | id "org.jetbrains.kotlin.android" version "1.8.22" apply false 23 | } 24 | 25 | include ":app" 26 | -------------------------------------------------------------------------------- /assets/fonts/Trebuchet-MS.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/assets/fonts/Trebuchet-MS.ttf -------------------------------------------------------------------------------- /assets/icons/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/assets/icons/app_icon.png -------------------------------------------------------------------------------- /assets/images/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/assets/images/app_icon.png -------------------------------------------------------------------------------- /assets/images/empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/assets/images/empty.png -------------------------------------------------------------------------------- /assets/sound/slow_spring_board.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/assets/sound/slow_spring_board.mp3 -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '12.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 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/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 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import workmanager 4 | 5 | @UIApplicationMain 6 | 7 | @objc class AppDelegate: FlutterAppDelegate { 8 | 9 | override func application( 10 | _ application: UIApplication, 11 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 12 | 13 | GeneratedPluginRegistrant.register(with: self) 14 | UNUserNotificationCenter.current().delegate = self 15 | 16 | WorkmanagerPlugin.setPluginRegistrantCallback { registry in 17 | GeneratedPluginRegistrant.register(with: registry) 18 | } 19 | 20 | WorkmanagerPlugin.registerTask(withIdentifier: "com.swahilib.initTask") 21 | WorkmanagerPlugin.registerTask(withIdentifier: "com.swahilib.syncTask") 22 | 23 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 24 | 25 | } 26 | 27 | override func userNotificationCenter( 28 | _ center: UNUserNotificationCenter, 29 | willPresent notification: UNNotification, 30 | withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { 31 | completionHandler(.alert) 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/120 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/ios/Runner/Assets.xcassets/AppIcon.appiconset/120 1.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/29 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/ios/Runner/Assets.xcassets/AppIcon.appiconset/29 1.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/40 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/ios/Runner/Assets.xcassets/AppIcon.appiconset/40 1.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/40 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/ios/Runner/Assets.xcassets/AppIcon.appiconset/40 2.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/58 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/ios/Runner/Assets.xcassets/AppIcon.appiconset/58 1.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/80 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/ios/Runner/Assets.xcassets/AppIcon.appiconset/80 1.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "40.png", 5 | "idiom" : "iphone", 6 | "scale" : "2x", 7 | "size" : "20x20" 8 | }, 9 | { 10 | "filename" : "60.png", 11 | "idiom" : "iphone", 12 | "scale" : "3x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "29.png", 17 | "idiom" : "iphone", 18 | "scale" : "1x", 19 | "size" : "29x29" 20 | }, 21 | { 22 | "filename" : "58.png", 23 | "idiom" : "iphone", 24 | "scale" : "2x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "87.png", 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "29x29" 32 | }, 33 | { 34 | "filename" : "80.png", 35 | "idiom" : "iphone", 36 | "scale" : "2x", 37 | "size" : "40x40" 38 | }, 39 | { 40 | "filename" : "120.png", 41 | "idiom" : "iphone", 42 | "scale" : "3x", 43 | "size" : "40x40" 44 | }, 45 | { 46 | "filename" : "120 1.png", 47 | "idiom" : "iphone", 48 | "scale" : "2x", 49 | "size" : "60x60" 50 | }, 51 | { 52 | "filename" : "180.png", 53 | "idiom" : "iphone", 54 | "scale" : "3x", 55 | "size" : "60x60" 56 | }, 57 | { 58 | "filename" : "20.png", 59 | "idiom" : "ipad", 60 | "scale" : "1x", 61 | "size" : "20x20" 62 | }, 63 | { 64 | "filename" : "40 1.png", 65 | "idiom" : "ipad", 66 | "scale" : "2x", 67 | "size" : "20x20" 68 | }, 69 | { 70 | "filename" : "29 1.png", 71 | "idiom" : "ipad", 72 | "scale" : "1x", 73 | "size" : "29x29" 74 | }, 75 | { 76 | "filename" : "58 1.png", 77 | "idiom" : "ipad", 78 | "scale" : "2x", 79 | "size" : "29x29" 80 | }, 81 | { 82 | "filename" : "40 2.png", 83 | "idiom" : "ipad", 84 | "scale" : "1x", 85 | "size" : "40x40" 86 | }, 87 | { 88 | "filename" : "80 1.png", 89 | "idiom" : "ipad", 90 | "scale" : "2x", 91 | "size" : "40x40" 92 | }, 93 | { 94 | "filename" : "76.png", 95 | "idiom" : "ipad", 96 | "scale" : "1x", 97 | "size" : "76x76" 98 | }, 99 | { 100 | "filename" : "152.png", 101 | "idiom" : "ipad", 102 | "scale" : "2x", 103 | "size" : "76x76" 104 | }, 105 | { 106 | "filename" : "167.png", 107 | "idiom" : "ipad", 108 | "scale" : "2x", 109 | "size" : "83.5x83.5" 110 | }, 111 | { 112 | "filename" : "1024.png", 113 | "idiom" : "ios-marketing", 114 | "scale" : "1x", 115 | "size" : "1024x1024" 116 | } 117 | ], 118 | "info" : { 119 | "author" : "xcode", 120 | "version" : 1 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BGTaskSchedulerPermittedIdentifiers 6 | 7 | com.swahilib.initTask 8 | com.swahilib.syncTask 9 | 10 | CFBundleDevelopmentRegion 11 | $(DEVELOPMENT_LANGUAGE) 12 | CFBundleDisplayName 13 | Swahilib 14 | CFBundleExecutable 15 | $(EXECUTABLE_NAME) 16 | CFBundleIdentifier 17 | $(PRODUCT_BUNDLE_IDENTIFIER) 18 | CFBundleInfoDictionaryVersion 19 | 6.0 20 | CFBundleName 21 | SwahiLib 22 | CFBundlePackageType 23 | APPL 24 | CFBundleShortVersionString 25 | $(FLUTTER_BUILD_NAME) 26 | CFBundleSignature 27 | ???? 28 | CFBundleVersion 29 | $(FLUTTER_BUILD_NUMBER) 30 | LSRequiresIPhoneOS 31 | 32 | UIBackgroundModes 33 | 34 | fetch 35 | processing 36 | 37 | UILaunchStoryboardName 38 | LaunchScreen 39 | UIMainStoryboardFile 40 | Main 41 | UISupportedInterfaceOrientations 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationLandscapeLeft 45 | UIInterfaceOrientationLandscapeRight 46 | 47 | UISupportedInterfaceOrientations~ipad 48 | 49 | UIInterfaceOrientationPortrait 50 | UIInterfaceOrientationPortraitUpsideDown 51 | UIInterfaceOrientationLandscapeLeft 52 | UIInterfaceOrientationLandscapeRight 53 | 54 | CADisableMinimumFrameDurationOnPhone 55 | 56 | UIApplicationSupportsIndirectInputEvents 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /ios/Runner/Runner.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /keys.example: -------------------------------------------------------------------------------- 1 | { 2 | "supabaseUrl": "", 3 | "supabaseAnonKey": "", 4 | "sentryUrl": "" 5 | } -------------------------------------------------------------------------------- /lib/core/auth/auth_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | import 'package:freezed_annotation/freezed_annotation.dart'; 5 | 6 | import '../../data/repository/auth_repository.dart'; 7 | 8 | part 'auth_event.dart'; 9 | part 'auth_state.dart'; 10 | 11 | part 'auth_bloc.freezed.dart'; 12 | 13 | class AuthBloc extends Bloc { 14 | AuthBloc({ 15 | required AuthRepository autheRepo, 16 | }) : _autheRepository = autheRepo, 17 | super(XAuthState.unauthenticated()) { 18 | on(_onAuthStatusChanged); 19 | on(_onAuthLogoutRequested); 20 | _autheStatusSubscription = _autheRepository.status.listen( 21 | (status) => add(AuthStatusChanged(status)), 22 | ); 23 | } 24 | 25 | final AuthRepository _autheRepository; 26 | late StreamSubscription _autheStatusSubscription; 27 | 28 | @override 29 | Future close() { 30 | _autheStatusSubscription.cancel(); 31 | return super.close(); 32 | } 33 | 34 | Future _onAuthStatusChanged( 35 | AuthStatusChanged event, 36 | Emitter emit, 37 | ) async { 38 | switch (event.status) { 39 | case AuthStatus.unauthenticated: 40 | return emit(XAuthState.unauthenticated()); 41 | case AuthStatus.unverified: 42 | return emit(XAuthState.unverified()); 43 | case AuthStatus.authenticated: 44 | return emit(XAuthState.authenticated()); 45 | } 46 | } 47 | 48 | void _onAuthLogoutRequested( 49 | AuthLogoutRequested event, 50 | Emitter emit, 51 | ) { 52 | _autheRepository.logOut(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/core/auth/auth_event.dart: -------------------------------------------------------------------------------- 1 | part of 'auth_bloc.dart'; 2 | 3 | @freezed 4 | sealed class AuthEvent with _$AuthEvent { 5 | const factory AuthEvent.autheStatusChanged( 6 | AuthStatus status) = AuthStatusChanged; 7 | const factory AuthEvent.autheLogoutRequested() = 8 | AuthLogoutRequested; 9 | } 10 | -------------------------------------------------------------------------------- /lib/core/auth/auth_state.dart: -------------------------------------------------------------------------------- 1 | part of 'auth_bloc.dart'; 2 | 3 | @freezed 4 | class AuthState with _$AuthState { 5 | factory AuthState._({ 6 | @Default(AuthStatus.unauthenticated) AuthStatus status, 7 | }) = _AuthState; 8 | } 9 | 10 | extension XAuthState on AuthState { 11 | static AuthState unverified() => AuthState._( 12 | status: AuthStatus.unverified, 13 | ); 14 | static AuthState authenticated() => AuthState._( 15 | status: AuthStatus.authenticated, 16 | ); 17 | static AuthState unauthenticated() => AuthState._( 18 | status: AuthStatus.unauthenticated, 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /lib/core/di/injectable.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:get_it/get_it.dart'; 5 | import 'package:injectable/injectable.dart'; 6 | import 'package:shared_preferences/shared_preferences.dart'; 7 | 8 | import '../utils/app_util.dart'; 9 | import '../utils/constants/app_constants.dart'; 10 | import '../../data/sources/local/app_database.dart'; 11 | import '../../data/repository/db/database_repository_impl.dart'; 12 | import '../../data/repository/db/database_repository.dart'; 13 | import 'injectable.config.dart'; 14 | 15 | final getIt = GetIt.instance; 16 | 17 | @InjectableInit( 18 | initializerName: r'initGetIt', 19 | generateForDir: ['lib'], 20 | ) 21 | Future configureDependencies(String environment) async { 22 | logger('Using environment: $environment'); 23 | await getIt.initGetIt(environment: environment); 24 | await getIt.allReady(); 25 | } 26 | 27 | @module 28 | abstract class RegisterModule { 29 | @singleton 30 | @preResolve 31 | Future prefRepo() => SharedPreferences.getInstance(); 32 | 33 | @singleton 34 | @preResolve 35 | Future provideAppDatabase() async => await $FloorAppDatabase 36 | .databaseBuilder(await AppConstants.databaseFile) 37 | .build(); 38 | 39 | @lazySingleton 40 | DatabaseRepository provideDatabaseRepository(AppDatabase appDatabase) => 41 | DatabaseRepositoryImpl(appDatabase); 42 | } 43 | 44 | dynamic _parseAndDecode(String response) => jsonDecode(response); 45 | 46 | dynamic parseJson(String text) => 47 | compute(_parseAndDecode, text); 48 | -------------------------------------------------------------------------------- /lib/core/exceptions/app_exception.dart: -------------------------------------------------------------------------------- 1 | class AppException implements Exception { 2 | AppException([ 3 | this.message = "", 4 | this.debugString = "", 5 | ]); 6 | 7 | /// This MUST be a user friendly error message 8 | final String message; 9 | 10 | final String debugString; 11 | 12 | @override 13 | String toString() { 14 | return "$message $debugString"; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/core/network/api_util.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | import 'dart:io'; 4 | 5 | import 'package:http/http.dart' as http; 6 | 7 | import '../utils/app_util.dart'; 8 | import 'package:connectivity_plus/connectivity_plus.dart'; 9 | 10 | Future isConnectedToInternet() async { 11 | try { 12 | final connectivityResult = await Connectivity().checkConnectivity(); 13 | // ignore: unrelated_type_equality_checks 14 | if (connectivityResult == ConnectivityResult.none) return false; 15 | 16 | // Verify actual internet connection 17 | const exampleHost = 'example.com'; // Or use your server 18 | final result = await InternetAddress.lookup(exampleHost); 19 | return result.isNotEmpty && result[0].rawAddress.isNotEmpty; 20 | } on SocketException catch (_) { 21 | return false; 22 | } catch (_) { 23 | return false; // Handle other exceptions 24 | } 25 | } 26 | 27 | /// Http get request 28 | Future makeApiGetRequest( 29 | String siteUrl, 30 | Map headers, 31 | ) async { 32 | try { 33 | logger('Api Request [GET]: $siteUrl \nHeaders: ${json.encode(headers)}'); 34 | 35 | final response = await http 36 | .get( 37 | Uri.parse(siteUrl), 38 | headers: headers, 39 | ) 40 | .timeout( 41 | const Duration(seconds: 60), 42 | onTimeout: () { 43 | logger('Timeout occurred. Please try again later.'); 44 | return http.Response('Timeout occurred', 504); 45 | }, 46 | ); 47 | 48 | logger('Api Response: [${response.statusCode}] ${response.body}'); 49 | 50 | return response; 51 | } catch (e) { 52 | if (e is TimeoutException) { 53 | logger('Timeout occurred. Please try again later.'); 54 | return http.Response('Timeout occurred', 504); 55 | } else { 56 | logger('An error occurred during the HTTP request: $e'); 57 | return http.Response('Internal server error', 500); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/core/utils/constants/api_constants.dart: -------------------------------------------------------------------------------- 1 | class ApiConstants { 2 | static const String idioms = 'idioms'; 3 | static const String proverbs = 'proverbs'; 4 | static const String sayings = 'sayings'; 5 | static const String words = 'words'; 6 | } 7 | -------------------------------------------------------------------------------- /lib/core/utils/constants/app_assets.dart: -------------------------------------------------------------------------------- 1 | class AppAssets { 2 | AppAssets._(); 3 | 4 | static const assetsImages = 'assets/images'; 5 | static const assetsIcon = 'assets/icons'; 6 | 7 | static const String appIcon = '$assetsIcon/app_icon.png'; 8 | static const String appIconWhiteBg = '$assetsIcon/app_icon_white_bg.png'; 9 | static const String iconProject = '$assetsIcon/project.png'; 10 | static const String imgZeroState = '$assetsImages/empty.png'; 11 | } 12 | -------------------------------------------------------------------------------- /lib/core/utils/constants/pref_constants.dart: -------------------------------------------------------------------------------- 1 | class PrefConstants { 2 | static const appThemeKey = 'app_theme'; 3 | static const darkModeKey = 'dark_mode'; 4 | 5 | static const dataIsLoadedKey = 'data_loaded'; 6 | static const dateInstalledKey = 'date_installed'; 7 | } -------------------------------------------------------------------------------- /lib/core/utils/env/environments.dart: -------------------------------------------------------------------------------- 1 | import 'package:injectable/injectable.dart'; 2 | 3 | class Environments { 4 | static const String develop = 'dev'; 5 | static const String staging = 'stg'; 6 | static const String production = 'prod'; 7 | } 8 | 9 | const dummy = Environment(Environments.develop); 10 | -------------------------------------------------------------------------------- /lib/core/utils/env/flavor_config.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | enum Flavor { develop, staging, production } 4 | 5 | class FlavorValues { 6 | final bool logNetworkInfo; 7 | final bool showFullErrorMessages; 8 | 9 | const FlavorValues({ 10 | required this.logNetworkInfo, 11 | required this.showFullErrorMessages, 12 | }); 13 | } 14 | 15 | class FlavorConfig { 16 | double devicePixelRatio = 1; 17 | final Flavor flavor; 18 | final String name; 19 | final Color color; 20 | final FlavorValues values; 21 | ThemeMode themeMode; 22 | static FlavorConfig? _instance; 23 | 24 | factory FlavorConfig({ 25 | required Flavor flavor, 26 | required String name, 27 | required Color color, 28 | required FlavorValues values, 29 | ThemeMode themeMode = ThemeMode.system, 30 | }) => 31 | _instance = 32 | FlavorConfig._internal(flavor, name, color, values, themeMode); 33 | 34 | FlavorConfig._internal( 35 | this.flavor, this.name, this.color, this.values, this.themeMode); 36 | 37 | static FlavorConfig get instance => _instance!; 38 | 39 | static bool get hasInstance => _instance != null; 40 | 41 | static bool isProd() => 42 | _instance!.flavor == Flavor.production || _instance!.flavor == Flavor.staging; 43 | 44 | static bool isDev() => _instance!.flavor == Flavor.develop; 45 | } 46 | -------------------------------------------------------------------------------- /lib/core/utils/pagination_handler.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class PaginationHandler { 4 | late ScrollController scrollController; 5 | bool isLoading = false; 6 | bool stopLoading = false; 7 | int currentPage = 1; 8 | double boundaryOffset = 0.5; 9 | late Function loadAction; 10 | 11 | void init({ 12 | Function? initAction, 13 | required Function loadAction, 14 | }) { 15 | if (initAction != null) { 16 | initAction(); 17 | } 18 | this.loadAction = loadAction; 19 | scrollController = ScrollController()..addListener(scrollListener); 20 | } 21 | 22 | void dispose() { 23 | scrollController.removeListener(scrollListener); 24 | scrollController.dispose(); 25 | } 26 | 27 | void scrollListener() { 28 | if (!stopLoading) { 29 | //load more data 30 | if (scrollController.offset >= 31 | scrollController.position.maxScrollExtent * boundaryOffset && 32 | !isLoading) { 33 | isLoading = true; 34 | loadAction().then((shouldStop) { 35 | isLoading = false; 36 | currentPage++; 37 | boundaryOffset = 1 - 1 / (currentPage * 2); 38 | if (shouldStop == true) { 39 | stopLoading = true; 40 | } 41 | }); 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/core/utils/web/app_configurator.dart: -------------------------------------------------------------------------------- 1 | void configureWebApp() { 2 | //noop because this should only be used in web 3 | } 4 | -------------------------------------------------------------------------------- /lib/core/utils/web/app_configurator_web.dart: -------------------------------------------------------------------------------- 1 | // ignore: depend_on_referenced_packages 2 | import 'package:flutter_web_plugins/flutter_web_plugins.dart'; 3 | 4 | void configureWebApp() { 5 | setUrlStrategy(PathUrlStrategy()); 6 | } 7 | -------------------------------------------------------------------------------- /lib/data/models/app_update.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'app_update.g.dart'; 4 | 5 | @JsonSerializable() 6 | class AppUpdate { 7 | String ?version; 8 | String? title; 9 | String? description; 10 | AppLinks? appLinks; 11 | 12 | AppUpdate({ 13 | this.version, 14 | this.title, 15 | this.description, 16 | this.appLinks, 17 | }); 18 | 19 | factory AppUpdate.fromJson(Map json) => _$AppUpdateFromJson(json); 20 | 21 | Map toJson() => _$AppUpdateToJson(this); 22 | } 23 | 24 | @JsonSerializable() 25 | class AppLinks { 26 | String? android; 27 | String? ios; 28 | String? windows; 29 | String? macos; 30 | String? linux; 31 | 32 | AppLinks({ 33 | this.android, 34 | this.ios, 35 | this.windows, 36 | this.macos, 37 | this.linux, 38 | }); 39 | 40 | factory AppLinks.fromJson(Map json) => _$AppLinksFromJson(json); 41 | 42 | Map toJson() => _$AppLinksToJson(this); 43 | } 44 | -------------------------------------------------------------------------------- /lib/data/models/basic_model.dart: -------------------------------------------------------------------------------- 1 | class ResponseModel { 2 | int status; 3 | String feedback; 4 | dynamic response; 5 | 6 | ResponseModel({this.status = 0, this.feedback = '', this.response}); 7 | } 8 | 9 | class Selectable { 10 | bool isSelected = false; 11 | T data; 12 | Selectable(this.data, this.isSelected); 13 | } -------------------------------------------------------------------------------- /lib/data/models/history.dart: -------------------------------------------------------------------------------- 1 | import 'package:floor/floor.dart'; 2 | 3 | import '../../core/utils/constants/app_constants.dart'; 4 | 5 | @Entity(tableName: AppConstants.historiesTable) 6 | class History { 7 | @PrimaryKey() 8 | int? id; 9 | int? rid; 10 | int? item; 11 | String? type; 12 | String? createdAt; 13 | 14 | History({ 15 | this.id, 16 | this.rid, 17 | this.item, 18 | this.type, 19 | this.createdAt, 20 | }); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /lib/data/models/idiom.dart: -------------------------------------------------------------------------------- 1 | import 'package:floor/floor.dart'; 2 | import 'package:json_annotation/json_annotation.dart'; 3 | 4 | import '../../core/utils/constants/app_constants.dart'; 5 | 6 | part 'idiom.g.dart'; 7 | 8 | @Entity(tableName: AppConstants.idiomsTable) 9 | @JsonSerializable() 10 | class Idiom { 11 | @PrimaryKey() 12 | int? id; 13 | int? rid; 14 | String? title; 15 | String? meaning; 16 | int? views; 17 | int? likes; 18 | bool? liked; 19 | String? createdAt; 20 | String? updatedAt; 21 | 22 | Idiom({ 23 | this.id, 24 | this.rid, 25 | this.title, 26 | this.meaning, 27 | this.views, 28 | this.likes, 29 | this.liked, 30 | this.createdAt, 31 | this.updatedAt, 32 | }); 33 | 34 | factory Idiom.fromJson(Map json) => _$IdiomFromJson(json); 35 | 36 | Map toJson() => _$IdiomToJson(this); 37 | } 38 | -------------------------------------------------------------------------------- /lib/data/models/models.dart: -------------------------------------------------------------------------------- 1 | export 'app_update.dart'; 2 | export 'history.dart'; 3 | export 'idiom.dart'; 4 | export 'proverb.dart'; 5 | export 'saying.dart'; 6 | export 'search.dart'; 7 | export 'word.dart'; -------------------------------------------------------------------------------- /lib/data/models/proverb.dart: -------------------------------------------------------------------------------- 1 | import 'package:floor/floor.dart'; 2 | import 'package:json_annotation/json_annotation.dart'; 3 | 4 | import '../../core/utils/constants/app_constants.dart'; 5 | 6 | part 'proverb.g.dart'; 7 | 8 | @Entity(tableName: AppConstants.proverbsTable) 9 | @JsonSerializable() 10 | class Proverb { 11 | @PrimaryKey() 12 | int? id; 13 | int? rid; 14 | String? title; 15 | String? synonyms; 16 | String? meaning; 17 | int? views; 18 | int? likes; 19 | bool? liked; 20 | String? createdAt; 21 | String? updatedAt; 22 | 23 | Proverb({ 24 | this.id, 25 | this.rid, 26 | this.title, 27 | this.synonyms, 28 | this.meaning, 29 | this.views, 30 | this.likes, 31 | this.liked, 32 | this.createdAt, 33 | this.updatedAt, 34 | }); 35 | 36 | factory Proverb.fromJson(Map json) => _$ProverbFromJson(json); 37 | 38 | Map toJson() => _$ProverbToJson(this); 39 | } 40 | -------------------------------------------------------------------------------- /lib/data/models/saying.dart: -------------------------------------------------------------------------------- 1 | import 'package:floor/floor.dart'; 2 | import 'package:json_annotation/json_annotation.dart'; 3 | 4 | import '../../core/utils/constants/app_constants.dart'; 5 | 6 | part 'saying.g.dart'; 7 | 8 | @Entity(tableName: AppConstants.sayingsTable) 9 | @JsonSerializable() 10 | class Saying { 11 | @PrimaryKey() 12 | int? id; 13 | int? rid; 14 | String? title; 15 | String? meaning; 16 | int? views; 17 | int? likes; 18 | bool? liked; 19 | String? createdAt; 20 | String? updatedAt; 21 | 22 | Saying({ 23 | this.id, 24 | this.rid, 25 | this.title, 26 | this.meaning, 27 | this.views, 28 | this.likes, 29 | this.liked, 30 | this.createdAt, 31 | this.updatedAt, 32 | }); 33 | 34 | factory Saying.fromJson(Map json) => _$SayingFromJson(json); 35 | 36 | Map toJson() => _$SayingToJson(this); 37 | } 38 | -------------------------------------------------------------------------------- /lib/data/models/search.dart: -------------------------------------------------------------------------------- 1 | import 'package:floor/floor.dart'; 2 | 3 | import '../../core/utils/constants/app_constants.dart'; 4 | 5 | @Entity(tableName: AppConstants.searchesTable) 6 | class Search { 7 | @PrimaryKey() 8 | int? id; 9 | int? rid; 10 | String? title; 11 | String? createdAt; 12 | 13 | Search({ 14 | this.id, 15 | this.rid, 16 | this.title, 17 | this.createdAt, 18 | }); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /lib/data/models/word.dart: -------------------------------------------------------------------------------- 1 | import 'package:floor/floor.dart'; 2 | import 'package:json_annotation/json_annotation.dart'; 3 | 4 | import '../../core/utils/constants/app_constants.dart'; 5 | 6 | part 'word.g.dart'; 7 | 8 | @Entity(tableName: AppConstants.wordsTable) 9 | @JsonSerializable() 10 | class Word { 11 | @PrimaryKey() 12 | int? id; 13 | int? rid; 14 | String? title; 15 | String? synonyms; 16 | String? meaning; 17 | String? conjugation; 18 | int? views; 19 | int? likes; 20 | bool? liked; 21 | String? createdAt; 22 | String? updatedAt; 23 | 24 | Word({ 25 | this.id, 26 | this.rid, 27 | this.title, 28 | this.synonyms, 29 | this.meaning, 30 | this.conjugation, 31 | this.views, 32 | this.likes, 33 | this.liked, 34 | this.createdAt, 35 | this.updatedAt, 36 | }); 37 | 38 | factory Word.fromJson(Map json) => _$WordFromJson(json); 39 | 40 | Map toJson() => _$WordToJson(this); 41 | } 42 | -------------------------------------------------------------------------------- /lib/data/repository/auth_repository.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:injectable/injectable.dart'; 4 | 5 | import '../../core/utils/app_util.dart'; 6 | 7 | 8 | enum AuthStatus { authenticated, unauthenticated, unverified } 9 | 10 | @lazySingleton 11 | class AuthRepository { 12 | final _controller = StreamController(); 13 | 14 | Stream get status async* { 15 | await Future.delayed(const Duration(seconds: 3)); 16 | yield AuthStatus.authenticated; 17 | yield* _controller.stream; 18 | } 19 | 20 | Future signUp() async { 21 | logger("User auth status: Unverified"); 22 | await Future.delayed( 23 | const Duration(seconds: 2), 24 | () => _controller.add(AuthStatus.unverified), 25 | ); 26 | } 27 | 28 | Future logIn() async { 29 | logger("User auth status: Authnticated"); 30 | await Future.delayed( 31 | const Duration(seconds: 2), 32 | () => _controller.add(AuthStatus.authenticated), 33 | ); 34 | } 35 | 36 | Future logOut() async { 37 | logger("User auth status: Unauthenticated"); 38 | await Future.delayed( 39 | const Duration(seconds: 2), 40 | () => _controller.add(AuthStatus.unauthenticated), 41 | ); 42 | } 43 | 44 | void dispose() => _controller.close(); 45 | } 46 | -------------------------------------------------------------------------------- /lib/data/repository/db/database_repository.dart: -------------------------------------------------------------------------------- 1 | import '../../models/models.dart'; 2 | 3 | abstract class DatabaseRepository { 4 | Future> fetchHistories(); 5 | 6 | Future saveHistory(History history); 7 | 8 | Future removeAllHistories(); 9 | 10 | Future> fetchIdioms(); 11 | 12 | Future saveIdiom(Idiom idiom); 13 | 14 | Future saveIdioms(List idioms); 15 | 16 | Future removeAllIdioms(); 17 | 18 | Future> fetchProverbs(); 19 | 20 | Future saveProverb(Proverb proverb); 21 | 22 | Future saveProverbs(List proverbs); 23 | 24 | Future removeAllProverbs(); 25 | 26 | Future> fetchSayings(); 27 | 28 | Future saveSaying(Saying saying); 29 | 30 | Future saveSayings(List sayings); 31 | 32 | Future removeAllSayings(); 33 | 34 | Future> fetchSearches(); 35 | 36 | Future saveSearch(Search search); 37 | 38 | 39 | Future removeAllSearches(); 40 | 41 | Future> fetchWords(); 42 | 43 | Future saveWord(Word word); 44 | 45 | Future saveWords(List words); 46 | 47 | Future removeAllWords(); 48 | } 49 | -------------------------------------------------------------------------------- /lib/data/repository/pref_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:injectable/injectable.dart'; 3 | import 'package:shared_preferences/shared_preferences.dart'; 4 | 5 | import '../models/word.dart'; 6 | import '../../core/utils/constants/pref_constants.dart'; 7 | 8 | @singleton 9 | abstract class PrefRepository { 10 | @factoryMethod 11 | factory PrefRepository(SharedPreferences prefRepo) = PrefRepositoryImp; 12 | 13 | ThemeMode getThemeMode(); 14 | 15 | List? words = []; 16 | 17 | Future updateThemeMode(ThemeMode themeMode); 18 | 19 | bool getPrefBool(String settingsKey); 20 | int getPrefInt(String settingsKey); 21 | String getPrefString(String settingsKey); 22 | 23 | void setPrefBool(String settingsKey, bool settingsValue); 24 | void setPrefInt(String settingsKey, int settingsValue); 25 | void setPrefString(String settingsKey, String settingsValue); 26 | 27 | void clearData(); 28 | void removeKeyPair(String settingsKey); 29 | bool keyExists(String settingsKey); 30 | } 31 | 32 | class PrefRepositoryImp implements PrefRepository { 33 | final SharedPreferences sharedPrefs; 34 | 35 | PrefRepositoryImp(this.sharedPrefs); 36 | 37 | @override 38 | Future updateThemeMode(ThemeMode themeMode) async { 39 | await sharedPrefs.setString( 40 | PrefConstants.appThemeKey, 41 | themeMode.toString(), 42 | ); 43 | } 44 | 45 | @override 46 | ThemeMode getThemeMode() { 47 | switch (sharedPrefs.getString(PrefConstants.appThemeKey)) { 48 | case 'ThemeMode.light': 49 | return ThemeMode.light; 50 | 51 | case 'ThemeMode.dark': 52 | return ThemeMode.dark; 53 | 54 | default: 55 | return ThemeMode.system; 56 | } 57 | } 58 | 59 | @override 60 | void clearData() { 61 | sharedPrefs.remove(PrefConstants.appThemeKey); 62 | sharedPrefs.remove(PrefConstants.darkModeKey); 63 | } 64 | 65 | @override 66 | void removeKeyPair(String key) { 67 | sharedPrefs.remove(key); 68 | } 69 | 70 | @override 71 | bool keyExists(String key) { 72 | return sharedPrefs.containsKey(key); 73 | } 74 | 75 | @override 76 | bool getPrefBool(String settingsKey) { 77 | return sharedPrefs.getBool(settingsKey) ?? false; 78 | } 79 | 80 | @override 81 | int getPrefInt(String settingsKey) { 82 | return sharedPrefs.getInt(settingsKey) ?? 0; 83 | } 84 | 85 | @override 86 | String getPrefString(String settingsKey) { 87 | return sharedPrefs.getString(settingsKey) ?? ''; 88 | } 89 | 90 | @override 91 | void setPrefBool(String settingsKey, bool settingsValue) { 92 | if (!settingsValue) { 93 | sharedPrefs.remove(settingsKey); 94 | return; 95 | } 96 | sharedPrefs.setBool(settingsKey, settingsValue); 97 | } 98 | 99 | @override 100 | void setPrefInt(String settingsKey, int settingsValue) { 101 | if (settingsValue.isNegative) { 102 | sharedPrefs.remove(settingsKey); 103 | return; 104 | } 105 | sharedPrefs.setInt(settingsKey, settingsValue); 106 | } 107 | 108 | @override 109 | void setPrefString(String settingsKey, String settingsValue) { 110 | if (settingsValue.isEmpty) { 111 | sharedPrefs.remove(settingsKey); 112 | return; 113 | } 114 | sharedPrefs.setString(settingsKey, settingsValue); 115 | } 116 | 117 | @override 118 | List? words = []; 119 | } 120 | -------------------------------------------------------------------------------- /lib/data/sources/local/app_database.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:floor/floor.dart'; 4 | import 'package:sqflite/sqflite.dart' as sqflite; 5 | 6 | import '../../models/models.dart'; 7 | import 'dao/histories_dao.dart'; 8 | import 'dao/idioms_dao.dart'; 9 | import 'dao/proverbs_dao.dart'; 10 | import 'dao/sayings_dao.dart'; 11 | import 'dao/searches_dao.dart'; 12 | import 'dao/words_dao.dart'; 13 | 14 | part 'app_database.g.dart'; 15 | 16 | @Database( 17 | version: 2, 18 | entities: [History, Idiom, Proverb, Saying, Search, Word], 19 | ) 20 | abstract class AppDatabase extends FloorDatabase { 21 | HistoriesDao get historiesDao; 22 | IdiomsDao get idiomsDao; 23 | ProverbsDao get proverbsDao; 24 | SayingsDao get sayingsDao; 25 | SearchesDao get searchesDao; 26 | WordsDao get wordsDao; 27 | } 28 | 29 | Future buildInMemoryDatabase() { 30 | return $FloorAppDatabase.inMemoryDatabaseBuilder().build(); 31 | } 32 | -------------------------------------------------------------------------------- /lib/data/sources/local/dao/histories_dao.dart: -------------------------------------------------------------------------------- 1 | import 'package:floor/floor.dart'; 2 | 3 | import '../../../../core/utils/constants/app_constants.dart'; 4 | import '../../../models/history.dart'; 5 | 6 | @dao 7 | abstract class HistoriesDao { 8 | @Query('SELECT * FROM ${AppConstants.historiesTable} WHERE id = :id') 9 | Future findHistoryById(int id); 10 | 11 | @Query('SELECT * FROM ${AppConstants.historiesTable}') 12 | Future> fetchHistories(); 13 | 14 | @Insert(onConflict: OnConflictStrategy.replace) 15 | Future insertHistory(History history); 16 | 17 | @Query("DELETE FROM ${AppConstants.historiesTable}") 18 | Future deleteAllHistories(); 19 | } 20 | -------------------------------------------------------------------------------- /lib/data/sources/local/dao/idioms_dao.dart: -------------------------------------------------------------------------------- 1 | import 'package:floor/floor.dart'; 2 | 3 | import '../../../../core/utils/constants/app_constants.dart'; 4 | import '../../../models/idiom.dart'; 5 | 6 | @dao 7 | abstract class IdiomsDao { 8 | @Query('SELECT * FROM ${AppConstants.idiomsTable} WHERE id = :id') 9 | Future findIdiomById(int id); 10 | 11 | @Query('SELECT * FROM ${AppConstants.idiomsTable}') 12 | Future> fetchIdioms(); 13 | 14 | @Insert(onConflict: OnConflictStrategy.replace) 15 | Future insertIdiom(Idiom idiom); 16 | 17 | @Query("DELETE FROM ${AppConstants.idiomsTable}") 18 | Future deleteAllIdioms(); 19 | } 20 | -------------------------------------------------------------------------------- /lib/data/sources/local/dao/proverbs_dao.dart: -------------------------------------------------------------------------------- 1 | import 'package:floor/floor.dart'; 2 | 3 | import '../../../../core/utils/constants/app_constants.dart'; 4 | import '../../../models/proverb.dart'; 5 | 6 | @dao 7 | abstract class ProverbsDao { 8 | @Query('SELECT * FROM ${AppConstants.proverbsTable} WHERE id = :id') 9 | Future findProverbById(int id); 10 | 11 | @Query('SELECT * FROM ${AppConstants.proverbsTable}') 12 | Future> fetchProverbs(); 13 | 14 | @Insert(onConflict: OnConflictStrategy.replace) 15 | Future insertProverb(Proverb proverb); 16 | 17 | @Query("DELETE FROM ${AppConstants.proverbsTable}") 18 | Future deleteAllProverbs(); 19 | } 20 | -------------------------------------------------------------------------------- /lib/data/sources/local/dao/sayings_dao.dart: -------------------------------------------------------------------------------- 1 | import 'package:floor/floor.dart'; 2 | 3 | import '../../../../core/utils/constants/app_constants.dart'; 4 | import '../../../models/saying.dart'; 5 | 6 | @dao 7 | abstract class SayingsDao { 8 | @Query('SELECT * FROM ${AppConstants.sayingsTable} WHERE id = :id') 9 | Future findSayingById(int id); 10 | 11 | @Query('SELECT * FROM ${AppConstants.sayingsTable}') 12 | Future> fetchSayings(); 13 | 14 | @Insert(onConflict: OnConflictStrategy.replace) 15 | Future insertSaying(Saying saying); 16 | 17 | @Query("DELETE FROM ${AppConstants.sayingsTable}") 18 | Future deleteAllSayings(); 19 | } 20 | -------------------------------------------------------------------------------- /lib/data/sources/local/dao/searches_dao.dart: -------------------------------------------------------------------------------- 1 | import 'package:floor/floor.dart'; 2 | 3 | import '../../../../core/utils/constants/app_constants.dart'; 4 | import '../../../models/search.dart'; 5 | 6 | @dao 7 | abstract class SearchesDao { 8 | @Query('SELECT * FROM ${AppConstants.searchesTable} WHERE id = :id') 9 | Future findSearchById(int id); 10 | 11 | @Query('SELECT * FROM ${AppConstants.searchesTable}') 12 | Future> fetchSearches(); 13 | 14 | @Insert(onConflict: OnConflictStrategy.replace) 15 | Future insertSearch(Search search); 16 | 17 | @Query("DELETE FROM ${AppConstants.searchesTable}") 18 | Future deleteAllSearches(); 19 | } 20 | -------------------------------------------------------------------------------- /lib/data/sources/local/dao/words_dao.dart: -------------------------------------------------------------------------------- 1 | import 'package:floor/floor.dart'; 2 | 3 | import '../../../../core/utils/constants/app_constants.dart'; 4 | import '../../../models/word.dart'; 5 | 6 | @dao 7 | abstract class WordsDao { 8 | @Query('SELECT * FROM ${AppConstants.wordsTable} WHERE id = :id') 9 | Future findWordById(int id); 10 | 11 | @Query('SELECT * FROM ${AppConstants.wordsTable}') 12 | Future> fetchWords(); 13 | 14 | @Insert(onConflict: OnConflictStrategy.replace) 15 | Future insertWord(Word word); 16 | 17 | @Query("DELETE FROM ${AppConstants.wordsTable}") 18 | Future deleteAllWords(); 19 | } 20 | -------------------------------------------------------------------------------- /lib/data/sources/remote/home_client.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:http/http.dart'; 4 | 5 | import '../../../core/network/api_util.dart'; 6 | import '../../../core/utils/constants/app_constants.dart'; 7 | 8 | class HomeClient { 9 | Future fetchUpdateInfo() async { 10 | return await makeApiGetRequest( 11 | AppConstants.githubRepoUpdateJson, 12 | { 13 | 'Content-Type': 'application/json', 14 | }, 15 | ); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /lib/domain/data_init_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:supabase_flutter/supabase_flutter.dart'; 2 | 3 | import '../data/models/models.dart'; 4 | import '../core/utils/app_util.dart'; 5 | 6 | class DataInitRepository { 7 | final SupabaseClient supabase; 8 | 9 | DataInitRepository(this.supabase); 10 | 11 | Future> fetchIdioms() async { 12 | logger('Now fetching idioms'); 13 | try { 14 | final idiomsData = await Supabase.instance.client.from('idioms').select(); 15 | if (idiomsData.isNotEmpty) { 16 | final idioms = 17 | (idiomsData as List).map((item) => Idiom.fromJson(item)).toList(); 18 | logger('${idioms.length} idioms fetched'); 19 | return idioms; 20 | } 21 | } catch (e) { 22 | logger('Unable to fetch idioms: $e'); 23 | } 24 | return []; 25 | } 26 | 27 | Future> fetchProverbs() async { 28 | logger('Now fetching proverbs'); 29 | try { 30 | final proverbsData = await supabase.from('proverbs').select(); 31 | if (proverbsData.isNotEmpty) { 32 | final proverbs = (proverbsData as List) 33 | .map((item) => Proverb.fromJson(item)) 34 | .toList(); 35 | logger('${proverbs.length} proverbs fetched'); 36 | return proverbs; 37 | } 38 | } catch (e) { 39 | logger('Unable to fetch proverbs: $e'); 40 | } 41 | return []; 42 | } 43 | 44 | Future> fetchSayings() async { 45 | logger('Now fetching sayings'); 46 | try { 47 | final sayingsData = await supabase.from('sayings').select(); 48 | if (sayingsData.isNotEmpty) { 49 | final sayings = 50 | (sayingsData as List).map((item) => Saying.fromJson(item)).toList(); 51 | logger('${sayings.length} sayings fetched'); 52 | return sayings; 53 | } 54 | } catch (e) { 55 | logger('Unable to fetch sayings: $e'); 56 | } 57 | return []; 58 | } 59 | 60 | Future> fetchWords() async { 61 | logger('Now fetching words'); 62 | try { 63 | final wordsData = await supabase.from('words').select(); 64 | if (wordsData.isNotEmpty) { 65 | final words = 66 | (wordsData as List).map((item) => Word.fromJson(item)).toList(); 67 | logger('${words.length} words fetched'); 68 | return words; 69 | } 70 | } catch (e) { 71 | logger('Unable to fetch words: $e'); 72 | } 73 | return []; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/domain/home_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:http/http.dart'; 2 | 3 | import '../data/sources/remote/home_client.dart'; 4 | 5 | class HomeRepository { 6 | final _client = HomeClient(); 7 | 8 | Future fetchUpdateInfo() async => await _client.fetchUpdateInfo(); 9 | } 10 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:sentry_flutter/sentry_flutter.dart'; 3 | import 'package:supabase_flutter/supabase_flutter.dart'; 4 | 5 | import 'app.dart'; 6 | import 'core/utils/app_util.dart'; 7 | import 'core/utils/env/flavor_config.dart'; 8 | import 'core/utils/env/environments.dart'; 9 | import 'core/di/injectable.dart'; 10 | 11 | Future main() async { 12 | logger('Starting app from main.dart'); 13 | WidgetsFlutterBinding.ensureInitialized(); 14 | FlavorConfig( 15 | flavor: Flavor.production, 16 | name: 'PROD', 17 | color: Colors.transparent, 18 | values: const FlavorValues( 19 | logNetworkInfo: false, 20 | showFullErrorMessages: false, 21 | ), 22 | ); 23 | 24 | const supabaseUrl = String.fromEnvironment("supabaseUrl"); 25 | const supabaseAnonKey = String.fromEnvironment("supabaseAnonKey"); 26 | const sentryUrl = String.fromEnvironment("sentryUrl"); 27 | 28 | await Supabase.initialize(url: supabaseUrl, anonKey: supabaseAnonKey); 29 | logger('Supabase initialisation started: $supabaseUrl'); 30 | 31 | await configureDependencies(Environments.production); 32 | 33 | await SentryFlutter.init( 34 | (options) => options.dsn = sentryUrl, 35 | appRunner: () => runApp( 36 | const SentryWidget(child: MyApp()), 37 | ), 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /lib/presentation/blocs/datainit/data_init_event.dart: -------------------------------------------------------------------------------- 1 | part of 'data_init_bloc.dart'; 2 | 3 | @freezed 4 | sealed class DataInitEvent with _$DataInitEvent { 5 | const factory DataInitEvent.fetch() = FetchData; 6 | 7 | const factory DataInitEvent.save( 8 | List idioms, 9 | List proverbs, 10 | List sayings, 11 | List words, 12 | ) = SaveData; 13 | } 14 | -------------------------------------------------------------------------------- /lib/presentation/blocs/datainit/data_init_state.dart: -------------------------------------------------------------------------------- 1 | part of 'data_init_bloc.dart'; 2 | 3 | @freezed 4 | class DataInitState with _$DataInitState { 5 | const factory DataInitState.initial() = _DataInitState; 6 | 7 | const factory DataInitState.loaded() = DataInitLoadedState; 8 | 9 | const factory DataInitState.progress() = DataInitProgressState; 10 | 11 | const factory DataInitState.success() = DataInitSuccessState; 12 | 13 | const factory DataInitState.fetched( 14 | List idioms, 15 | List proverbs, 16 | List sayings, 17 | List words, 18 | ) = DataInitFetchedState; 19 | 20 | const factory DataInitState.saving(String feedback, int progress) = DataInitSavingState; 21 | 22 | const factory DataInitState.saved() = DataInitSavedState; 23 | 24 | const factory DataInitState.failure(String feedback) = DataInitFailureState; 25 | } 26 | -------------------------------------------------------------------------------- /lib/presentation/blocs/home/home_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | import 'package:freezed_annotation/freezed_annotation.dart'; 5 | import 'package:package_info_plus/package_info_plus.dart'; 6 | 7 | import '../../../data/models/models.dart'; 8 | import '../../../core/utils/app_util.dart'; 9 | import '../../../data/repository/db/database_repository.dart'; 10 | import '../../../core/di/injectable.dart'; 11 | import '../../../domain/home_repository.dart'; 12 | 13 | part 'home_event.dart'; 14 | part 'home_state.dart'; 15 | 16 | part 'home_bloc.freezed.dart'; 17 | 18 | class HomeBloc extends Bloc { 19 | HomeBloc() : super(const _HomeState()) { 20 | on(_onFetchData); 21 | on(_onCheckAppUpdates); 22 | } 23 | 24 | final _dbRepo = getIt(); 25 | final _homeRepo = HomeRepository(); 26 | 27 | void _onCheckAppUpdates( 28 | CheckAppUpdates event, 29 | Emitter emit, 30 | ) async { 31 | emit(const HomeProgressState()); 32 | AppUpdate? appUpdate; 33 | PackageInfo packageInfo = await PackageInfo.fromPlatform(); 34 | String currentVersion = packageInfo.version; 35 | var resp = await _homeRepo.fetchUpdateInfo(); 36 | 37 | try { 38 | switch (resp.statusCode) { 39 | case 200: 40 | appUpdate = AppUpdate.fromJson(jsonDecode(resp.body)); 41 | if (isNewerVersion(currentVersion, appUpdate.version!)) { 42 | logger("We need to upgrade from v$currentVersion to ${appUpdate.version}"); 43 | emit(HomeUpdateAppState(true, appUpdate)); 44 | } else { 45 | logger("No never version found, sticking to $currentVersion"); 46 | emit(HomeUpdateAppState(false, appUpdate)); 47 | } 48 | break; 49 | 50 | default: 51 | logger("Error finding new update info: ${resp.statusCode}"); 52 | emit(HomeUpdateAppState(false, appUpdate!)); 53 | break; 54 | } 55 | } catch (e) { 56 | logger("Error finding new update info: $e"); 57 | emit(HomeUpdateAppState(false, appUpdate!)); 58 | } 59 | } 60 | 61 | void _onFetchData( 62 | FetchData event, 63 | Emitter emit, 64 | ) async { 65 | emit(const HomeProgressState()); 66 | List idioms = []; 67 | List proverbs = []; 68 | List sayings = []; 69 | List words = []; 70 | 71 | try { 72 | idioms = await _dbRepo.fetchIdioms(); 73 | proverbs = await _dbRepo.fetchProverbs(); 74 | sayings = await _dbRepo.fetchSayings(); 75 | words = await _dbRepo.fetchWords(); 76 | emit(HomeFetchedDataState(idioms, proverbs, sayings, words)); 77 | } catch (e) { 78 | logger("Error log: $e"); 79 | emit(HomeFetchedDataState(idioms, proverbs, sayings, words)); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/presentation/blocs/home/home_event.dart: -------------------------------------------------------------------------------- 1 | part of 'home_bloc.dart'; 2 | 3 | @freezed 4 | sealed class HomeEvent with _$HomeEvent { 5 | const factory HomeEvent.fetchData() = FetchData; 6 | 7 | const factory HomeEvent.appUpdates() = CheckAppUpdates; 8 | 9 | } 10 | -------------------------------------------------------------------------------- /lib/presentation/blocs/home/home_state.dart: -------------------------------------------------------------------------------- 1 | part of 'home_bloc.dart'; 2 | 3 | @freezed 4 | class HomeState with _$HomeState { 5 | const factory HomeState.initial() = _HomeState; 6 | 7 | const factory HomeState.loaded() = HomeLoadedState; 8 | 9 | const factory HomeState.progress() = HomeProgressState; 10 | 11 | const factory HomeState.success() = HomeSuccessState; 12 | 13 | const factory HomeState.updateApp( 14 | bool hasNewUpdate, 15 | AppUpdate appUpdate, 16 | ) = HomeUpdateAppState; 17 | 18 | const factory HomeState.fetched( 19 | List idioms, 20 | List proverbs, 21 | List sayings, 22 | List words, 23 | ) = HomeFetchedDataState; 24 | 25 | const factory HomeState.filterWords( 26 | List words, 27 | ) = HomeFiteredWordsState; 28 | 29 | const factory HomeState.filterIdioms( 30 | List idioms, 31 | ) = HomeFiteredIdiomsState; 32 | 33 | const factory HomeState.filterSayings( 34 | List sayings, 35 | ) = HomeFiteredSayingsState; 36 | 37 | const factory HomeState.filterProverbs( 38 | List proverbs, 39 | ) = HomeFiteredProverbsState; 40 | 41 | const factory HomeState.failure(String feedback) = HomeFailureState; 42 | } 43 | -------------------------------------------------------------------------------- /lib/presentation/blocs/settings/settings_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | import 'package:freezed_annotation/freezed_annotation.dart'; 5 | 6 | part 'settings_event.dart'; 7 | part 'settings_state.dart'; 8 | 9 | part 'settings_bloc.freezed.dart'; 10 | 11 | class SettingsBloc extends Bloc { 12 | SettingsBloc() : super(const _SettingsState()) { 13 | on(_onInit); 14 | } 15 | 16 | Future _onInit( 17 | SettingsInit event, 18 | Emitter emit, 19 | ) async { 20 | emit(ProgressState()); 21 | 22 | emit(LoadedState()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/presentation/blocs/settings/settings_event.dart: -------------------------------------------------------------------------------- 1 | part of 'settings_bloc.dart'; 2 | 3 | @freezed 4 | sealed class SettingsEvent with _$SettingsEvent { 5 | const factory SettingsEvent.init() = SettingsInit; 6 | } 7 | -------------------------------------------------------------------------------- /lib/presentation/blocs/settings/settings_state.dart: -------------------------------------------------------------------------------- 1 | part of 'settings_bloc.dart'; 2 | 3 | @freezed 4 | class SettingsState with _$SettingsState { 5 | const factory SettingsState.initial() = _SettingsState; 6 | 7 | const factory SettingsState.loaded() = LoadedState; 8 | 9 | const factory SettingsState.progress() = ProgressState; 10 | 11 | const factory SettingsState.success() = SuccessState; 12 | 13 | const factory SettingsState.failure(String feedback) = FailureState; 14 | } 15 | -------------------------------------------------------------------------------- /lib/presentation/blocs/viewer/viewer_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | import 'package:freezed_annotation/freezed_annotation.dart'; 5 | 6 | import '../../../data/models/models.dart'; 7 | import '../../../core/utils/app_util.dart'; 8 | 9 | part 'viewer_event.dart'; 10 | part 'viewer_state.dart'; 11 | 12 | part 'viewer_bloc.freezed.dart'; 13 | 14 | class ViewerBloc extends Bloc { 15 | ViewerBloc() : super(const _ViewerState()) { 16 | on(_onLoadWord); 17 | on(_onLikeWord); 18 | on(_onSaveHistoryWord); 19 | } 20 | 21 | //final _dbRepo = getIt(); 22 | 23 | Future _onLoadWord( 24 | LoadWord event, 25 | Emitter emit, 26 | ) async { 27 | List synonyms = [], meanings = []; 28 | if (event.word.synonyms?.isNotEmpty ?? false) { 29 | synonyms = event.word.synonyms!.split(','); 30 | } 31 | 32 | meanings = cleanMeaning(event.word.meaning ?? "").split("|"); 33 | 34 | emit(ViewerLoadedState(meanings, synonyms)); 35 | } 36 | 37 | Future _onLikeWord( 38 | LikeWord event, 39 | Emitter emit, 40 | ) async { 41 | /*emit(const ViewerProgressState()); 42 | 43 | try { 44 | await _dbRepo.updateWord( 45 | event.song.rid, 46 | event.song.title, 47 | event.song.content, 48 | !event.song.liked, 49 | getIso8601Date(), 50 | ); 51 | } catch (e) { 52 | logger('Unable to update song: $e'); 53 | } 54 | 55 | emit(ViewerLikedState(!event.word.liked));*/ 56 | } 57 | 58 | Future _onSaveHistoryWord( 59 | SaveHistoryWord event, 60 | Emitter emit, 61 | ) async { 62 | /*await _dbRepo.saveHistory( 63 | History(song: event.song.rid, created: getCurrentDate()), 64 | ); 65 | emit(ViewerHistoryState());*/ 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/presentation/blocs/viewer/viewer_event.dart: -------------------------------------------------------------------------------- 1 | part of 'viewer_bloc.dart'; 2 | 3 | @freezed 4 | sealed class ViewerEvent with _$ViewerEvent { 5 | const factory ViewerEvent.loadWord(Word word) = LoadWord; 6 | 7 | const factory ViewerEvent.likeWord(Word word) = LikeWord; 8 | 9 | const factory ViewerEvent.historyWord(Word word) = SaveHistoryWord; 10 | } 11 | -------------------------------------------------------------------------------- /lib/presentation/blocs/viewer/viewer_state.dart: -------------------------------------------------------------------------------- 1 | part of 'viewer_bloc.dart'; 2 | 3 | @freezed 4 | class ViewerState with _$ViewerState { 5 | const factory ViewerState.initial() = _ViewerState; 6 | 7 | const factory ViewerState.loaded( 8 | List meanings, 9 | List synonyms, 10 | ) = ViewerLoadedState; 11 | 12 | const factory ViewerState.progress() = ViewerProgressState; 13 | 14 | const factory ViewerState.success() = ViewerSuccessState; 15 | 16 | const factory ViewerState.liked(bool liked) = ViewerLikedState; 17 | 18 | const factory ViewerState.history() = ViewerHistoryState; 19 | 20 | const factory ViewerState.failure(String feedback) = ViewerFailureState; 21 | } 22 | -------------------------------------------------------------------------------- /lib/presentation/navigator/main_navigator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../widgets/text_scale_factor.dart'; 4 | import '../screens/datainit/data_init_screen.dart'; 5 | import '../screens/home/home_screen.dart'; 6 | import '../screens/settings/settings_screen.dart'; 7 | import '../screens/splash/splash_screen.dart'; 8 | import 'route_names.dart'; 9 | 10 | class MainNavigator extends StatefulWidget { 11 | final Widget? child; 12 | 13 | const MainNavigator({this.child, super.key}); 14 | 15 | @override 16 | MainNavigatorState createState() => MainNavigatorState(); 17 | 18 | static MainNavigationMixin of(BuildContext context, 19 | {bool rootNavigator = false}) { 20 | final navigator = rootNavigator 21 | ? context.findRootAncestorStateOfType() 22 | : context.findAncestorStateOfType(); 23 | assert(() { 24 | if (navigator == null) { 25 | throw FlutterError( 26 | 'MainNavigation operation requested with a context that does not include a MainNavigation.\n' 27 | 'The context used to push or pop routes from the MainNavigation must be that of a ' 28 | 'widget that is a descendant of a MainNavigator widget.'); 29 | } 30 | return true; 31 | }()); 32 | return navigator!; 33 | } 34 | } 35 | 36 | class MainNavigatorState extends State with MainNavigationMixin { 37 | static final GlobalKey _navigationKey = 38 | GlobalKey(); 39 | static final List _navigatorObservers = []; 40 | 41 | static String get initialRoute => RouteNames.splash; 42 | 43 | static GlobalKey get navigationKey => _navigationKey; 44 | 45 | static List get navigatorObservers => _navigatorObservers; 46 | 47 | NavigatorState get navigator => _navigationKey.currentState!; 48 | 49 | @override 50 | Widget build(BuildContext context) { 51 | return TextScaleFactor( 52 | child: widget.child ?? const SizedBox.shrink(), 53 | ); 54 | } 55 | 56 | static Route? onGenerateRoute(RouteSettings settings) { 57 | final strippedPath = settings.name?.replaceFirst('/', ''); 58 | final Map routes = { 59 | '': (context) => const SplashScreen(), 60 | RouteNames.splash: (context) => const SplashScreen(), 61 | RouteNames.dataInit: (context) => const DataInitScreen(), 62 | RouteNames.home: (context) => const HomeScreen(), 63 | RouteNames.settings: (context) => const SettingsScreen(), 64 | }; 65 | 66 | defaultRoute(context) => const SplashScreen(); 67 | 68 | WidgetBuilder? getRouteBuilder(String routeName) { 69 | if (routes.containsKey(routeName)) { 70 | return routes[routeName]; 71 | } else { 72 | return defaultRoute; 73 | } 74 | } 75 | 76 | MaterialPageRoute createMaterialPageRoute( 77 | WidgetBuilder builder, RouteSettings settings) { 78 | return MaterialPageRoute( 79 | builder: builder, 80 | settings: settings, 81 | ); 82 | } 83 | 84 | WidgetBuilder? routeBuilder = getRouteBuilder(strippedPath!); 85 | if (routeBuilder != null) { 86 | return createMaterialPageRoute(routeBuilder, settings); 87 | } else { 88 | return null; 89 | } 90 | } 91 | } 92 | 93 | abstract class MainNavigation {} 94 | 95 | mixin MainNavigationMixin on State 96 | implements MainNavigation {} 97 | -------------------------------------------------------------------------------- /lib/presentation/navigator/route_names.dart: -------------------------------------------------------------------------------- 1 | class RouteNames { 2 | RouteNames._(); 3 | 4 | static const splash = 'splash'; 5 | static const init = 'init'; 6 | static const login = 'login'; 7 | static const signup = 'signup'; 8 | static const account = 'account'; 9 | static const dataInit = 'data-initialization'; 10 | static const home = 'home'; 11 | static const sessions = 'sessions'; 12 | static const speakers = 'speakers'; 13 | static const settings = 'settings'; 14 | } 15 | -------------------------------------------------------------------------------- /lib/presentation/screens/datainit/common/data_init_utils.dart: -------------------------------------------------------------------------------- 1 | String progressDesc(int value) { 2 | if (value <= 5) { 3 | return 'Inapakia maneno (words) 16,641 ...'; 4 | } else if (value <= 10) { 5 | return 'Inapakia maneno ...'; 6 | } else if (value <= 20) { 7 | return 'Kuwa mvumilivu ...'; 8 | } else if (value <= 35) { 9 | return 'Mvumilivu hula mbivu ...'; 10 | } else if (value <= 50) { 11 | return 'Kama una haraka, shuka ukimbie ...'; 12 | } else if (value <= 75) { 13 | return 'Asante kwa uvumilivu wako!'; 14 | } else if (value <= 85) { 15 | return 'Hatimaye'; 16 | } else if (value <= 90) { 17 | return 'Inapakia maneno ...'; 18 | } else if (value <= 95) { 19 | return 'Karibu tunamalizia'; 20 | } else { 21 | return 'Inapakia maneno (words)'; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/presentation/screens/datainit/data_init_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:percent_indicator/percent_indicator.dart'; 4 | 5 | import '../../widgets/progress/custom_snackbar.dart'; 6 | import '../../widgets/progress/general_progress.dart'; 7 | import '../../navigator/route_names.dart'; 8 | import '../../theme/theme_colors.dart'; 9 | import '../../blocs/datainit/data_init_bloc.dart'; 10 | 11 | class DataInitScreen extends StatefulWidget { 12 | const DataInitScreen({super.key}); 13 | 14 | @override 15 | State createState() => DataInitScreenState(); 16 | } 17 | 18 | class DataInitScreenState extends State { 19 | @override 20 | Widget build(BuildContext context) { 21 | return BlocProvider( 22 | create: (context) => DataInitBloc()..add(const FetchData()), 23 | child: BlocConsumer( 24 | listener: (context, state) { 25 | if (state is DataInitFetchedState) { 26 | context.read().add( 27 | SaveData( 28 | state.idioms, 29 | state.proverbs, 30 | state.sayings, 31 | state.words, 32 | ), 33 | ); 34 | } else if (state is DataInitFailureState) { 35 | CustomSnackbar.show(context, state.feedback); 36 | } else if (state is DataInitSavedState) { 37 | Navigator.pushNamedAndRemoveUntil( 38 | context, 39 | RouteNames.home, 40 | (route) => false, 41 | ); 42 | } 43 | }, 44 | builder: (context, state) { 45 | var bloc = context.read(); 46 | return Scaffold( 47 | body: state.maybeWhen( 48 | orElse: () => const SizedBox(), 49 | failure: (feedback) => EmptyState( 50 | title: 'Hamna chochote hapa', 51 | showRetry: true, 52 | onRetry: () => bloc.add(const FetchData()), 53 | ), 54 | progress: () => const LoadingProgress(title: "Inapakia data ..."), 55 | saving: (feedback, progress) => Column( 56 | children: [ 57 | const Spacer(), 58 | LoadingProgress(title: feedback), 59 | if (progress > 0.0) ...[ 60 | Container( 61 | height: 50, 62 | padding: const EdgeInsets.symmetric(horizontal: 20), 63 | child: LinearPercentIndicator( 64 | lineHeight: 50, 65 | percent: progress * .01, 66 | center: Text( 67 | "$progress %", 68 | style: const TextStyle( 69 | fontSize: 40, 70 | fontWeight: FontWeight.bold, 71 | ), 72 | ), 73 | barRadius: const Radius.circular(20), 74 | backgroundColor: ThemeColors.bgColorPrimary4(context), 75 | progressColor: Colors.green, 76 | ), 77 | ), 78 | ], 79 | const Spacer(), 80 | ], 81 | ), 82 | ), 83 | ); 84 | }, 85 | ), 86 | ); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /lib/presentation/screens/datainit/widgets/data_init_widgets.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:percent_indicator/percent_indicator.dart'; 3 | 4 | import '../../../theme/theme_colors.dart'; 5 | 6 | class BackgroundProgress extends StatelessWidget { 7 | final Size size; 8 | final int progress; 9 | 10 | const BackgroundProgress({ 11 | super.key, 12 | required this.size, 13 | required this.progress, 14 | }); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return RotatedBox( 19 | quarterTurns: 7, 20 | child: SizedBox( 21 | height: size.width, 22 | child: LinearPercentIndicator( 23 | percent: progress / 100, 24 | lineHeight: size.width, 25 | backgroundColor: Colors.black, 26 | progressColor: ThemeColors.primaryDark, 27 | ), 28 | ), 29 | ); 30 | } 31 | } 32 | 33 | class ForegroundProgress extends StatelessWidget { 34 | final int progress; 35 | final double radius; 36 | final String feedback; 37 | 38 | const ForegroundProgress({ 39 | super.key, 40 | required this.progress, 41 | required this.radius, 42 | required this.feedback, 43 | }); 44 | 45 | @override 46 | Widget build(BuildContext context) { 47 | return const Center( 48 | child:SizedBox() 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/presentation/screens/home/common/home_utils.dart: -------------------------------------------------------------------------------- 1 | import '../home_screen.dart'; 2 | 3 | void filterList(int page, String letter, HomeScreenState parent) { 4 | final lowercaseLetter = letter.toLowerCase(); 5 | 6 | switch (page) { 7 | case 0: 8 | parent.filteredWords = parent.words 9 | .where( 10 | (item) => item.title!.toLowerCase().startsWith(lowercaseLetter)) 11 | .toList(); 12 | break; 13 | case 1: 14 | parent.filteredIdioms = parent.idioms 15 | .where( 16 | (item) => item.title!.toLowerCase().startsWith(lowercaseLetter)) 17 | .toList(); 18 | break; 19 | case 2: 20 | parent.filteredSayings = parent.sayings 21 | .where( 22 | (item) => item.title!.toLowerCase().startsWith(lowercaseLetter)) 23 | .toList(); 24 | break; 25 | case 3: 26 | parent.filteredProverbs = parent.proverbs 27 | .where( 28 | (item) => item.title!.toLowerCase().startsWith(lowercaseLetter)) 29 | .toList(); 30 | break; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/presentation/screens/home/search/word_search.dart: -------------------------------------------------------------------------------- 1 | part of '../home_screen.dart'; 2 | 3 | class WordSearch extends SearchDelegate { 4 | final List words; 5 | 6 | WordSearch(BuildContext context, this.words); 7 | 8 | @override 9 | String get searchFieldLabel => "Tafuta Neno"; 10 | 11 | @override 12 | ThemeData appBarTheme(BuildContext context) { 13 | return Theme.of(context).brightness == Brightness.light 14 | ? AppTheme.darkTheme() 15 | : AppTheme.lightTheme(); 16 | } 17 | 18 | @override 19 | List buildActions(BuildContext context) { 20 | return [ 21 | IconButton( 22 | onPressed: () => query = '', 23 | icon: const Icon(Icons.clear, color: Colors.white), 24 | ), 25 | ]; 26 | } 27 | 28 | @override 29 | Widget buildLeading(BuildContext context) { 30 | return IconButton( 31 | onPressed: () => close(context, words), 32 | icon: const Icon(Icons.arrow_back, color: Colors.white), 33 | ); 34 | } 35 | 36 | @override 37 | Widget buildResults(BuildContext context) => _buildSearchResults(context); 38 | 39 | @override 40 | Widget buildSuggestions(BuildContext context) => _buildSearchResults(context); 41 | 42 | Widget _buildSearchResults(BuildContext context) { 43 | final filteredWords = _filterWords(query); 44 | 45 | if (filteredWords.isEmpty) { 46 | return const Center( 47 | child: Text( 48 | "Hakuna matokeo", 49 | style: TextStyle(fontSize: 18, color: Colors.grey), 50 | ), 51 | ); 52 | } 53 | 54 | return ListView.builder( 55 | padding: const EdgeInsets.all(10), 56 | itemCount: filteredWords.length, 57 | itemBuilder: (context, index) { 58 | final word = filteredWords[index]; 59 | return WordItem( 60 | word: word, 61 | onTap: () { 62 | Navigator.push( 63 | context, 64 | MaterialPageRoute( 65 | builder: (context) => WordScreen( 66 | word: word, 67 | words: words, 68 | ), 69 | ), 70 | ); 71 | }, 72 | ); 73 | }, 74 | ); 75 | } 76 | 77 | List _filterWords(String query) { 78 | if (query.trim().isEmpty) return words; 79 | 80 | final lowerQuery = query.trim().toLowerCase(); 81 | 82 | return words.where((word) { 83 | return (word.title?.toLowerCase().contains(lowerQuery) ?? false); 84 | }).toList(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /lib/presentation/screens/home/widgets/home_appbar.dart: -------------------------------------------------------------------------------- 1 | part of '../home_screen.dart'; 2 | 3 | class HomeAppBar extends StatefulWidget implements PreferredSizeWidget { 4 | final HomeScreenState parent; 5 | const HomeAppBar({super.key, required this.parent}); 6 | 7 | @override 8 | HomeAppBarState createState() => HomeAppBarState(); 9 | 10 | @override 11 | Size get preferredSize => const Size.fromHeight(100); 12 | } 13 | 14 | class HomeAppBarState extends State { 15 | late HomeScreenState parent; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | parent = widget.parent; 20 | var tabActions = PreferredSize( 21 | preferredSize: const Size.fromHeight(50), 22 | child: SizedBox( 23 | height: 50, 24 | child: ListView.builder( 25 | shrinkWrap: true, 26 | scrollDirection: Axis.horizontal, 27 | itemCount: AppConstants.filters.length, 28 | itemBuilder: (context, index) { 29 | return TabActionItem( 30 | index: index, 31 | isSelected: parent.setPage == index, 32 | onTapped: () => parent.onTabChanged(index), 33 | ); 34 | }, 35 | ), 36 | ), 37 | ); 38 | 39 | return AppBar( 40 | centerTitle: true, 41 | title: Text( 42 | '${AppConstants.appTitle} - ${AppConstants.appTitle1}', 43 | style: TextStyles.headingStyle1.bold.size(25), 44 | ), 45 | bottom: tabActions, 46 | ); 47 | } 48 | } 49 | 50 | class TabActionItem extends StatelessWidget { 51 | final int? index; 52 | final bool isSelected; 53 | final VoidCallback? onTapped; 54 | 55 | const TabActionItem({ 56 | super.key, 57 | required this.index, 58 | this.isSelected = false, 59 | this.onTapped, 60 | }); 61 | 62 | @override 63 | Widget build(BuildContext context) { 64 | return Padding( 65 | padding: const EdgeInsets.symmetric(horizontal: 5), 66 | child: RawMaterialButton( 67 | fillColor: isSelected 68 | ? ThemeColors.bgColorAccent2(context) 69 | : ThemeColors.bgColorPrimary5(context), 70 | highlightColor: Colors.white, 71 | focusElevation: 0, 72 | hoverColor: ThemeColors.primaryDark, 73 | hoverElevation: 1, 74 | highlightElevation: 0, 75 | elevation: 0, 76 | padding: EdgeInsets.zero, 77 | shape: const RoundedRectangleBorder( 78 | borderRadius: BorderRadius.only( 79 | topRight: Radius.circular(10), 80 | topLeft: Radius.circular(10), 81 | ), 82 | ), 83 | onPressed: onTapped, 84 | child: Text( 85 | AppConstants.filters[index!].toUpperCase(), 86 | style: TextStyles.buttonTextStyle.bold 87 | .textColor(isSelected ? ThemeColors.primary : Colors.white), 88 | ), 89 | ), 90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /lib/presentation/screens/home/widgets/home_body.dart: -------------------------------------------------------------------------------- 1 | part of '../home_screen.dart'; 2 | 3 | class HomeBody extends StatelessWidget { 4 | final HomeScreenState parent; 5 | const HomeBody({super.key, required this.parent}); 6 | 7 | @override 8 | Widget build(BuildContext context) { 9 | var filterLetters = SizedBox( 10 | width: 80, 11 | child: ListView.builder( 12 | physics: const BouncingScrollPhysics(), 13 | itemCount: AppConstants.letters.length, 14 | itemBuilder: (context, index) => LetterItem( 15 | index: index, 16 | onTap: () { 17 | parent.onLetterTapped(AppConstants.letters[index]); 18 | }, 19 | ), 20 | ), 21 | ); 22 | 23 | return SafeArea( 24 | child: Row( 25 | children: [ 26 | filterLetters, 27 | IndexedStack( 28 | index: parent.setPage, 29 | children: [ 30 | WordsList(parent: parent), 31 | IdiomsList(parent: parent), 32 | SayingsList(parent: parent), 33 | ProverbsList(parent: parent), 34 | ], 35 | ) 36 | ], 37 | ), 38 | ); 39 | } 40 | } 41 | 42 | class LetterItem extends StatelessWidget { 43 | final int? index; 44 | final VoidCallback? onTap; 45 | 46 | const LetterItem({super.key, required this.index, this.onTap}); 47 | 48 | @override 49 | Widget build(BuildContext context) { 50 | final letter = AppConstants.letters[index!]; 51 | return Padding( 52 | padding: const EdgeInsets.all(5), 53 | child: FloatingActionButton( 54 | heroTag: 'herufi_$letter', 55 | onPressed: onTap, 56 | child: Text( 57 | AppConstants.letters[index!], 58 | style: TextStyle( 59 | fontSize: 30, 60 | fontWeight: FontWeight.bold, 61 | color: ThemeColors.bgColorPrimary3(context), 62 | ), 63 | textAlign: TextAlign.center, 64 | ), 65 | ), 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/presentation/screens/home/widgets/lists/idioms_list.dart: -------------------------------------------------------------------------------- 1 | part of '../../home_screen.dart'; 2 | 3 | class IdiomsList extends StatelessWidget { 4 | final HomeScreenState parent; 5 | const IdiomsList({super.key, required this.parent}); 6 | 7 | @override 8 | Widget build(BuildContext context) { 9 | final Size size = MediaQuery.of(context).size; 10 | 11 | var emptyState = const EmptyState(title: 'Samahani hamna chochote hapa'); 12 | 13 | var listView = ListView.builder( 14 | padding: const EdgeInsets.only(right: 15), 15 | itemCount: parent.filteredIdioms.length, 16 | itemBuilder: (BuildContext context, int index) { 17 | final Idiom idiom = parent.filteredIdioms[index]; 18 | return IdiomItem(idiom: idiom); 19 | }, 20 | ); 21 | 22 | return SizedBox( 23 | width: size.width - 85, 24 | child: parent.filteredIdioms.isEmpty ? emptyState : listView, 25 | ); 26 | } 27 | } 28 | 29 | class IdiomItem extends StatelessWidget { 30 | final Idiom idiom; 31 | const IdiomItem({super.key, required this.idiom}); 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | final titleTxtStyle = 36 | TextStyles.headingStyle4.bold.size(22).textHeight(1.2); 37 | final bodyTxtStyle = TextStyles.bodyStyle1.size(18).textHeight(2); 38 | 39 | var meaning = cleanMeaning(idiom.meaning ?? ""); 40 | 41 | final contents = meaning.split("|"); 42 | var extra = contents.isNotEmpty ? contents[0].split(":") : []; 43 | meaning = extra.isNotEmpty ? " ~ ${extra[0].trim()}." : ""; 44 | 45 | if (contents.length > 1) { 46 | extra = contents[1].split(":"); 47 | meaning = "$meaning\n ~ ${extra[0].trim()}."; 48 | } 49 | 50 | return Card( 51 | elevation: 2, 52 | child: ListTile( 53 | title: Text(idiom.title ?? "", style: titleTxtStyle), 54 | subtitle: Column( 55 | crossAxisAlignment: CrossAxisAlignment.start, 56 | children: [ 57 | if (meaning.isNotEmpty) 58 | Text(meaning, style: bodyTxtStyle, maxLines: 2), 59 | const SizedBox(height: 10), 60 | ], 61 | ), 62 | ), 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/presentation/screens/home/widgets/lists/proverbs_list.dart: -------------------------------------------------------------------------------- 1 | part of '../../home_screen.dart'; 2 | 3 | class ProverbsList extends StatelessWidget { 4 | final HomeScreenState parent; 5 | const ProverbsList({super.key, required this.parent}); 6 | 7 | @override 8 | Widget build(BuildContext context) { 9 | final Size size = MediaQuery.of(context).size; 10 | 11 | var emptyState = const EmptyState(title: 'Samahani hamna chochote hapa'); 12 | 13 | var listView = ListView.builder( 14 | padding: const EdgeInsets.only(right: 15), 15 | itemCount: parent.filteredProverbs.length, 16 | itemBuilder: (BuildContext context, int index) { 17 | final Proverb proverb = parent.filteredProverbs[index]; 18 | return ProverbItem( 19 | proverb: proverb, 20 | onTap: () {}, //=> vm.openProverb(proverb), 21 | ); 22 | }, 23 | ); 24 | 25 | return SizedBox( 26 | width: size.width - 85, 27 | child: parent.filteredProverbs.isEmpty ? emptyState : listView, 28 | ); 29 | } 30 | } 31 | 32 | class ProverbItem extends StatelessWidget { 33 | final Proverb proverb; 34 | final VoidCallback? onTap; 35 | const ProverbItem({super.key, required this.proverb, this.onTap}); 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | final titleTxtStyle = 40 | TextStyles.headingStyle4.bold.size(22).textHeight(1.2); 41 | final bodyTxtStyle = TextStyles.bodyStyle1.size(18); 42 | 43 | var meaning = cleanMeaning(proverb.meaning ?? ""); 44 | 45 | final contents = meaning.split("|"); 46 | var extra = contents.isNotEmpty ? contents[0].split(":") : []; 47 | meaning = extra.isNotEmpty ? " ~ ${extra[0].trim()}." : ""; 48 | 49 | if (contents.length > 1) { 50 | extra = contents[1].split(":"); 51 | meaning = "$meaning\n ~ ${extra[0].trim()}."; 52 | } 53 | 54 | final synonyms = 55 | (proverb.synonyms ?? "").split(',').where((s) => s.isNotEmpty).toList(); 56 | final synonymsContainer = synonyms.isNotEmpty 57 | ? Row( 58 | children: [ 59 | Text( 60 | "${synonyms.length == 1 ? 'KISAWE' : 'VISAWE ${synonyms.length}'}:", 61 | style: bodyTxtStyle.bold, 62 | ), 63 | const SizedBox(width: 10), 64 | Flexible( 65 | child: SizedBox( 66 | height: 35, 67 | child: ListView.builder( 68 | scrollDirection: Axis.horizontal, 69 | itemCount: synonyms.length, 70 | itemBuilder: (BuildContext context, int index) { 71 | return TagView(tagText: synonyms[index], size: 20); 72 | }, 73 | ), 74 | ), 75 | ), 76 | ], 77 | ) 78 | : const SizedBox.shrink(); 79 | 80 | return Card( 81 | elevation: 2, 82 | child: ListTile( 83 | onTap: onTap, 84 | title: Text(proverb.title ?? "", style: titleTxtStyle), 85 | subtitle: Column( 86 | crossAxisAlignment: CrossAxisAlignment.start, 87 | children: [ 88 | if (meaning.isNotEmpty) 89 | Text(meaning, style: bodyTxtStyle, maxLines: 2), 90 | if (synonyms.isNotEmpty) const SizedBox(height: 5), 91 | if (synonyms.isNotEmpty) synonymsContainer, 92 | const SizedBox(height: 10), 93 | ], 94 | ), 95 | ), 96 | ); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /lib/presentation/screens/home/widgets/lists/sayings_list.dart: -------------------------------------------------------------------------------- 1 | part of '../../home_screen.dart'; 2 | 3 | class SayingsList extends StatelessWidget { 4 | final HomeScreenState parent; 5 | const SayingsList({super.key, required this.parent}); 6 | 7 | @override 8 | Widget build(BuildContext context) { 9 | final Size size = MediaQuery.of(context).size; 10 | 11 | var emptyState = const EmptyState(title: 'Samahani hamna chochote hapa'); 12 | 13 | var listView = ListView.builder( 14 | padding: const EdgeInsets.only(right: 15), 15 | itemCount: parent.filteredSayings.length, 16 | itemBuilder: (BuildContext context, int index) { 17 | final Saying saying = parent.filteredSayings[index]; 18 | return SayingItem(saying: saying); 19 | }, 20 | ); 21 | 22 | return SizedBox( 23 | width: size.width - 85, 24 | child: parent.filteredSayings.isEmpty ? emptyState : listView, 25 | ); 26 | } 27 | } 28 | 29 | class SayingItem extends StatelessWidget { 30 | final Saying saying; 31 | const SayingItem({super.key, required this.saying}); 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | final titleTxtStyle = TextStyles.headingStyle4.bold 36 | .size(22) 37 | .textHeight(1.2); 38 | final bodyTxtStyle = TextStyles.bodyStyle1.size(18); 39 | 40 | var meaning = cleanMeaning(saying.meaning ?? ""); 41 | 42 | final contents = meaning.split("|"); 43 | var extra = contents.isNotEmpty ? contents[0].split(":") : []; 44 | meaning = extra.isNotEmpty ? " ~ ${extra[0].trim()}." : ""; 45 | 46 | if (contents.length > 1) { 47 | extra = contents[1].split(":"); 48 | meaning = "$meaning\n ~ ${extra[0].trim()}."; 49 | } 50 | 51 | return Card( 52 | elevation: 2, 53 | child: ListTile( 54 | onTap: () {}, //=> vm.openSaying(saying), 55 | title: Text(saying.title ?? "", style: titleTxtStyle), 56 | subtitle: Column( 57 | crossAxisAlignment: CrossAxisAlignment.start, 58 | children: [ 59 | if (meaning.isNotEmpty) 60 | Text(meaning, style: bodyTxtStyle, maxLines: 2), 61 | const SizedBox(height: 10), 62 | ], 63 | ), 64 | ), 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/presentation/screens/home/widgets/update_now.dart: -------------------------------------------------------------------------------- 1 | part of '../home_screen.dart'; 2 | 3 | class UpdateNow extends StatelessWidget { 4 | final AppUpdate update; 5 | const UpdateNow({ 6 | super.key, 7 | required this.update, 8 | }); 9 | 10 | Future launchPlayStore() async { 11 | String appUrl = ''; 12 | if (Platform.isAndroid) { 13 | appUrl = update.appLinks!.android!; 14 | } else if (Platform.isIOS) { 15 | appUrl = update.appLinks!.ios!; 16 | } else if (Platform.isWindows) { 17 | appUrl = update.appLinks!.windows!; 18 | } else if (Platform.isMacOS) { 19 | appUrl = update.appLinks!.macos!; 20 | } else if (Platform.isLinux) { 21 | appUrl = update.appLinks!.linux!; 22 | } 23 | if (!await launchUrl(Uri.parse(appUrl))) { 24 | throw Exception('Could not launch updater'); 25 | } 26 | } 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | return Padding( 31 | padding: const EdgeInsets.all(20), 32 | child: Card( 33 | child: Padding( 34 | padding: const EdgeInsets.all(20), 35 | child: Column( 36 | mainAxisAlignment: MainAxisAlignment.center, 37 | children: [ 38 | [ 39 | const Padding( 40 | padding: EdgeInsets.all(10), 41 | child: Icon( 42 | Icons.system_update, 43 | size: 25, 44 | color: ThemeColors.primary, 45 | ), 46 | ), 47 | const Text( 48 | 'There\'s a new update', 49 | style: TextStyle( 50 | color: ThemeColors.primary, 51 | fontSize: 22, 52 | fontWeight: FontWeight.w600, 53 | ), 54 | ), 55 | ].toRow( 56 | mainAxisAlignment: MainAxisAlignment.center, 57 | ), 58 | const SizedBox(height: 10), 59 | Text( 60 | 'Version: ${update.version}', 61 | style: TextStyles.bodyStyle1.size(20).textHeight(1.2), 62 | ), 63 | const SizedBox(height: 30), 64 | const Text( 65 | "What's new:", 66 | style: TextStyle( 67 | color: ThemeColors.primary, 68 | fontSize: 25, 69 | fontWeight: FontWeight.w600, 70 | ), 71 | ), 72 | Text( 73 | update.description!, 74 | textAlign: TextAlign.center, 75 | style: TextStyles.bodyStyle1.size(18).textHeight(1.2), 76 | ), 77 | const SizedBox(height: 20), 78 | AppButton( 79 | label: 'UPDATE NOW', 80 | onPressed: launchPlayStore, 81 | bgColor: ThemeColors.primary, 82 | foreColor: Colors.white, 83 | hoverColor: Colors.red, 84 | padding: 85 | const EdgeInsets.symmetric(vertical: 10, horizontal: 30), 86 | borderRadius: const BorderRadius.all(Radius.circular(10)), 87 | ).center() 88 | ], 89 | ), 90 | ), 91 | ), 92 | ); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /lib/presentation/screens/splash/splash_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../core/utils/constants/app_constants.dart'; 4 | import '../../../core/utils/constants/app_assets.dart'; 5 | 6 | class SplashScreen extends StatelessWidget { 7 | const SplashScreen({super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Scaffold( 12 | //backgroundColor: ThemeColors.bgColorBW(context), 13 | body: Center( 14 | child: Column( 15 | mainAxisAlignment: MainAxisAlignment.center, 16 | children: [ 17 | const Spacer(), 18 | Image.asset(AppAssets.appIcon, height: 200, width: 200), 19 | const SizedBox(height: 10), 20 | const Text( 21 | AppConstants.appTitle, 22 | style: TextStyle( 23 | fontSize: 50, 24 | letterSpacing: 5, 25 | fontWeight: FontWeight.bold, 26 | ), 27 | ), 28 | const SizedBox(height: 5), 29 | const Spacer(), 30 | const Padding( 31 | padding: EdgeInsets.symmetric(horizontal: 50), 32 | child: Divider(thickness: 2, height: 50), 33 | ), 34 | const Text( 35 | AppConstants.appTagline, 36 | style: TextStyle( 37 | fontSize: 22, 38 | letterSpacing: 3, 39 | fontWeight: FontWeight.bold, 40 | ), 41 | ), 42 | const SizedBox(height: 20), 43 | ], 44 | ), 45 | ), 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/presentation/screens/viewer/models/presentor_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ViewerModel { 4 | String songBook; 5 | String songTitle; 6 | bool hasChorus; 7 | List songVerses; 8 | List widgetTabs; 9 | List widgetContent; 10 | 11 | ViewerModel({ 12 | this.songBook = '', 13 | this.songTitle = '', 14 | this.hasChorus = false, 15 | this.songVerses = const [], 16 | this.widgetTabs = const [], 17 | this.widgetContent = const [], 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /lib/presentation/screens/viewer/widgets/word_details.dart: -------------------------------------------------------------------------------- 1 | part of '../word_screen.dart'; 2 | 3 | class WordDetails extends StatelessWidget { 4 | final Word word; 5 | final List meanings; 6 | final List synonyms; 7 | 8 | const WordDetails({ 9 | super.key, 10 | required this.word, 11 | required this.meanings, 12 | required this.synonyms, 13 | }); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Column( 18 | crossAxisAlignment: CrossAxisAlignment.start, 19 | children: [ 20 | if (meanings.isNotEmpty) MeaningCard(meanings: meanings), 21 | if (synonyms.isNotEmpty) ...[ 22 | const SizedBox(height: 15), 23 | Text( 24 | 'Visawe', 25 | style: TextStyles.headingStyle2.bold 26 | .size(25) 27 | .italic 28 | .textColor(ThemeColors.primary), 29 | ), 30 | ListView.builder( 31 | shrinkWrap: true, 32 | physics: const NeverScrollableScrollPhysics(), 33 | itemCount: synonyms.length, 34 | itemBuilder: (context, index) => 35 | SynonymCard(synonym: synonyms[index]), 36 | ), 37 | ], 38 | ], 39 | ); 40 | } 41 | } 42 | 43 | class MeaningCard extends StatelessWidget { 44 | final List meanings; 45 | 46 | const MeaningCard({super.key, required this.meanings}); 47 | 48 | @override 49 | Widget build(BuildContext context) { 50 | final String formattedHtml = meanings.asMap().entries.map((entry) { 51 | final parts = entry.value.split(":"); 52 | return parts.length > 1 53 | ? "
  • ${parts.first}
    Mfano: ${parts.last}
  • " 54 | : "
  • ${parts.first}
  • "; 55 | }).join(); 56 | 57 | return Card( 58 | elevation: 2, 59 | color: Colors.white, 60 | child: Html( 61 | data: "
      $formattedHtml
    ", 62 | style: { 63 | "ol": Style( 64 | fontSize: FontSize(18), 65 | color: ThemeColors.primary, 66 | ), 67 | "li": Style( 68 | margin: Margins.only(bottom: 10), 69 | ), 70 | }, 71 | ), 72 | ); 73 | } 74 | } 75 | 76 | class SynonymCard extends StatelessWidget { 77 | final String synonym; 78 | 79 | const SynonymCard({super.key, required this.synonym}); 80 | 81 | @override 82 | Widget build(BuildContext context) { 83 | final TextStyle bodyTxtStyle = 84 | TextStyles.bodyStyle1.size(20).textColor(ThemeColors.primary); 85 | 86 | return Card( 87 | elevation: 2, 88 | color: Colors.white, 89 | child: Padding( 90 | padding: const EdgeInsets.all(10), 91 | child: Row( 92 | children: [ 93 | const Icon(Icons.arrow_circle_right, color: ThemeColors.primary), 94 | const SizedBox(width: 10), 95 | Expanded( 96 | child: Text(synonym, style: bodyTxtStyle), 97 | ), 98 | ], 99 | ), 100 | ), 101 | ); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /lib/presentation/screens/viewer/word_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:flutter_html/flutter_html.dart'; 4 | import 'package:textstyle_extensions/textstyle_extensions.dart'; 5 | 6 | import '../../../data/models/models.dart'; 7 | import '../../../core/utils/constants/app_constants.dart'; 8 | import '../../widgets/progress/custom_snackbar.dart'; 9 | import '../../widgets/progress/general_progress.dart'; 10 | import '../../theme/theme_colors.dart'; 11 | import '../../theme/theme_fonts.dart'; 12 | import '../../blocs/viewer/viewer_bloc.dart'; 13 | 14 | part 'widgets/word_details.dart'; 15 | part 'word_view.dart'; 16 | 17 | class WordScreen extends StatefulWidget { 18 | final Word word; 19 | final List words; 20 | 21 | const WordScreen({ 22 | super.key, 23 | required this.word, 24 | required this.words, 25 | }); 26 | 27 | @override 28 | State createState() => WordScreenState(); 29 | } 30 | 31 | class WordScreenState extends State { 32 | late Word word; 33 | IconData likeIcon = Icons.favorite_border; 34 | bool isLiked = false, likeChanged = false; 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | word = widget.word; 39 | 40 | return BlocProvider( 41 | create: (context) => ViewerBloc()..add(LoadWord(widget.word)), 42 | child: BlocConsumer( 43 | listener: (context, state) { 44 | if (state is ViewerFailureState) { 45 | CustomSnackbar.show(context, state.feedback); 46 | } else if (state is ViewerLikedState) { 47 | setState(() { 48 | //word.liked = !word.liked; 49 | likeChanged = true; 50 | }); 51 | if (state.liked) { 52 | CustomSnackbar.show( 53 | context, 54 | '${word.title} added to your likes', 55 | isSuccess: true, 56 | ); 57 | } else { 58 | CustomSnackbar.show( 59 | context, 60 | '${word.title} removed from your likes', 61 | ); 62 | } 63 | } 64 | }, 65 | builder: (context, state) { 66 | return Scaffold( 67 | appBar: AppBar( 68 | title: const Text( 69 | '${AppConstants.appTitle} - ${AppConstants.appTitle1}', 70 | ), 71 | ), 72 | body: state.maybeWhen( 73 | failure: (feedback) => EmptyState(title: feedback), 74 | orElse: () => const EmptyState(title: 'Hamna chochote hapa'), 75 | progress: () => const LoadingProgress(title: "Inapakia data ..."), 76 | loaded: (meanings, synonyms) { 77 | return WordView( 78 | word: word, 79 | meanings: meanings, 80 | synonyms: synonyms, 81 | ); 82 | }, 83 | ), 84 | ); 85 | }, 86 | ), 87 | ); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /lib/presentation/screens/viewer/word_view.dart: -------------------------------------------------------------------------------- 1 | part of 'word_screen.dart'; 2 | 3 | class WordView extends StatelessWidget { 4 | final Word word; 5 | final List meanings; 6 | final List synonyms; 7 | 8 | const WordView({ 9 | super.key, 10 | required this.word, 11 | required this.meanings, 12 | required this.synonyms, 13 | }); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return NestedScrollView( 18 | headerSliverBuilder: (context, innerBoxIsScrolled) { 19 | return [ 20 | SliverAppBar( 21 | expandedHeight: 100, 22 | floating: false, 23 | pinned: true, 24 | leading: const SizedBox(), 25 | flexibleSpace: FlexibleSpaceBar( 26 | centerTitle: false, 27 | title: Text( 28 | word.title ?? "", 29 | style: TextStyles.headingStyle2.bold 30 | .size(25) 31 | .textColor(Colors.white), 32 | ), 33 | ), 34 | ), 35 | ]; 36 | }, 37 | body: Container( 38 | constraints: const BoxConstraints.expand(), 39 | decoration: const BoxDecoration( 40 | gradient: LinearGradient( 41 | begin: Alignment.topLeft, 42 | end: Alignment.bottomRight, 43 | colors: [Colors.white, ThemeColors.accent, ThemeColors.primary], 44 | ), 45 | ), 46 | child: SingleChildScrollView( 47 | padding: const EdgeInsets.all(10), 48 | child: Column( 49 | crossAxisAlignment: CrossAxisAlignment.start, 50 | children: [ 51 | if (word.conjugation?.isNotEmpty ?? false) 52 | Html( 53 | data: "

    Mnyambuliko: ${word.conjugation}

    ", 54 | style: {"p": Style(fontSize: FontSize(20))}, 55 | ), 56 | WordDetails( 57 | word: word, 58 | meanings: meanings, 59 | synonyms: synonyms, 60 | ), 61 | ], 62 | ), 63 | ), 64 | ), 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/presentation/theme/bloc/theme_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | import 'package:freezed_annotation/freezed_annotation.dart'; 5 | 6 | part 'theme_event.dart'; 7 | part 'theme_state.dart'; 8 | 9 | part 'theme_bloc.freezed.dart'; 10 | 11 | class ThemeBloc extends Bloc { 12 | ThemeBloc() : super(ThemeMode.system) { 13 | on(_onThemeModeChanged); 14 | } 15 | 16 | Future _onThemeModeChanged( 17 | ThemeModeChanged event, 18 | Emitter emit, 19 | ) async { 20 | emit(event.themeMode); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/presentation/theme/bloc/theme_event.dart: -------------------------------------------------------------------------------- 1 | part of 'theme_bloc.dart'; 2 | 3 | @freezed 4 | sealed class ThemeEvent with _$ThemeEvent { 5 | const factory ThemeEvent.themeModeChanged( 6 | ThemeMode themeMode, 7 | ) = ThemeModeChanged; 8 | } 9 | -------------------------------------------------------------------------------- /lib/presentation/theme/bloc/theme_state.dart: -------------------------------------------------------------------------------- 1 | part of 'theme_bloc.dart'; 2 | 3 | enum ThemeState { system, light, dark } 4 | -------------------------------------------------------------------------------- /lib/presentation/theme/theme_data.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../data/repository/pref_repository.dart'; 4 | import '../../core/utils/app_util.dart'; 5 | import '../../core/di/injectable.dart'; 6 | import 'theme_colors.dart'; 7 | 8 | class AppTheme { 9 | AppTheme._(); 10 | 11 | static String currentTheme() { 12 | var prefRepo = getIt(); 13 | return getThemeModeString(prefRepo.getThemeMode()); 14 | } 15 | 16 | static ThemeData lightTheme() { 17 | return ThemeData( 18 | scaffoldBackgroundColor: ThemeColors.accent1, 19 | colorScheme: const ColorScheme( 20 | brightness: Brightness.light, 21 | primary: ThemeColors.primary, 22 | onPrimary: Colors.white, 23 | primaryContainer: ThemeColors.primary, 24 | secondary: ThemeColors.primary1, 25 | onSecondary: Colors.grey, 26 | secondaryContainer: ThemeColors.primary1, 27 | surface: Colors.white, 28 | onSurface: Colors.black, 29 | error: Colors.red, 30 | onError: Colors.white, 31 | ), 32 | appBarTheme: const AppBarTheme( 33 | backgroundColor: ThemeColors.primary, 34 | foregroundColor: ThemeColors.accent, 35 | elevation: 3, 36 | ), 37 | navigationBarTheme: const NavigationBarThemeData( 38 | backgroundColor: Colors.white, 39 | indicatorColor: ThemeColors.accent, 40 | elevation: 3, 41 | ), 42 | ); 43 | } 44 | 45 | static ThemeData darkTheme() { 46 | return ThemeData( 47 | scaffoldBackgroundColor: ThemeColors.primaryDark2, 48 | colorScheme: const ColorScheme( 49 | brightness: Brightness.light, 50 | primary: ThemeColors.primary2, 51 | onPrimary: ThemeColors.primaryDark1, 52 | primaryContainer: ThemeColors.primaryDark, 53 | secondary: ThemeColors.primaryDark1, 54 | onSecondary: ThemeColors.primaryDark1, 55 | secondaryContainer: ThemeColors.primaryDark1, 56 | surface: ThemeColors.primaryDark, 57 | onSurface: Colors.white, 58 | error: Colors.red, 59 | onError: Colors.white, 60 | ), 61 | appBarTheme: const AppBarTheme( 62 | backgroundColor: ThemeColors.primaryDark1, 63 | foregroundColor: Colors.white, 64 | elevation: 3, 65 | ), 66 | navigationBarTheme: const NavigationBarThemeData( 67 | backgroundColor: Colors.white, 68 | indicatorColor: ThemeColors.accent, 69 | elevation: 3, 70 | ), 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/presentation/theme/theme_fonts.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:textstyle_extensions/textstyle_extensions.dart'; 4 | 5 | class ThemeFonts { 6 | 7 | static const themeFonts = 'TrebuchetMS'; 8 | 9 | static const title = themeFonts; 10 | static const body = themeFonts; 11 | static const button = themeFonts; 12 | } 13 | 14 | class FontSizes { 15 | /// font size 10 16 | static const double s10 = 10; 17 | 18 | /// font size 12 19 | static const double s12 = 12; 20 | 21 | /// font size 14 22 | static const double s14 = 14; 23 | 24 | /// font size 16 25 | static const double s16 = 16; 26 | 27 | /// font size 18 28 | static const double s18 = 18; 29 | 30 | /// font size 20 31 | static const double s20 = 20; 32 | 33 | /// font size 22 34 | static const double s22 = 22; 35 | 36 | /// font size 25 37 | static const double s25 = 25; 38 | 39 | /// font size 30 40 | static const double s30 = 30; 41 | } 42 | 43 | class TextStyles { 44 | static const TextStyle trebuchetMS = TextStyle( 45 | fontFamily: ThemeFonts.themeFonts, 46 | fontWeight: FontWeight.w400, 47 | letterSpacing: 0, 48 | height: 1, 49 | fontFamilyFallback: [ 50 | ThemeFonts.themeFonts, 51 | ], 52 | ); 53 | 54 | static TextStyle get pageTitle1 => 55 | trebuchetMS.bold.size(FontSizes.s30).letterSpace(2); 56 | 57 | static TextStyle get headingStyle1 => 58 | trebuchetMS.bold.size(FontSizes.s22).letterSpace(.5); 59 | 60 | static TextStyle get headingStyle2 => 61 | trebuchetMS.bold.size(FontSizes.s20).letterSpace(.3); 62 | 63 | static TextStyle get headingStyle3 => trebuchetMS.bold.size(FontSizes.s18); 64 | 65 | static TextStyle get headingStyle4 => trebuchetMS.bold.size(FontSizes.s16); 66 | 67 | static TextStyle get headingStyle5 => trebuchetMS.bold.size(FontSizes.s14); 68 | 69 | static TextStyle get headingStyle6 => trebuchetMS.bold.size(FontSizes.s12); 70 | 71 | static TextStyle get bodyStyle1 => trebuchetMS.size(FontSizes.s16); 72 | 73 | static TextStyle get bodyStyle2 => trebuchetMS.size(FontSizes.s14); 74 | 75 | static TextStyle get bodyStyle3 => trebuchetMS.size(FontSizes.s12); 76 | 77 | static TextStyle get callOut => trebuchetMS.size(FontSizes.s20).letterSpace(1.5).bold; 78 | 79 | static TextStyle get callOutFocus => callOut.bold; 80 | 81 | static TextStyle get buttonTextStyle => 82 | trebuchetMS.bold.size(FontSizes.s14).letterSpace(1.75); 83 | 84 | static TextStyle get buttonSelected => 85 | trebuchetMS.size(FontSizes.s14).letterSpace(1.75); 86 | 87 | static TextStyle get footNote => trebuchetMS.bold.size(FontSizes.s10); 88 | 89 | static TextStyle get captionText => 90 | trebuchetMS.size(FontSizes.s10).letterSpace(.3); 91 | 92 | static TextStyle get titleStyle12 => 93 | const TextStyle(fontWeight: FontWeight.bold, fontSize: 12); 94 | static TextStyle get titleStyle14 => 95 | const TextStyle(fontWeight: FontWeight.bold, fontSize: 14); 96 | static TextStyle get titleStyle22 => 97 | const TextStyle(fontWeight: FontWeight.bold, fontSize: 22); 98 | static TextStyle get titleStyle20 => 99 | const TextStyle(fontWeight: FontWeight.bold, fontSize: 20); 100 | 101 | static TextStyle get labelStyle16 => 102 | const TextStyle(fontWeight: FontWeight.bold, fontSize: 16); 103 | } 104 | -------------------------------------------------------------------------------- /lib/presentation/theme/theme_styles.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class AppDurations { 5 | static const Duration fastest = Duration(seconds: 1); 6 | 7 | static const Duration fast = Duration(seconds: 2); 8 | 9 | static const Duration medium = Duration(seconds: 3); 10 | 11 | static const Duration slow = Duration(seconds: 5); 12 | } 13 | 14 | class Sizes { 15 | /// extra small size = 5 16 | static const double xs = 5; 17 | 18 | /// small size = 10 19 | static const double sm = 10; 20 | 21 | /// medium size = 15 22 | static const double m = 15; 23 | 24 | /// large size = 20 25 | static const double l = 20; 26 | 27 | /// extra large size = 30 28 | static const double xl = 30; 29 | 30 | /// extra extra large size = 50 31 | static const double xxl = 50; 32 | } 33 | 34 | class ThemeFonts { 35 | static const String poppins = "Poppins"; 36 | } 37 | 38 | class Insets { 39 | static double gutterScale = 1; 40 | 41 | static const double scale = 1; 42 | 43 | /// Dynamic insets, may get scaled with the device size 44 | static double mGutter = m * gutterScale; 45 | 46 | static double lGutter = l * gutterScale; 47 | 48 | static const double xs = 2 * scale; 49 | static const double sm = 6 * scale; 50 | static const double m = 12 * scale; 51 | static const double l = 24 * scale; 52 | static const double xl = 36 * scale; 53 | } 54 | 55 | class Shadows { 56 | static bool enabled = true; 57 | 58 | static double get mRadius => 8; 59 | 60 | static List m(Color color, [double opacity = 0]) { 61 | return enabled 62 | ? [ 63 | BoxShadow( 64 | color: color.withOpacity(opacity), 65 | blurRadius: mRadius, 66 | spreadRadius: mRadius / 2, 67 | offset: const Offset(1, 0), 68 | ), 69 | BoxShadow( 70 | color: color.withOpacity(opacity), 71 | blurRadius: mRadius / 2, 72 | spreadRadius: mRadius / 4, 73 | offset: const Offset(1, 0), 74 | ) 75 | ] 76 | : const []; 77 | } 78 | } 79 | 80 | class Corners { 81 | static const double btn = s5; 82 | 83 | static const double dialog = 12; 84 | 85 | /// Xs 86 | static const double s3 = 3; 87 | 88 | static BorderRadius get s3Border => BorderRadius.all(s3Radius); 89 | 90 | static Radius get s3Radius => const Radius.circular(s3); 91 | 92 | /// Small 93 | static const double s5 = 5; 94 | 95 | static BorderRadius get s5Border => BorderRadius.all(s5Radius); 96 | 97 | static Radius get s5Radius => const Radius.circular(s5); 98 | 99 | /// Medium 100 | static const double s8 = 8; 101 | 102 | static const BorderRadius s8Border = BorderRadius.all(s8Radius); 103 | 104 | static const Radius s8Radius = Radius.circular(s8); 105 | 106 | /// Large 107 | static const double s10 = 10; 108 | 109 | static BorderRadius get s10Border => BorderRadius.all(s10Radius); 110 | 111 | static Radius get s10Radius => const Radius.circular(s10); 112 | } 113 | -------------------------------------------------------------------------------- /lib/presentation/widgets/action/app_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | Future appDialog( 7 | BuildContext context, 8 | String title, 9 | String message, 10 | List actions, 11 | ) { 12 | if (Platform.isIOS) { 13 | return showCupertinoDialog( 14 | context: context, 15 | builder: (context) => CupertinoAlertDialog( 16 | title: Text(title), 17 | content: Text(message), 18 | actions: actions, 19 | ), 20 | ); 21 | } else { 22 | return showDialog( 23 | context: context, 24 | builder: (context) => AlertDialog( 25 | title: Text(title), 26 | content: Text(message), 27 | actions: actions, 28 | ), 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/presentation/widgets/action/app_nav_icon.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AppNavIcon extends StatelessWidget { 4 | const AppNavIcon( 5 | this.icon, { 6 | super.key, 7 | this.color, 8 | this.height, 9 | this.width, 10 | }); 11 | final IconData icon; 12 | final Color? color; 13 | final double? height; 14 | final double? width; 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return Icon(icon, color: color); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/presentation/widgets/features/custom_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CustomButton extends StatelessWidget { 4 | final VoidCallback onTap; 5 | final Color? buttonColor; 6 | final double? borderRadius; 7 | final Color? textColor; 8 | final String buttonText; 9 | final double? verticalPadding; 10 | final double? horizontalPadding; 11 | final Color? borderColor; 12 | final double? fontSize; 13 | 14 | const CustomButton({ 15 | super.key, 16 | required this.onTap, 17 | this.buttonColor, 18 | this.borderRadius, 19 | this.textColor, 20 | required this.buttonText, 21 | this.verticalPadding, 22 | this.horizontalPadding, 23 | this.borderColor, 24 | this.fontSize, 25 | }); 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return Container( 30 | decoration: BoxDecoration( 31 | color: buttonColor ?? Colors.white, 32 | borderRadius: BorderRadius.circular(borderRadius ?? 0), 33 | border: Border.all(color: borderColor ?? Colors.transparent, width: 1), 34 | ), 35 | child: Material( 36 | color: Colors.transparent, 37 | child: InkWell( 38 | onTap: onTap, 39 | child: Container( 40 | padding: EdgeInsets.symmetric( 41 | horizontal: horizontalPadding ?? 10, 42 | vertical: verticalPadding ?? 10, 43 | ), 44 | child: Center( 45 | child: Text( 46 | buttonText, 47 | style: TextStyle( 48 | color: textColor, 49 | fontSize: fontSize ?? 15, 50 | fontWeight: FontWeight.bold, 51 | ), 52 | ), 53 | ), 54 | ), 55 | ), 56 | ), 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/presentation/widgets/features/custom_drop_down.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CustomDropdownItem extends StatelessWidget { 4 | final String value; 5 | final String selectedOption; 6 | 7 | const CustomDropdownItem({ 8 | super.key, 9 | required this.value, 10 | required this.selectedOption, 11 | }); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | bool isSelected = value == selectedOption; 16 | return Container( 17 | padding: const EdgeInsets.all(10), 18 | color: isSelected ? Colors.blue : Colors.white, 19 | child: Text( 20 | value, 21 | style: TextStyle( 22 | fontSize: 16, 23 | color: isSelected ? Colors.white : Colors.black, 24 | ), 25 | ), 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/presentation/widgets/features/large_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../theme/theme_colors.dart'; 4 | 5 | class LargeCard extends StatelessWidget { 6 | final String cardName; 7 | final String image; 8 | final void Function()? onTap; 9 | const LargeCard({ 10 | super.key, 11 | required this.cardName, 12 | required this.image, 13 | this.onTap, 14 | }); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return GestureDetector( 19 | onTap: onTap, 20 | child: Padding( 21 | padding: const EdgeInsets.only(left: 14, right: 14, top: 14), 22 | child: Row( 23 | children: [ 24 | Container( 25 | decoration: BoxDecoration( 26 | border: Border.all( 27 | color: ThemeColors.blackText, 28 | ), 29 | borderRadius: const BorderRadius.all( 30 | Radius.circular(10), 31 | ), 32 | ), 33 | width: MediaQuery.of(context).size.width - 28, 34 | height: 230, 35 | child: Card( 36 | elevation: 0, 37 | shape: const RoundedRectangleBorder( 38 | borderRadius: BorderRadius.all(Radius.circular(10))), 39 | child: Column( 40 | mainAxisAlignment: MainAxisAlignment.center, 41 | crossAxisAlignment: CrossAxisAlignment.center, 42 | children: [ 43 | Image.asset("assets/images/messages.png"), 44 | Text( 45 | cardName, 46 | textAlign: TextAlign.center, 47 | style: const TextStyle( 48 | fontSize: 15, 49 | color: ThemeColors.blackText, 50 | ), 51 | ) 52 | ], 53 | ), 54 | ), 55 | ), 56 | ], 57 | ), 58 | ), 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/presentation/widgets/features/tag_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:textstyle_extensions/textstyle_extensions.dart'; 3 | 4 | import '../../theme/theme_colors.dart'; 5 | import '../../theme/theme_fonts.dart'; 6 | import '../../theme/theme_styles.dart'; 7 | 8 | class TagItem extends StatelessWidget { 9 | final String tagText; 10 | 11 | const TagItem({ 12 | super.key, 13 | required this.tagText, 14 | }); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | try { 19 | if (tagText.isNotEmpty) { 20 | return Container( 21 | padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), 22 | margin: const EdgeInsets.only(right: Sizes.xs), 23 | decoration: BoxDecoration( 24 | color: Theme.of(context).brightness == Brightness.light 25 | ? ThemeColors.primaryDark1 26 | : ThemeColors.primary, 27 | borderRadius: const BorderRadius.all(Radius.circular(5)), 28 | ), 29 | child: Text(tagText, 30 | style: TextStyles.headingStyle5.textColor(Colors.white)), 31 | ); 32 | } else { 33 | return const SizedBox.shrink(); 34 | } 35 | } on Exception { 36 | return const SizedBox.shrink(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/presentation/widgets/general/labels.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../theme/theme_colors.dart'; 4 | 5 | class TagView extends StatelessWidget { 6 | final String tagText; 7 | final double size; 8 | 9 | const TagView({super.key, required this.tagText, required this.size}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | try { 14 | if (tagText.isNotEmpty) { 15 | return Container( 16 | padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), 17 | margin: const EdgeInsets.only(right: 5), 18 | decoration: const BoxDecoration( 19 | color: ThemeColors.primary, 20 | borderRadius: BorderRadius.only( 21 | topRight: Radius.circular(10), 22 | bottomLeft: Radius.circular(10), 23 | ), 24 | boxShadow: [BoxShadow(blurRadius: 1)], 25 | ), 26 | child: Text( 27 | tagText, 28 | style: TextStyle( 29 | fontSize: size * 0.8, 30 | color: Colors.white, 31 | ), 32 | ), 33 | ); 34 | } else { 35 | return Container(); 36 | } 37 | } on Exception { 38 | return Container(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/presentation/widgets/general/text_scale_factor.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../core/utils/env/flavor_config.dart'; 4 | 5 | class TextScaleFactor extends StatelessWidget { 6 | final Widget child; 7 | 8 | const TextScaleFactor({ 9 | required this.child, 10 | Key? key, 11 | }) : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | final mediaQuery = MediaQuery.of(context); 16 | FlavorConfig.instance.devicePixelRatio = mediaQuery.devicePixelRatio; 17 | return MediaQuery( 18 | data: mediaQuery.copyWith(textScaler: const TextScaler.linear(1)), 19 | child: child, 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/presentation/widgets/inputs/form_input.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | 4 | import '../../theme/theme_colors.dart'; 5 | 6 | class FormInput extends StatefulWidget { 7 | final String? iLabel; 8 | final TextEditingController? iController; 9 | final TextInputType iType; 10 | final AutovalidateMode? validationMode; 11 | final bool? isReadOnly; 12 | final bool? isLight; 13 | final bool? isEnabled; 14 | final bool? executeValueChange; 15 | final FormFieldValidator? iValidator; 16 | final Function(String)? onChanged; 17 | final Function()? onValueChanged; 18 | final Function()? onTap; 19 | final Widget? prefix; 20 | final bool? isActive; 21 | final bool? isMultiline; 22 | final double bdRadius; 23 | final int maxInput; 24 | 25 | const FormInput({ 26 | Key? key, 27 | this.iLabel = "", 28 | this.iType = TextInputType.text, 29 | this.iController, 30 | this.validationMode = AutovalidateMode.disabled, 31 | this.isReadOnly = false, 32 | this.isLight = false, 33 | this.isEnabled = true, 34 | this.executeValueChange = false, 35 | this.iValidator, 36 | this.onChanged, 37 | this.onValueChanged, 38 | this.onTap, 39 | this.prefix, 40 | this.isActive = true, 41 | this.isMultiline = false, 42 | this.bdRadius = 5, 43 | this.maxInput = 20000, 44 | }) : super(key: key); 45 | 46 | @override 47 | FormInputState createState() => FormInputState(); 48 | } 49 | 50 | class FormInputState extends State { 51 | @override 52 | Widget build(BuildContext context) { 53 | Color foreColor = widget.isLight! ? Colors.white : ThemeColors.primary; 54 | 55 | return Container( 56 | margin: const EdgeInsets.all(10), 57 | child: TextFormField( 58 | controller: widget.iController, 59 | keyboardType: widget.iType, 60 | autovalidateMode: widget.validationMode, 61 | validator: widget.iValidator, 62 | minLines: widget.isMultiline! ? 50 : 1, 63 | maxLines: widget.isMultiline! ? null : 1, 64 | enabled: widget.isEnabled, 65 | readOnly: widget.isReadOnly!, 66 | onTap: widget.onTap, 67 | inputFormatters: [ 68 | LengthLimitingTextInputFormatter(widget.maxInput), 69 | ], 70 | decoration: InputDecoration( 71 | labelText: widget.iLabel, 72 | prefixIcon: widget.prefix, 73 | suffixIcon: InkWell( 74 | onTap: () => widget.iController!.clear(), 75 | child: Icon(Icons.clear, color: foreColor), 76 | ), 77 | labelStyle: TextStyle(fontSize: 16, color: foreColor), 78 | isDense: widget.isMultiline! ? true : false, 79 | contentPadding: widget.isMultiline! ? null : const EdgeInsets.all(5), 80 | enabledBorder: OutlineInputBorder( 81 | borderRadius: BorderRadius.circular(widget.bdRadius), 82 | borderSide: BorderSide(color: foreColor), 83 | ), 84 | focusedBorder: OutlineInputBorder( 85 | borderRadius: BorderRadius.circular(widget.bdRadius), 86 | borderSide: BorderSide(color: foreColor), 87 | ), 88 | ), 89 | style: TextStyle( 90 | fontSize: 18, 91 | color: foreColor, 92 | ), 93 | //textInputAction: widget.isMultiline! ? TextInputAction.newline : TextInputAction.next, 94 | onChanged: widget.onChanged, 95 | ), 96 | ); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /lib/presentation/widgets/progress/custom_snackbar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CustomSnackbar { 4 | static void show( 5 | BuildContext context, 6 | String feedback, { 7 | SnackBarBehavior behavior = SnackBarBehavior.floating, 8 | bool isSuccess = false, 9 | Duration duration = const Duration(seconds: 5), 10 | }) { 11 | ScaffoldMessenger.of(context) 12 | ..hideCurrentSnackBar() 13 | ..showSnackBar( 14 | SnackBar( 15 | duration: duration, 16 | backgroundColor: isSuccess ? Colors.green : Colors.red, 17 | content: Text( 18 | feedback, 19 | style: const TextStyle( 20 | color: Colors.white, 21 | ), 22 | ), 23 | ), 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/presentation/widgets/progress/skeleton.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:skeleton_loader/skeleton_loader.dart'; 3 | 4 | import '../../theme/theme_colors.dart'; 5 | import '../../theme/theme_styles.dart'; 6 | 7 | class SkeletonLoading extends StatelessWidget { 8 | const SkeletonLoading({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | final size = MediaQuery.of(context).size; 13 | var rowWidget = Container( 14 | margin: 15 | const EdgeInsets.only(left: Sizes.m, right: Sizes.m, top: Sizes.m), 16 | child: Column( 17 | mainAxisAlignment: MainAxisAlignment.start, 18 | crossAxisAlignment: CrossAxisAlignment.start, 19 | children: [ 20 | Container( 21 | margin: const EdgeInsets.only(bottom: Sizes.xs), 22 | height: 15, 23 | width: size.width - 120, 24 | color: Colors.black, 25 | ), 26 | Container( 27 | margin: const EdgeInsets.only(bottom: Sizes.xs), 28 | height: 30, 29 | width: size.width - 50, 30 | color: Colors.black, 31 | ), 32 | Row( 33 | children: [ 34 | Container( 35 | height: 10, 36 | width: size.width / 6, 37 | color: Colors.black, 38 | ), 39 | SizedBox(width: Sizes.xs), 40 | Container( 41 | height: 10, 42 | width: size.width / 6, 43 | color: Colors.black, 44 | ), 45 | SizedBox(width: Sizes.xs), 46 | Container( 47 | height: 10, 48 | width: size.width / 5, 49 | color: Colors.black, 50 | ), 51 | ], 52 | ), 53 | ], 54 | )); 55 | 56 | return SingleChildScrollView( 57 | child: SkeletonLoader( 58 | builder: rowWidget, 59 | items: 10, 60 | period: const Duration(seconds: 5), 61 | highlightColor: ThemeColors.primary, 62 | direction: SkeletonDirection.ltr, 63 | ), 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/presentation/widgets/text_scale_factor.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class TextScaleFactor extends StatelessWidget { 4 | final Widget child; 5 | 6 | const TextScaleFactor({ 7 | required this.child, 8 | Key? key, 9 | }) : super(key: key); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | final mediaQuery = MediaQuery.of(context); 14 | return MediaQuery( 15 | data: mediaQuery.copyWith(textScaler: const TextScaler.linear(1)), 16 | child: child, 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /linux/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.10) 3 | 4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 5 | 6 | # Configuration provided via flutter tool. 7 | include(${EPHEMERAL_DIR}/generated_config.cmake) 8 | 9 | # TODO: Move the rest of this into files in ephemeral. See 10 | # https://github.com/flutter/flutter/issues/57146. 11 | 12 | # Serves the same purpose as list(TRANSFORM ... PREPEND ...), 13 | # which isn't available in 3.10. 14 | function(list_prepend LIST_NAME PREFIX) 15 | set(NEW_LIST "") 16 | foreach(element ${${LIST_NAME}}) 17 | list(APPEND NEW_LIST "${PREFIX}${element}") 18 | endforeach(element) 19 | set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) 20 | endfunction() 21 | 22 | # === Flutter Library === 23 | # System-level dependencies. 24 | find_package(PkgConfig REQUIRED) 25 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 26 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) 27 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) 28 | 29 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") 30 | 31 | # Published to parent scope for install step. 32 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 33 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 34 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 35 | set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) 36 | 37 | list(APPEND FLUTTER_LIBRARY_HEADERS 38 | "fl_basic_message_channel.h" 39 | "fl_binary_codec.h" 40 | "fl_binary_messenger.h" 41 | "fl_dart_project.h" 42 | "fl_engine.h" 43 | "fl_json_message_codec.h" 44 | "fl_json_method_codec.h" 45 | "fl_message_codec.h" 46 | "fl_method_call.h" 47 | "fl_method_channel.h" 48 | "fl_method_codec.h" 49 | "fl_method_response.h" 50 | "fl_plugin_registrar.h" 51 | "fl_plugin_registry.h" 52 | "fl_standard_message_codec.h" 53 | "fl_standard_method_codec.h" 54 | "fl_string_codec.h" 55 | "fl_value.h" 56 | "fl_view.h" 57 | "flutter_linux.h" 58 | ) 59 | list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") 60 | add_library(flutter INTERFACE) 61 | target_include_directories(flutter INTERFACE 62 | "${EPHEMERAL_DIR}" 63 | ) 64 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") 65 | target_link_libraries(flutter INTERFACE 66 | PkgConfig::GTK 67 | PkgConfig::GLIB 68 | PkgConfig::GIO 69 | ) 70 | add_dependencies(flutter flutter_assemble) 71 | 72 | # === Flutter tool backend === 73 | # _phony_ is a non-existent file to force this command to run every time, 74 | # since currently there's no way to get a full input/output list from the 75 | # flutter tool. 76 | add_custom_command( 77 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 78 | ${CMAKE_CURRENT_BINARY_DIR}/_phony_ 79 | COMMAND ${CMAKE_COMMAND} -E env 80 | ${FLUTTER_TOOL_ENVIRONMENT} 81 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" 82 | ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} 83 | VERBATIM 84 | ) 85 | add_custom_target(flutter_assemble DEPENDS 86 | "${FLUTTER_LIBRARY}" 87 | ${FLUTTER_LIBRARY_HEADERS} 88 | ) 89 | -------------------------------------------------------------------------------- /linux/flutter/ephemeral/.plugin_symlinks/app_links_linux: -------------------------------------------------------------------------------- 1 | /Users/sirodaves/.pub-cache/hosted/pub.dev/app_links_linux-1.0.3/ -------------------------------------------------------------------------------- /linux/flutter/ephemeral/.plugin_symlinks/gtk: -------------------------------------------------------------------------------- 1 | /Users/sirodaves/.pub-cache/hosted/pub.dev/gtk-2.1.0/ -------------------------------------------------------------------------------- /linux/flutter/ephemeral/.plugin_symlinks/package_info_plus: -------------------------------------------------------------------------------- 1 | /Users/sirodaves/.pub-cache/hosted/pub.dev/package_info_plus-8.1.2/ -------------------------------------------------------------------------------- /linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux: -------------------------------------------------------------------------------- 1 | /Users/sirodaves/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/ -------------------------------------------------------------------------------- /linux/flutter/ephemeral/.plugin_symlinks/sentry_flutter: -------------------------------------------------------------------------------- 1 | /Users/sirodaves/.pub-cache/hosted/pub.dev/sentry_flutter-8.11.1/ -------------------------------------------------------------------------------- /linux/flutter/ephemeral/.plugin_symlinks/share_plus: -------------------------------------------------------------------------------- 1 | /Users/sirodaves/.pub-cache/hosted/pub.dev/share_plus-10.1.3/ -------------------------------------------------------------------------------- /linux/flutter/ephemeral/.plugin_symlinks/shared_preferences_linux: -------------------------------------------------------------------------------- 1 | /Users/sirodaves/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.4.1/ -------------------------------------------------------------------------------- /linux/flutter/ephemeral/.plugin_symlinks/url_launcher_linux: -------------------------------------------------------------------------------- 1 | /Users/sirodaves/.pub-cache/hosted/pub.dev/url_launcher_linux-3.2.1/ -------------------------------------------------------------------------------- /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 | #include 12 | 13 | void fl_register_plugins(FlPluginRegistry* registry) { 14 | g_autoptr(FlPluginRegistrar) gtk_registrar = 15 | fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin"); 16 | gtk_plugin_register_with_registrar(gtk_registrar); 17 | g_autoptr(FlPluginRegistrar) sentry_flutter_registrar = 18 | fl_plugin_registry_get_registrar_for_plugin(registry, "SentryFlutterPlugin"); 19 | sentry_flutter_plugin_register_with_registrar(sentry_flutter_registrar); 20 | g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = 21 | fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); 22 | url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); 23 | } 24 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | gtk 7 | sentry_flutter 8 | url_launcher_linux 9 | ) 10 | 11 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 12 | ) 13 | 14 | set(PLUGIN_BUNDLED_LIBRARIES) 15 | 16 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 17 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 18 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 19 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 20 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 21 | endforeach(plugin) 22 | 23 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 24 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 25 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 26 | endforeach(ffi_plugin) 27 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import app_links 9 | import connectivity_plus 10 | import package_info_plus 11 | import path_provider_foundation 12 | import sentry_flutter 13 | import share_plus 14 | import shared_preferences_foundation 15 | import sqflite_darwin 16 | import url_launcher_macos 17 | 18 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 19 | AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin")) 20 | ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) 21 | FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) 22 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 23 | SentryFlutterPlugin.register(with: registry.registrar(forPlugin: "SentryFlutterPlugin")) 24 | SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) 25 | SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) 26 | SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) 27 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) 28 | } 29 | -------------------------------------------------------------------------------- /macos/Flutter/ephemeral/Flutter-Generated.xcconfig: -------------------------------------------------------------------------------- 1 | // This is a generated file; do not edit or check into version control. 2 | FLUTTER_ROOT=/Users/sirodaves/Developer/Flutter/Sdk/default 3 | FLUTTER_APPLICATION_PATH=/Users/sirodaves/Developer/Flutter/Projects/SwahiLib 4 | COCOAPODS_PARALLEL_CODE_SIGN=true 5 | FLUTTER_TARGET=/Users/sirodaves/Developer/Flutter/Projects/SwahiLib/lib/main.dart 6 | FLUTTER_BUILD_DIR=build 7 | FLUTTER_BUILD_NAME=1.0.12 8 | FLUTTER_BUILD_NUMBER=1 9 | DART_DEFINES=c3VwYWJhc2VVcmw9aHR0cHM6Ly9sd2toenp2ZWV4Y2lob2JxZ2hrcS5zdXBhYmFzZS5jbw==,c3VwYWJhc2VBbm9uS2V5PWV5SmhiR2NpT2lKSVV6STFOaUlzSW5SNWNDSTZJa3BYVkNKOS5leUpwYzNNaU9pSnpkWEJoWW1GelpTSXNJbkpsWmlJNklteDNhMmg2ZW5abFpYaGphV2h2WW5GbmFHdHhJaXdpY205c1pTSTZJbUZ1YjI0aUxDSnBZWFFpT2pFM016YzBOamM1TVRVc0ltVjRjQ0k2TWpBMU16QTBNemt4TlgwLmtEVzRBWWtFMWlIOUljMXVPYUQ2Q0M0eFpaVjR3dFRrUmhlaVh1aF9tUXM=,c2VudHJ5VXJsPTQ1MDQ2NTE1MjczNTY0MTY= 10 | DART_OBFUSCATION=false 11 | TRACK_WIDGET_CREATION=true 12 | TREE_SHAKE_ICONS=false 13 | PACKAGE_CONFIG=/Users/sirodaves/Developer/Flutter/Projects/SwahiLib/.dart_tool/package_config.json 14 | -------------------------------------------------------------------------------- /macos/Flutter/ephemeral/flutter_export_environment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This is a generated file; do not edit or check into version control. 3 | export "FLUTTER_ROOT=/Users/sirodaves/Developer/Flutter/Sdk/default" 4 | export "FLUTTER_APPLICATION_PATH=/Users/sirodaves/Developer/Flutter/Projects/SwahiLib" 5 | export "COCOAPODS_PARALLEL_CODE_SIGN=true" 6 | export "FLUTTER_TARGET=/Users/sirodaves/Developer/Flutter/Projects/SwahiLib/lib/main.dart" 7 | export "FLUTTER_BUILD_DIR=build" 8 | export "FLUTTER_BUILD_NAME=1.0.12" 9 | export "FLUTTER_BUILD_NUMBER=1" 10 | export "DART_DEFINES=c3VwYWJhc2VVcmw9aHR0cHM6Ly9sd2toenp2ZWV4Y2lob2JxZ2hrcS5zdXBhYmFzZS5jbw==,c3VwYWJhc2VBbm9uS2V5PWV5SmhiR2NpT2lKSVV6STFOaUlzSW5SNWNDSTZJa3BYVkNKOS5leUpwYzNNaU9pSnpkWEJoWW1GelpTSXNJbkpsWmlJNklteDNhMmg2ZW5abFpYaGphV2h2WW5GbmFHdHhJaXdpY205c1pTSTZJbUZ1YjI0aUxDSnBZWFFpT2pFM016YzBOamM1TVRVc0ltVjRjQ0k2TWpBMU16QTBNemt4TlgwLmtEVzRBWWtFMWlIOUljMXVPYUQ2Q0M0eFpaVjR3dFRrUmhlaVh1aF9tUXM=,c2VudHJ5VXJsPTQ1MDQ2NTE1MjczNTY0MTY=" 11 | export "DART_OBFUSCATION=false" 12 | export "TRACK_WIDGET_CREATION=true" 13 | export "TREE_SHAKE_ICONS=false" 14 | export "PACKAGE_CONFIG=/Users/sirodaves/Developer/Flutter/Projects/SwahiLib/.dart_tool/package_config.json" 15 | -------------------------------------------------------------------------------- /macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.14' 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 | end 43 | end 44 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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 | override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { 11 | return true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "app_icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "app_icon_32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "app_icon_32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "app_icon_64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "app_icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "app_icon_256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "app_icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "app_icon_512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "app_icon_512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "app_icon_1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /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 = SwahiLib 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.swahilib 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2024 Futuristic Ke. All rights reserved. 15 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /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/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.client 10 | 11 | com.apple.security.network.server 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | com.apple.security.network.server 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 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: swahilib 2 | description: Kiswahili Kitukuzwe - Kamusi ya Kiswahili ya maneno, nahau, methali na misemo 3 | publish_to: 'none' 4 | 5 | version: 1.0.125 6 | 7 | environment: 8 | sdk: ^3.5.4 9 | 10 | dependencies: 11 | connectivity_plus: ^6.1.3 # Check network connectivity status (WiFi/Mobile/Offline) 12 | cupertino_icons: ^1.0.8 # iOS-style icons for use with the Cupertino widgets 13 | dartx: ^1.2.0 # Kotlin-like extension methods for Dart collections and primitives 14 | floor: ^1.5.0 # SQLite abstraction for Flutter with DAO pattern 15 | flutter: 16 | sdk: flutter # Core Flutter SDK 17 | flutter_bloc: ^8.1.6 # State management library based on BLoC (Business Logic Component) pattern 18 | flutter_html: ^3.0.0-beta.2 # Render HTML content as Flutter widgets 19 | freezed_annotation: ^2.4.4 # Annotations for generating immutable classes with union/pattern matching 20 | get_it: ^8.0.2 # Simple service locator for dependency injection 21 | http: ^1.3.0 # HTTP client for making web requests 22 | injectable: ^2.5.0 # Dependency injection framework for Flutter using annotations 23 | intl: ^0.19.0 # Internationalization and localization support 24 | json_annotation: ^4.9.0 # Annotations for code generation to serialize/deserialize JSON 25 | loading_indicator: ^3.1.1 # Various loading animations for async operations 26 | package_info_plus: ^8.1.1 # Access app version, package name, and build number 27 | path: ^1.9.0 # Path manipulation utilities (for files, directories, etc.) 28 | path_provider: ^2.1.5 # Access commonly used locations on the filesystem 29 | percent_indicator: ^4.2.4 # Circular and linear percent indicators 30 | rxdart: ^0.28.0 # Reactive functional programming with observables and streams 31 | sentry_flutter: ^8.5.0 # Error tracking and performance monitoring using Sentry 32 | share_plus: ^10.1.2 # Share content from your Flutter app with other apps 33 | shared_preferences: ^2.3.3 # Persistent storage for simple key-value pairs 34 | sizer: ^3.0.4 # Helps with responsive UI using screen dimensions 35 | skeleton_loader: ^2.0.0+4 # Display shimmer loading placeholders for content 36 | sqflite: ^2.4.1 # SQLite plugin for Flutter 37 | styled_widget: ^0.4.1 # Simplifies Flutter widget styling through extension methods 38 | supabase_flutter: ^2.8.3 # Supabase client for Flutter, provides auth, storage, database access 39 | textstyle_extensions: ^2.0.0-nullsafety # Utility extensions for `TextStyle` manipulation 40 | url_launcher: ^6.3.1 # Launch URLs (email, phone, web) in a mobile browser or other apps 41 | 42 | dev_dependencies: 43 | build_runner: ^2.4.13 # Code generation tool (used with freezed, json_serializable, etc.) 44 | floor_generator: ^1.5.0 # Generates the database access code for Floor 45 | flutter_lints: ^4.0.0 # Recommended lint rules for Flutter apps 46 | flutter_test: 47 | sdk: flutter # Flutter's built-in testing framework 48 | freezed: ^2.5.7 # Code generator for creating immutable classes with union types 49 | injectable_generator: ^2.6.2 # Code generator for injectable DI setup 50 | json_serializable: ^6.8.0 # Code generator for serializing classes to/from JSON 51 | 52 | 53 | flutter: 54 | generate: true 55 | uses-material-design: true 56 | assets: 57 | - assets/fonts/ 58 | - assets/icons/ 59 | - assets/images/ 60 | - assets/sound/ 61 | fonts: 62 | - family: TrebuchetMS 63 | fonts: 64 | - asset: assets/fonts/Trebuchet-MS.ttf 65 | -------------------------------------------------------------------------------- /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:swahilib/main.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 MyApp()); 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 | -------------------------------------------------------------------------------- /updates.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.124", 3 | "title": "Letter filtering fixed", 4 | "description": "Now words, sayings, proverbs or sayings can be filtered when you tap on the letters on the far left", 5 | "appLinks": { 6 | "android": "https://play.google.com/store/apps/details?id=com.swahilib", 7 | "ios": "https://apps.apple.com/us/app/id6446771678", 8 | "windows": "https://github.com/SiroDaves/SwahiLib", 9 | "macos": "https://github.com/SiroDaves/SwahiLib", 10 | "linux": "https://github.com/SiroDaves/SwahiLib" 11 | } 12 | } -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | SwahiLib 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SwahiLib", 3 | "short_name": "SwahiLib", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /windows/flutter/ephemeral/.plugin_symlinks/app_links: -------------------------------------------------------------------------------- 1 | /Users/sirodaves/.pub-cache/hosted/pub.dev/app_links-6.3.3/ -------------------------------------------------------------------------------- /windows/flutter/ephemeral/.plugin_symlinks/connectivity_plus: -------------------------------------------------------------------------------- 1 | /Users/sirodaves/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.3/ -------------------------------------------------------------------------------- /windows/flutter/ephemeral/.plugin_symlinks/package_info_plus: -------------------------------------------------------------------------------- 1 | /Users/sirodaves/.pub-cache/hosted/pub.dev/package_info_plus-8.1.2/ -------------------------------------------------------------------------------- /windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows: -------------------------------------------------------------------------------- 1 | /Users/sirodaves/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/ -------------------------------------------------------------------------------- /windows/flutter/ephemeral/.plugin_symlinks/sentry_flutter: -------------------------------------------------------------------------------- 1 | /Users/sirodaves/.pub-cache/hosted/pub.dev/sentry_flutter-8.11.1/ -------------------------------------------------------------------------------- /windows/flutter/ephemeral/.plugin_symlinks/share_plus: -------------------------------------------------------------------------------- 1 | /Users/sirodaves/.pub-cache/hosted/pub.dev/share_plus-10.1.3/ -------------------------------------------------------------------------------- /windows/flutter/ephemeral/.plugin_symlinks/shared_preferences_windows: -------------------------------------------------------------------------------- 1 | /Users/sirodaves/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.4.1/ -------------------------------------------------------------------------------- /windows/flutter/ephemeral/.plugin_symlinks/url_launcher_windows: -------------------------------------------------------------------------------- 1 | /Users/sirodaves/.pub-cache/hosted/pub.dev/url_launcher_windows-3.1.3/ -------------------------------------------------------------------------------- /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 | 15 | void RegisterPlugins(flutter::PluginRegistry* registry) { 16 | AppLinksPluginCApiRegisterWithRegistrar( 17 | registry->GetRegistrarForPlugin("AppLinksPluginCApi")); 18 | ConnectivityPlusWindowsPluginRegisterWithRegistrar( 19 | registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); 20 | SentryFlutterPluginRegisterWithRegistrar( 21 | registry->GetRegistrarForPlugin("SentryFlutterPlugin")); 22 | SharePlusWindowsPluginCApiRegisterWithRegistrar( 23 | registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); 24 | UrlLauncherWindowsRegisterWithRegistrar( 25 | registry->GetRegistrarForPlugin("UrlLauncherWindows")); 26 | } 27 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | app_links 7 | connectivity_plus 8 | sentry_flutter 9 | share_plus 10 | url_launcher_windows 11 | ) 12 | 13 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 14 | ) 15 | 16 | set(PLUGIN_BUNDLED_LIBRARIES) 17 | 18 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 19 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 20 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 21 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 22 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 23 | endforeach(plugin) 24 | 25 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 26 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 27 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 28 | endforeach(ffi_plugin) 29 | -------------------------------------------------------------------------------- /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/Runner.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #pragma code_page(65001) 4 | #include "resource.h" 5 | 6 | #define APSTUDIO_READONLY_SYMBOLS 7 | ///////////////////////////////////////////////////////////////////////////// 8 | // 9 | // Generated from the TEXTINCLUDE 2 resource. 10 | // 11 | #include "winres.h" 12 | 13 | ///////////////////////////////////////////////////////////////////////////// 14 | #undef APSTUDIO_READONLY_SYMBOLS 15 | 16 | ///////////////////////////////////////////////////////////////////////////// 17 | // English (United States) resources 18 | 19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""winres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Icon 51 | // 52 | 53 | // Icon with lowest ID value placed first to ensure application icon 54 | // remains consistent on all systems. 55 | IDI_APP_ICON ICON "resources\\app_icon.ico" 56 | 57 | 58 | ///////////////////////////////////////////////////////////////////////////// 59 | // 60 | // Version 61 | // 62 | 63 | #if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) 64 | #define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD 65 | #else 66 | #define VERSION_AS_NUMBER 1,0,0,0 67 | #endif 68 | 69 | #if defined(FLUTTER_VERSION) 70 | #define VERSION_AS_STRING FLUTTER_VERSION 71 | #else 72 | #define VERSION_AS_STRING "1.0.0" 73 | #endif 74 | 75 | VS_VERSION_INFO VERSIONINFO 76 | FILEVERSION VERSION_AS_NUMBER 77 | PRODUCTVERSION VERSION_AS_NUMBER 78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 79 | #ifdef _DEBUG 80 | FILEFLAGS VS_FF_DEBUG 81 | #else 82 | FILEFLAGS 0x0L 83 | #endif 84 | FILEOS VOS__WINDOWS32 85 | FILETYPE VFT_APP 86 | FILESUBTYPE 0x0L 87 | BEGIN 88 | BLOCK "StringFileInfo" 89 | BEGIN 90 | BLOCK "040904e4" 91 | BEGIN 92 | VALUE "CompanyName", "Futuristic Ke" "\0" 93 | VALUE "FileDescription", "SwahiLib" "\0" 94 | VALUE "FileVersion", VERSION_AS_STRING "\0" 95 | VALUE "InternalName", "SwahiLib" "\0" 96 | VALUE "LegalCopyright", "Copyright (C) 2024 Futuristic Ke. All rights reserved." "\0" 97 | VALUE "OriginalFilename", "SwahiLib.exe" "\0" 98 | VALUE "ProductName", "SwahiLib" "\0" 99 | VALUE "ProductVersion", VERSION_AS_STRING "\0" 100 | END 101 | END 102 | BLOCK "VarFileInfo" 103 | BEGIN 104 | VALUE "Translation", 0x409, 1252 105 | END 106 | END 107 | 108 | #endif // English (United States) resources 109 | ///////////////////////////////////////////////////////////////////////////// 110 | 111 | 112 | 113 | #ifndef APSTUDIO_INVOKED 114 | ///////////////////////////////////////////////////////////////////////////// 115 | // 116 | // Generated from the TEXTINCLUDE 3 resource. 117 | // 118 | 119 | 120 | ///////////////////////////////////////////////////////////////////////////// 121 | #endif // not APSTUDIO_INVOKED 122 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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(720, 1280); 30 | if (!window.Create(L"SwahiLib", 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiroDaves/SwahiLib-Flutter/b269068f0196abf145ee565ea6ba0f4eb1fff126/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------