├── .gitignore ├── .metadata ├── CONTRIBUTING.md ├── README.md ├── SudokuSolver.gif ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── ic_launcher-playstore.png │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── flutterbuddies │ │ │ │ └── sudoku_solver │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable │ │ │ ├── ic_launcher_background.xml │ │ │ └── launch_background.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets └── translations │ ├── ar.json │ └── en.json ├── 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 │ │ ├── Contents.json │ │ ├── Icon-App-1024x1024@1x.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ └── Icon-App-83.5x83.5@2x.png │ └── 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 ├── lib ├── constants │ ├── enums.dart │ ├── example_boards.dart │ └── ui_constants.dart ├── main.dart ├── models │ ├── board_square.dart │ ├── check_legal_fn.dart │ ├── old_solve_fn.dart │ ├── position_model.dart │ ├── solve_fn.dart │ └── sudoku_grid.dart ├── screens │ └── solve_screen.dart ├── translations │ ├── codegen_loader.g.dart │ └── locale_keys.g.dart ├── utils │ └── utils.dart └── widgets │ ├── animated_solve_button.dart │ ├── custom_slider_thumb_circle.dart │ ├── custom_slider_thumb_rect.dart │ ├── horizontal_layout.dart │ ├── keypad.dart │ ├── keypad_cell.dart │ ├── language_bottom_sheet.dart │ ├── options_row.dart │ ├── secondary_button.dart │ ├── solve_button.dart │ ├── sudoku_cell.dart │ ├── sudoku_table.dart │ └── vertical_layout.dart ├── pubspec.lock ├── pubspec.yaml └── test └── widget_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | key.properties -------------------------------------------------------------------------------- /.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: 84f3d28555368a70270e9ac8390a9441df95e752 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | This document will briefly describe basic guidelines for contributors. All project communication shall be done at [issues](https://github.com/Flutter-Buddies/sudoku-solver/issues) or FlutterBuddies Discord [server](http://flutterbuddies.com/). Everything in this text that you find confusing or have any objections, can and should be discussed at issues or Discord. 4 | 5 | The upcoming titles sort of describe the order of things you want to explore before contributing. 6 | 7 | - [Discord](#discord) 8 | - [Issues](#issues) 9 | - [Pull Requests](#pull-requests) 10 | - [Useful Links](#useful-links) 11 | - [Code Style](#code-style) 12 | 13 | ## Discord 14 | 15 | If you'd like to talk about the project in general, please do at project's Discord [channel](https://discord.com/channels/768528774991446088/774393898164289576). We can go on from there. 16 | 17 | ## Issues 18 | 19 | Issues at this repository are a main place to look at when planning to contribute. Two situations can occur: 20 | 21 | **Your idea is already present in an issue** 22 | 23 | In this case, after reading all the comments at this issue to assess the situation and how it aligns with your ideas, you can 24 | 25 | - add your comment with some further insight or a question 26 | - contact issue's assignee (either by leaving a comment with assignee tagged or contacting assignee directly) for details on how can you contribute code 27 | 28 | **You have an idea not present in issues** 29 | 30 | In this case, you can create a new issue with detailed explanation of your idea, question or bug report with minimal code example if applicable. Then the other contributors will comment further at your issue and you can plan how this will be developed. By default, you can then become the assignee at this issue, the one who will be responsible for it's development. Of course, this is not forced upon anyone so you can let us know in the comments that you prefer not to be the assignee. Then others can apply to be the assignee. 31 | 32 | If an issue doesn't have an assignee, the position is open for anyone who wants it. In this assigneeless situation, the "acting assignee" is the project creator (Zambrella). 33 | 34 | ## Pull Requests 35 | 36 | To contribute, you will eventually be doing a Pull Request for your fork. First, you need to fork the repository and set the upstream via `git remote add upstream https://github.com/Flutter-Buddies/sudoku-solver.git`. 37 | 38 | When you fork the project, you will be at `main` branch by default. To switch branches (if necessary) , use `git checkout `. It will be clearly discussed on which branch should each issue be worked on. 39 | 40 | If you are contributing at some branch for a while, to have the latest changes if something has been pushed in the meantime, do `git fetch upstream`. This will create branches `upstream/` that you then need to merge into your branch you're working on. To do that, `git checkout ` (if you're not already on it) and `git merge upstream/`. 41 | 42 | ### Useful Links 43 | 44 | - [Git Handbook](https://guides.github.com/introduction/git-handbook) 45 | - [GitHub Hello World](https://guides.github.com/activities/hello-world/) 46 | - [git branches - official GitHub docs](https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/about-branches) 47 | - [Fork - official GitHub docs](https://docs.github.com/en/free-pro-team@latest/github/getting-started-with-github/fork-a-repo) 48 | - [Syncing a fork - official GitHub docs](https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/syncing-a-fork) 49 | - [Syncing a fork - StackOverflow](https://stackoverflow.com/questions/7244321/how-do-i-update-a-github-forked-repository) 50 | - [Creating a pull request from a fork - official GitHub docs](https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork) 51 | 52 | 53 | ## Code Style 54 | 55 | It is sufficient that you format your code. See [here](https://flutter.dev/docs/development/tools/formatting) how this can be accomplished and automated. 56 | 57 | ## Adding Support for a New Language 58 | 59 | To add a language follow the following steps: 60 | 61 | - Fork this repository 62 | - Create a branch from `main` with name `add_{YOUR_LANGUAGE_NAME}_language`, so for example `add_arabic_language` 63 | - Create a new JSON file in `assets/translations` folder with the name of the language code only, no country specificatiom. So like `ar.json` for Arabic. 64 | - Fill the file with all missing values by opening the translations folder in `i18n Manager` app, [download here](https://github.com/gilmarsquinelato/i18n-manager/releases/tag/3.0.3) for your OS. This app will make your life so musch easier 😉 65 | 66 | 67 | After filling the newly created file: 68 | - Add a new entry in the `Info.plist` of iOS located at `ios/Runner/Info.plist` under the array of `CFBundleLocalizations`, append it to the end with the language code. 69 | Example of this: 70 | 71 | Before: 72 | ```xml 73 | CFBundleLocalizations 74 | 75 | en 76 | ar 77 | 78 | ``` 79 | After: 80 | ```xml 81 | CFBundleLocalizations 82 | 83 | en 84 | ar 85 | {YOUR_LANGUAGE_CODE} 86 | 87 | ``` 88 | 89 | - Append your locale to the `supportedLocales` in the `main.dart` file like so: 90 | 91 | Before: 92 | ```dart 93 | supportedLocales: [ 94 | Locale('en'), 95 | Locale('ar'), 96 | ], 97 | ``` 98 | After: 99 | ```dart 100 | supportedLocales: [ 101 | Locale('en'), 102 | Locale('ar'), 103 | Locale('{YOUR_LANGUAGE_CODE}'), 104 | ], 105 | ``` 106 | 107 | - Run the generator by running the following commands in a terminal in the app root directory: 108 | ```bash 109 | $ flutter pub run easy_localization:generate -S "assets/translations" -O "lib/translations" 110 | 111 | $ flutter pub run easy_localization:generate -S "assets/translations" -O "lib/translations" -o "locale_keys.g.dart" -f keys 112 | ``` 113 | 114 | - If the new language is a `RTL` language add it to the switch statement as a case returning true like this: 115 | 116 | Before: 117 | ```dart 118 | bool isCurrentLocaleRTL(BuildContext context) { 119 | switch (context.locale.languageCode) { 120 | case 'ar': 121 | return true; 122 | break; 123 | default: 124 | return false; 125 | } 126 | } 127 | ``` 128 | After: 129 | ```dart 130 | bool isCurrentLocaleRTL(BuildContext context) { 131 | switch (context.locale.languageCode) { 132 | case 'ar': 133 | case '{YOUR_LANGUAGE_CODE}': // only if rtl, else it will be false 134 | return true; 135 | break; 136 | default: 137 | return false; 138 | } 139 | } 140 | ``` 141 | 142 | - Add a new value to the `SupportedLocale` enum located at `lib/utils/utils.dart` with the language code you are adding like this: 143 | 144 | Before: 145 | ```dart 146 | enum SupportedLocale { 147 | en, 148 | ar, 149 | } 150 | ``` 151 | After: 152 | ```dart 153 | enum SupportedLocale { 154 | en, 155 | ar, 156 | {YOUR_LANGUAGE_CODE}, // here 157 | } 158 | ``` 159 | 160 | - Add a new case to the switch statement located at `lib/utils/utils.dart`: 161 | 162 | Before: 163 | ```dart 164 | static void changeLocale( 165 | BuildContext context, 166 | SupportedLocale supportedLocale, 167 | ) { 168 | Locale locale; 169 | 170 | switch (supportedLocale) { 171 | case SupportedLocale.en: 172 | locale = Locale('en'); 173 | break; 174 | case SupportedLocale.ar: 175 | locale = Locale('ar'); 176 | break; 177 | default: 178 | locale = Locale('en'); 179 | break; 180 | } 181 | 182 | EasyLocalization.of(context).setLocale(locale); 183 | } 184 | ``` 185 | After: 186 | ```dart 187 | static void changeLocale( 188 | BuildContext context, 189 | SupportedLocale supportedLocale, 190 | ) { 191 | Locale locale; 192 | 193 | switch (supportedLocale) { 194 | case SupportedLocale.en: 195 | locale = Locale('en'); 196 | break; 197 | case SupportedLocale.ar: 198 | locale = Locale('ar'); 199 | break; 200 | case SupportedLocale.YOUR_LANGUAGE_CODE: // here 201 | locale = Locale('{YOUR_LANGUAGE_CODE}'); // here 202 | break; // here 203 | default: 204 | locale = Locale('en'); 205 | break; 206 | } 207 | 208 | EasyLocalization.of(context).setLocale(locale); 209 | } 210 | ``` 211 | 212 | - Add a new `LanguageListTile` at `lib/menu/ui/language_bottom_sheet.dart` 213 | 214 | Before: 215 | ```dart 216 | Expanded( 217 | child: ListView( 218 | children: [ 219 | /// emojis from: https://emojipedia.org/flags/ 220 | LanguageListTile( 221 | languageEmoji: '🇬🇧', 222 | languageName: 'English', 223 | showCheck: Utils.currentLocale(context) == Locale('en'), 224 | locale: SupportedLocale.en, 225 | ), 226 | LanguageListTile( 227 | languageEmoji: '🇸🇦', 228 | languageName: 'العربية', 229 | showCheck: Utils.currentLocale(context) == Locale('ar'), 230 | locale: SupportedLocale.ar, 231 | ), 232 | ], 233 | ), 234 | ), 235 | ``` 236 | After: 237 | ```dart 238 | Expanded( 239 | child: ListView( 240 | children: [ 241 | /// emojis from: https://emojipedia.org/flags/ 242 | LanguageListTile( 243 | languageEmoji: '🇬🇧', 244 | languageName: 'English', 245 | showCheck: Utils.currentLocale(context) == Locale('en'), 246 | locale: SupportedLocale.en, 247 | ), 248 | LanguageListTile( 249 | languageEmoji: '🇸🇦', 250 | languageName: 'العربية', 251 | showCheck: Utils.currentLocale(context) == Locale('ar'), 252 | locale: SupportedLocale.ar, 253 | ), 254 | LanguageListTile( 255 | languageEmoji: '{EMOJI_FOR_LANGUAGE}', //get emoji from https://emojipedia.org/flags/ 256 | languageName: '{YOUR_LANGUAGE_NAME_IN_NATIVE_WAY_NOT_IN_ENGLISH}', // change this 257 | showCheck: Utils.currentLocale(context) == Locale('{YOUR_LANGUAGE_CODE}'), // change this 258 | locale: SupportedLocale.YOUR_LANGUAGE_CODE, // change this 259 | ), 260 | ], 261 | ), 262 | ), 263 | ``` 264 | 265 | - Create a Pull Request 🚀 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flutter Sudoku Solver 2 | 3 | Sudoku Solver is a simple Flutter application to get the solution to Sudoku puzzles. It uses a backtrack search algorithm to find the solution. The algorithm is performed on a separate isolate because complex puzzles can take a few seconds to solve. 4 | 5 | It is also fully responsive and works in vertical and horizontal orientations. 6 | 7 | Get it on the play store 8 | Get it on Google Play 9 | Apple App Store release coming soon... 10 | 11 | Any contributions are welcome. For help, see [CONTRIBUTING](CONTRIBUTING.md) or ask on Discord 12 | 13 | ![screen recording](SudokuSolver.gif) 14 | 15 | 16 | -------------------------------------------------------------------------------- /SudokuSolver.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/SudokuSolver.gif -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | def keystoreProperties = new Properties() 29 | def keystorePropertiesFile = rootProject.file('key.properties') 30 | if (keystorePropertiesFile.exists()) { 31 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 32 | } 33 | 34 | android { 35 | compileSdkVersion 29 36 | 37 | sourceSets { 38 | main.java.srcDirs += 'src/main/kotlin' 39 | } 40 | 41 | lintOptions { 42 | disable 'InvalidPackage' 43 | } 44 | 45 | defaultConfig { 46 | applicationId "com.flutterbuddies.sudoku_solver" 47 | minSdkVersion 16 48 | targetSdkVersion 29 49 | versionCode flutterVersionCode.toInteger() 50 | versionName flutterVersionName 51 | } 52 | 53 | signingConfigs { 54 | release { 55 | keyAlias keystoreProperties['keyAlias'] 56 | keyPassword keystoreProperties['keyPassword'] 57 | storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null 58 | storePassword keystoreProperties['storePassword'] 59 | } 60 | } 61 | buildTypes { 62 | release { 63 | signingConfig signingConfigs.release 64 | } 65 | } 66 | 67 | } 68 | 69 | flutter { 70 | source '../..' 71 | } 72 | 73 | dependencies { 74 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 75 | } 76 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 12 | 19 | 23 | 27 | 32 | 36 | 37 | 38 | 39 | 40 | 41 | 43 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /android/app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/android/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/flutterbuddies/sudoku_solver/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.flutterbuddies.sudoku_solver 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.1.2' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | android.enableR8=true 5 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Feb 12 15:58:48 GMT 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /assets/translations/ar.json: -------------------------------------------------------------------------------- 1 | { 2 | "slide_to_solve": "قم بالتمرير لحل اللغز", 3 | "solved": "تم الحل!", 4 | "solving": "جاري الحل...", 5 | "unsolvable": "غير قابلة للحل", 6 | "reset": "إعادة", 7 | "feature_coming_soon": "ستتوفر الميزة قريبًا!", 8 | "take_picture": "التقط صورة", 9 | "invalid_puzzle_unsolvable": "لغز غير صالح - غير قابل للحل", 10 | "invalid_puzzle_duplicate_number": "لغز غير صالح - رقم مكرر", 11 | "solve": "حل", 12 | "invalid_board": "لوحة غير صالحة", 13 | "this_board_connot_be_solved": "لا يمكن حل هذه اللوحة", 14 | "there_is_an_error_on_the_board": "هناك خطأ في لوحة السودوكو!", 15 | "invalid_board_duplicate_number": "لوحة غير صالحة - رقم مكرر", 16 | "sudoku_solver": "Sudoku Solver", 17 | "change_language": "تغيير اللغة" 18 | } -------------------------------------------------------------------------------- /assets/translations/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "slide_to_solve": "Slide to solve", 3 | "solved": "SOLVED!", 4 | "solving": "Solving...", 5 | "unsolvable": "Unsolvable", 6 | "reset": "Reset", 7 | "feature_coming_soon": "Feature coming soon!", 8 | "take_picture": "Take picture", 9 | "invalid_puzzle_unsolvable": "Invalid Puzzle - Unsolvable", 10 | "invalid_puzzle_duplicate_number": "Invalid Puzzle - Duplicate Number", 11 | "solve": "Solve", 12 | "invalid_board": "Invalid Board", 13 | "this_board_connot_be_solved": "This board cannot be solved", 14 | "there_is_an_error_on_the_board": "There is an error on the board!", 15 | "invalid_board_duplicate_number": "Invalid Board - Duplicate Number", 16 | "sudoku_solver": "Sudoku Solver", 17 | "change_language": "Change Language" 18 | } -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | 9.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, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | 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 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 13 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 14 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 15 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXCopyFilesBuildPhase section */ 19 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 20 | isa = PBXCopyFilesBuildPhase; 21 | buildActionMask = 2147483647; 22 | dstPath = ""; 23 | dstSubfolderSpec = 10; 24 | files = ( 25 | ); 26 | name = "Embed Frameworks"; 27 | runOnlyForDeploymentPostprocessing = 0; 28 | }; 29 | /* End PBXCopyFilesBuildPhase section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 33 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 34 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 35 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 36 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 37 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 38 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 39 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 40 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 42 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 43 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 44 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXFrameworksBuildPhase section */ 56 | 57 | /* Begin PBXGroup section */ 58 | 9740EEB11CF90186004384FC /* Flutter */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 62 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 63 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 64 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 65 | ); 66 | name = Flutter; 67 | sourceTree = ""; 68 | }; 69 | 97C146E51CF9000F007C117D = { 70 | isa = PBXGroup; 71 | children = ( 72 | 9740EEB11CF90186004384FC /* Flutter */, 73 | 97C146F01CF9000F007C117D /* Runner */, 74 | 97C146EF1CF9000F007C117D /* Products */, 75 | ); 76 | sourceTree = ""; 77 | }; 78 | 97C146EF1CF9000F007C117D /* Products */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 97C146EE1CF9000F007C117D /* Runner.app */, 82 | ); 83 | name = Products; 84 | sourceTree = ""; 85 | }; 86 | 97C146F01CF9000F007C117D /* Runner */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 90 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 91 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 92 | 97C147021CF9000F007C117D /* Info.plist */, 93 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 94 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 95 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 96 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 97 | ); 98 | path = Runner; 99 | sourceTree = ""; 100 | }; 101 | /* End PBXGroup section */ 102 | 103 | /* Begin PBXNativeTarget section */ 104 | 97C146ED1CF9000F007C117D /* Runner */ = { 105 | isa = PBXNativeTarget; 106 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 107 | buildPhases = ( 108 | 9740EEB61CF901F6004384FC /* Run Script */, 109 | 97C146EA1CF9000F007C117D /* Sources */, 110 | 97C146EB1CF9000F007C117D /* Frameworks */, 111 | 97C146EC1CF9000F007C117D /* Resources */, 112 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 113 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 114 | ); 115 | buildRules = ( 116 | ); 117 | dependencies = ( 118 | ); 119 | name = Runner; 120 | productName = Runner; 121 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 122 | productType = "com.apple.product-type.application"; 123 | }; 124 | /* End PBXNativeTarget section */ 125 | 126 | /* Begin PBXProject section */ 127 | 97C146E61CF9000F007C117D /* Project object */ = { 128 | isa = PBXProject; 129 | attributes = { 130 | LastUpgradeCheck = 1020; 131 | ORGANIZATIONNAME = ""; 132 | TargetAttributes = { 133 | 97C146ED1CF9000F007C117D = { 134 | CreatedOnToolsVersion = 7.3.1; 135 | LastSwiftMigration = 1100; 136 | }; 137 | }; 138 | }; 139 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 140 | compatibilityVersion = "Xcode 9.3"; 141 | developmentRegion = en; 142 | hasScannedForEncodings = 0; 143 | knownRegions = ( 144 | en, 145 | Base, 146 | ); 147 | mainGroup = 97C146E51CF9000F007C117D; 148 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 149 | projectDirPath = ""; 150 | projectRoot = ""; 151 | targets = ( 152 | 97C146ED1CF9000F007C117D /* Runner */, 153 | ); 154 | }; 155 | /* End PBXProject section */ 156 | 157 | /* Begin PBXResourcesBuildPhase section */ 158 | 97C146EC1CF9000F007C117D /* Resources */ = { 159 | isa = PBXResourcesBuildPhase; 160 | buildActionMask = 2147483647; 161 | files = ( 162 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 163 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 164 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 165 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 166 | ); 167 | runOnlyForDeploymentPostprocessing = 0; 168 | }; 169 | /* End PBXResourcesBuildPhase section */ 170 | 171 | /* Begin PBXShellScriptBuildPhase section */ 172 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 173 | isa = PBXShellScriptBuildPhase; 174 | buildActionMask = 2147483647; 175 | files = ( 176 | ); 177 | inputPaths = ( 178 | ); 179 | name = "Thin Binary"; 180 | outputPaths = ( 181 | ); 182 | runOnlyForDeploymentPostprocessing = 0; 183 | shellPath = /bin/sh; 184 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 185 | }; 186 | 9740EEB61CF901F6004384FC /* Run Script */ = { 187 | isa = PBXShellScriptBuildPhase; 188 | buildActionMask = 2147483647; 189 | files = ( 190 | ); 191 | inputPaths = ( 192 | ); 193 | name = "Run Script"; 194 | outputPaths = ( 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | shellPath = /bin/sh; 198 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 199 | }; 200 | /* End PBXShellScriptBuildPhase section */ 201 | 202 | /* Begin PBXSourcesBuildPhase section */ 203 | 97C146EA1CF9000F007C117D /* Sources */ = { 204 | isa = PBXSourcesBuildPhase; 205 | buildActionMask = 2147483647; 206 | files = ( 207 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 208 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 209 | ); 210 | runOnlyForDeploymentPostprocessing = 0; 211 | }; 212 | /* End PBXSourcesBuildPhase section */ 213 | 214 | /* Begin PBXVariantGroup section */ 215 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 216 | isa = PBXVariantGroup; 217 | children = ( 218 | 97C146FB1CF9000F007C117D /* Base */, 219 | ); 220 | name = Main.storyboard; 221 | sourceTree = ""; 222 | }; 223 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 224 | isa = PBXVariantGroup; 225 | children = ( 226 | 97C147001CF9000F007C117D /* Base */, 227 | ); 228 | name = LaunchScreen.storyboard; 229 | sourceTree = ""; 230 | }; 231 | /* End PBXVariantGroup section */ 232 | 233 | /* Begin XCBuildConfiguration section */ 234 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 235 | isa = XCBuildConfiguration; 236 | buildSettings = { 237 | ALWAYS_SEARCH_USER_PATHS = NO; 238 | CLANG_ANALYZER_NONNULL = YES; 239 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 240 | CLANG_CXX_LIBRARY = "libc++"; 241 | CLANG_ENABLE_MODULES = YES; 242 | CLANG_ENABLE_OBJC_ARC = YES; 243 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 244 | CLANG_WARN_BOOL_CONVERSION = YES; 245 | CLANG_WARN_COMMA = YES; 246 | CLANG_WARN_CONSTANT_CONVERSION = YES; 247 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 248 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 249 | CLANG_WARN_EMPTY_BODY = YES; 250 | CLANG_WARN_ENUM_CONVERSION = YES; 251 | CLANG_WARN_INFINITE_RECURSION = YES; 252 | CLANG_WARN_INT_CONVERSION = YES; 253 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 254 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 255 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 256 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 257 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 258 | CLANG_WARN_STRICT_PROTOTYPES = YES; 259 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 260 | CLANG_WARN_UNREACHABLE_CODE = YES; 261 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 262 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 263 | COPY_PHASE_STRIP = NO; 264 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 265 | ENABLE_NS_ASSERTIONS = NO; 266 | ENABLE_STRICT_OBJC_MSGSEND = YES; 267 | GCC_C_LANGUAGE_STANDARD = gnu99; 268 | GCC_NO_COMMON_BLOCKS = YES; 269 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 270 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 271 | GCC_WARN_UNDECLARED_SELECTOR = YES; 272 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 273 | GCC_WARN_UNUSED_FUNCTION = YES; 274 | GCC_WARN_UNUSED_VARIABLE = YES; 275 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 276 | MTL_ENABLE_DEBUG_INFO = NO; 277 | SDKROOT = iphoneos; 278 | SUPPORTED_PLATFORMS = iphoneos; 279 | TARGETED_DEVICE_FAMILY = "1,2"; 280 | VALIDATE_PRODUCT = YES; 281 | }; 282 | name = Profile; 283 | }; 284 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 285 | isa = XCBuildConfiguration; 286 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 287 | buildSettings = { 288 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 289 | CLANG_ENABLE_MODULES = YES; 290 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 291 | ENABLE_BITCODE = NO; 292 | FRAMEWORK_SEARCH_PATHS = ( 293 | "$(inherited)", 294 | "$(PROJECT_DIR)/Flutter", 295 | ); 296 | INFOPLIST_FILE = Runner/Info.plist; 297 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 298 | LIBRARY_SEARCH_PATHS = ( 299 | "$(inherited)", 300 | "$(PROJECT_DIR)/Flutter", 301 | ); 302 | PRODUCT_BUNDLE_IDENTIFIER = com.flutterbuddies.sudokuSolver; 303 | PRODUCT_NAME = "$(TARGET_NAME)"; 304 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 305 | SWIFT_VERSION = 5.0; 306 | VERSIONING_SYSTEM = "apple-generic"; 307 | }; 308 | name = Profile; 309 | }; 310 | 97C147031CF9000F007C117D /* Debug */ = { 311 | isa = XCBuildConfiguration; 312 | buildSettings = { 313 | ALWAYS_SEARCH_USER_PATHS = NO; 314 | CLANG_ANALYZER_NONNULL = YES; 315 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 316 | CLANG_CXX_LIBRARY = "libc++"; 317 | CLANG_ENABLE_MODULES = YES; 318 | CLANG_ENABLE_OBJC_ARC = YES; 319 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 320 | CLANG_WARN_BOOL_CONVERSION = YES; 321 | CLANG_WARN_COMMA = YES; 322 | CLANG_WARN_CONSTANT_CONVERSION = YES; 323 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 324 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 325 | CLANG_WARN_EMPTY_BODY = YES; 326 | CLANG_WARN_ENUM_CONVERSION = YES; 327 | CLANG_WARN_INFINITE_RECURSION = YES; 328 | CLANG_WARN_INT_CONVERSION = YES; 329 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 330 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 331 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 332 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 333 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 334 | CLANG_WARN_STRICT_PROTOTYPES = YES; 335 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 336 | CLANG_WARN_UNREACHABLE_CODE = YES; 337 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 338 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 339 | COPY_PHASE_STRIP = NO; 340 | DEBUG_INFORMATION_FORMAT = dwarf; 341 | ENABLE_STRICT_OBJC_MSGSEND = YES; 342 | ENABLE_TESTABILITY = YES; 343 | GCC_C_LANGUAGE_STANDARD = gnu99; 344 | GCC_DYNAMIC_NO_PIC = NO; 345 | GCC_NO_COMMON_BLOCKS = YES; 346 | GCC_OPTIMIZATION_LEVEL = 0; 347 | GCC_PREPROCESSOR_DEFINITIONS = ( 348 | "DEBUG=1", 349 | "$(inherited)", 350 | ); 351 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 352 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 353 | GCC_WARN_UNDECLARED_SELECTOR = YES; 354 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 355 | GCC_WARN_UNUSED_FUNCTION = YES; 356 | GCC_WARN_UNUSED_VARIABLE = YES; 357 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 358 | MTL_ENABLE_DEBUG_INFO = YES; 359 | ONLY_ACTIVE_ARCH = YES; 360 | SDKROOT = iphoneos; 361 | TARGETED_DEVICE_FAMILY = "1,2"; 362 | }; 363 | name = Debug; 364 | }; 365 | 97C147041CF9000F007C117D /* Release */ = { 366 | isa = XCBuildConfiguration; 367 | buildSettings = { 368 | ALWAYS_SEARCH_USER_PATHS = NO; 369 | CLANG_ANALYZER_NONNULL = YES; 370 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 371 | CLANG_CXX_LIBRARY = "libc++"; 372 | CLANG_ENABLE_MODULES = YES; 373 | CLANG_ENABLE_OBJC_ARC = YES; 374 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 375 | CLANG_WARN_BOOL_CONVERSION = YES; 376 | CLANG_WARN_COMMA = YES; 377 | CLANG_WARN_CONSTANT_CONVERSION = YES; 378 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 379 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 380 | CLANG_WARN_EMPTY_BODY = YES; 381 | CLANG_WARN_ENUM_CONVERSION = YES; 382 | CLANG_WARN_INFINITE_RECURSION = YES; 383 | CLANG_WARN_INT_CONVERSION = YES; 384 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 385 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 386 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 387 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 388 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 389 | CLANG_WARN_STRICT_PROTOTYPES = YES; 390 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 391 | CLANG_WARN_UNREACHABLE_CODE = YES; 392 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 393 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 394 | COPY_PHASE_STRIP = NO; 395 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 396 | ENABLE_NS_ASSERTIONS = NO; 397 | ENABLE_STRICT_OBJC_MSGSEND = YES; 398 | GCC_C_LANGUAGE_STANDARD = gnu99; 399 | GCC_NO_COMMON_BLOCKS = YES; 400 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 401 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 402 | GCC_WARN_UNDECLARED_SELECTOR = YES; 403 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 404 | GCC_WARN_UNUSED_FUNCTION = YES; 405 | GCC_WARN_UNUSED_VARIABLE = YES; 406 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 407 | MTL_ENABLE_DEBUG_INFO = NO; 408 | SDKROOT = iphoneos; 409 | SUPPORTED_PLATFORMS = iphoneos; 410 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 411 | TARGETED_DEVICE_FAMILY = "1,2"; 412 | VALIDATE_PRODUCT = YES; 413 | }; 414 | name = Release; 415 | }; 416 | 97C147061CF9000F007C117D /* Debug */ = { 417 | isa = XCBuildConfiguration; 418 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 419 | buildSettings = { 420 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 421 | CLANG_ENABLE_MODULES = YES; 422 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 423 | ENABLE_BITCODE = NO; 424 | FRAMEWORK_SEARCH_PATHS = ( 425 | "$(inherited)", 426 | "$(PROJECT_DIR)/Flutter", 427 | ); 428 | INFOPLIST_FILE = Runner/Info.plist; 429 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 430 | LIBRARY_SEARCH_PATHS = ( 431 | "$(inherited)", 432 | "$(PROJECT_DIR)/Flutter", 433 | ); 434 | PRODUCT_BUNDLE_IDENTIFIER = com.flutterbuddies.sudokuSolver; 435 | PRODUCT_NAME = "$(TARGET_NAME)"; 436 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 437 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 438 | SWIFT_VERSION = 5.0; 439 | VERSIONING_SYSTEM = "apple-generic"; 440 | }; 441 | name = Debug; 442 | }; 443 | 97C147071CF9000F007C117D /* Release */ = { 444 | isa = XCBuildConfiguration; 445 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 446 | buildSettings = { 447 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 448 | CLANG_ENABLE_MODULES = YES; 449 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 450 | ENABLE_BITCODE = NO; 451 | FRAMEWORK_SEARCH_PATHS = ( 452 | "$(inherited)", 453 | "$(PROJECT_DIR)/Flutter", 454 | ); 455 | INFOPLIST_FILE = Runner/Info.plist; 456 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 457 | LIBRARY_SEARCH_PATHS = ( 458 | "$(inherited)", 459 | "$(PROJECT_DIR)/Flutter", 460 | ); 461 | PRODUCT_BUNDLE_IDENTIFIER = com.flutterbuddies.sudokuSolver; 462 | PRODUCT_NAME = "$(TARGET_NAME)"; 463 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 464 | SWIFT_VERSION = 5.0; 465 | VERSIONING_SYSTEM = "apple-generic"; 466 | }; 467 | name = Release; 468 | }; 469 | /* End XCBuildConfiguration section */ 470 | 471 | /* Begin XCConfigurationList section */ 472 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 473 | isa = XCConfigurationList; 474 | buildConfigurations = ( 475 | 97C147031CF9000F007C117D /* Debug */, 476 | 97C147041CF9000F007C117D /* Release */, 477 | 249021D3217E4FDB00AE95B9 /* Profile */, 478 | ); 479 | defaultConfigurationIsVisible = 0; 480 | defaultConfigurationName = Release; 481 | }; 482 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 483 | isa = XCConfigurationList; 484 | buildConfigurations = ( 485 | 97C147061CF9000F007C117D /* Debug */, 486 | 97C147071CF9000F007C117D /* Release */, 487 | 249021D4217E4FDB00AE95B9 /* Profile */, 488 | ); 489 | defaultConfigurationIsVisible = 0; 490 | defaultConfigurationName = Release; 491 | }; 492 | /* End XCConfigurationList section */ 493 | }; 494 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 495 | } 496 | -------------------------------------------------------------------------------- /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.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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 UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /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/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Buddies/sudoku-solver/5174f39e4aff66eb21a29eaa4dfd702a5d082214/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 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | sudoku_solver 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | CFBundleLocalizations 45 | 46 | en 47 | ar 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /lib/constants/enums.dart: -------------------------------------------------------------------------------- 1 | enum SolveScreenStates { Idle, Loading, Solved, Error } 2 | enum BoardErrors { Duplicate, UnSolvable, None } 3 | -------------------------------------------------------------------------------- /lib/constants/example_boards.dart: -------------------------------------------------------------------------------- 1 | import '../models/board_square.dart'; 2 | import '../models/position_model.dart'; 3 | 4 | List> easyBoard = [ 5 | [ 6 | BoardSquare(position: Position(x: 0, y: 0), value: 0, userInputted: true), 7 | BoardSquare(position: Position(x: 0, y: 1), value: 0, userInputted: true), 8 | BoardSquare(position: Position(x: 0, y: 2), value: 0, userInputted: true), 9 | BoardSquare(position: Position(x: 0, y: 3), value: 2, userInputted: true), 10 | BoardSquare(position: Position(x: 0, y: 4), value: 6, userInputted: true), 11 | BoardSquare(position: Position(x: 0, y: 5), value: 0, userInputted: true), 12 | BoardSquare(position: Position(x: 0, y: 6), value: 7, userInputted: true), 13 | BoardSquare(position: Position(x: 0, y: 7), value: 0, userInputted: true), 14 | BoardSquare(position: Position(x: 0, y: 8), value: 1, userInputted: true) 15 | ], 16 | [ 17 | BoardSquare(position: Position(x: 1, y: 0), value: 6, userInputted: true), 18 | BoardSquare(position: Position(x: 1, y: 1), value: 8, userInputted: true), 19 | BoardSquare(position: Position(x: 1, y: 2), value: 0, userInputted: true), 20 | BoardSquare(position: Position(x: 1, y: 3), value: 0, userInputted: true), 21 | BoardSquare(position: Position(x: 1, y: 4), value: 7, userInputted: true), 22 | BoardSquare(position: Position(x: 1, y: 5), value: 0, userInputted: true), 23 | BoardSquare(position: Position(x: 1, y: 6), value: 0, userInputted: true), 24 | BoardSquare(position: Position(x: 1, y: 7), value: 9, userInputted: true), 25 | BoardSquare(position: Position(x: 1, y: 8), value: 0, userInputted: true) 26 | ], 27 | [ 28 | BoardSquare(position: Position(x: 2, y: 0), value: 1, userInputted: true), 29 | BoardSquare(position: Position(x: 2, y: 1), value: 9, userInputted: true), 30 | BoardSquare(position: Position(x: 2, y: 2), value: 0, userInputted: true), 31 | BoardSquare(position: Position(x: 2, y: 3), value: 0, userInputted: true), 32 | BoardSquare(position: Position(x: 2, y: 4), value: 0, userInputted: true), 33 | BoardSquare(position: Position(x: 2, y: 5), value: 4, userInputted: true), 34 | BoardSquare(position: Position(x: 2, y: 6), value: 5, userInputted: true), 35 | BoardSquare(position: Position(x: 2, y: 7), value: 0, userInputted: true), 36 | BoardSquare(position: Position(x: 2, y: 8), value: 0, userInputted: true) 37 | ], 38 | [ 39 | BoardSquare(position: Position(x: 3, y: 0), value: 8, userInputted: true), 40 | BoardSquare(position: Position(x: 3, y: 1), value: 2, userInputted: true), 41 | BoardSquare(position: Position(x: 3, y: 2), value: 0, userInputted: true), 42 | BoardSquare(position: Position(x: 3, y: 3), value: 1, userInputted: true), 43 | BoardSquare(position: Position(x: 3, y: 4), value: 0, userInputted: true), 44 | BoardSquare(position: Position(x: 3, y: 5), value: 0, userInputted: true), 45 | BoardSquare(position: Position(x: 3, y: 6), value: 0, userInputted: true), 46 | BoardSquare(position: Position(x: 3, y: 7), value: 4, userInputted: true), 47 | BoardSquare(position: Position(x: 3, y: 8), value: 0, userInputted: true) 48 | ], 49 | [ 50 | BoardSquare(position: Position(x: 4, y: 0), value: 0, userInputted: true), 51 | BoardSquare(position: Position(x: 4, y: 1), value: 0, userInputted: true), 52 | BoardSquare(position: Position(x: 4, y: 2), value: 4, userInputted: true), 53 | BoardSquare(position: Position(x: 4, y: 3), value: 6, userInputted: true), 54 | BoardSquare(position: Position(x: 4, y: 4), value: 0, userInputted: true), 55 | BoardSquare(position: Position(x: 4, y: 5), value: 2, userInputted: true), 56 | BoardSquare(position: Position(x: 4, y: 6), value: 9, userInputted: true), 57 | BoardSquare(position: Position(x: 4, y: 7), value: 0, userInputted: true), 58 | BoardSquare(position: Position(x: 4, y: 8), value: 0, userInputted: true) 59 | ], 60 | [ 61 | BoardSquare(position: Position(x: 5, y: 0), value: 0, userInputted: true), 62 | BoardSquare(position: Position(x: 5, y: 1), value: 5, userInputted: true), 63 | BoardSquare(position: Position(x: 5, y: 2), value: 0, userInputted: true), 64 | BoardSquare(position: Position(x: 5, y: 3), value: 0, userInputted: true), 65 | BoardSquare(position: Position(x: 5, y: 4), value: 0, userInputted: true), 66 | BoardSquare(position: Position(x: 5, y: 5), value: 3, userInputted: true), 67 | BoardSquare(position: Position(x: 5, y: 6), value: 0, userInputted: true), 68 | BoardSquare(position: Position(x: 5, y: 7), value: 2, userInputted: true), 69 | BoardSquare(position: Position(x: 5, y: 8), value: 8, userInputted: true) 70 | ], 71 | [ 72 | BoardSquare(position: Position(x: 6, y: 0), value: 0, userInputted: true), 73 | BoardSquare(position: Position(x: 6, y: 1), value: 0, userInputted: true), 74 | BoardSquare(position: Position(x: 6, y: 2), value: 9, userInputted: true), 75 | BoardSquare(position: Position(x: 6, y: 3), value: 3, userInputted: true), 76 | BoardSquare(position: Position(x: 6, y: 4), value: 0, userInputted: true), 77 | BoardSquare(position: Position(x: 6, y: 5), value: 0, userInputted: true), 78 | BoardSquare(position: Position(x: 6, y: 6), value: 0, userInputted: true), 79 | BoardSquare(position: Position(x: 6, y: 7), value: 7, userInputted: true), 80 | BoardSquare(position: Position(x: 6, y: 8), value: 4, userInputted: true) 81 | ], 82 | [ 83 | BoardSquare(position: Position(x: 7, y: 0), value: 0, userInputted: true), 84 | BoardSquare(position: Position(x: 7, y: 1), value: 4, userInputted: true), 85 | BoardSquare(position: Position(x: 7, y: 2), value: 0, userInputted: true), 86 | BoardSquare(position: Position(x: 7, y: 3), value: 0, userInputted: true), 87 | BoardSquare(position: Position(x: 7, y: 4), value: 5, userInputted: true), 88 | BoardSquare(position: Position(x: 7, y: 5), value: 0, userInputted: true), 89 | BoardSquare(position: Position(x: 7, y: 6), value: 0, userInputted: true), 90 | BoardSquare(position: Position(x: 7, y: 7), value: 3, userInputted: true), 91 | BoardSquare(position: Position(x: 7, y: 8), value: 6, userInputted: true) 92 | ], 93 | [ 94 | BoardSquare(position: Position(x: 8, y: 0), value: 7, userInputted: true), 95 | BoardSquare(position: Position(x: 8, y: 1), value: 0, userInputted: true), 96 | BoardSquare(position: Position(x: 8, y: 2), value: 3, userInputted: true), 97 | BoardSquare(position: Position(x: 8, y: 3), value: 0, userInputted: true), 98 | BoardSquare(position: Position(x: 8, y: 4), value: 1, userInputted: true), 99 | BoardSquare(position: Position(x: 8, y: 5), value: 8, userInputted: true), 100 | BoardSquare(position: Position(x: 8, y: 6), value: 0, userInputted: true), 101 | BoardSquare(position: Position(x: 8, y: 7), value: 0, userInputted: true), 102 | BoardSquare(position: Position(x: 8, y: 8), value: 0, userInputted: true) 103 | ], 104 | ]; 105 | 106 | List> mediumBoard = [ 107 | [ 108 | BoardSquare(position: Position(x: 0, y: 0), value: 0, userInputted: true), 109 | BoardSquare(position: Position(x: 0, y: 1), value: 2, userInputted: true), 110 | BoardSquare(position: Position(x: 0, y: 2), value: 0, userInputted: true), 111 | BoardSquare(position: Position(x: 0, y: 3), value: 6, userInputted: true), 112 | BoardSquare(position: Position(x: 0, y: 4), value: 0, userInputted: true), 113 | BoardSquare(position: Position(x: 0, y: 5), value: 8, userInputted: true), 114 | BoardSquare(position: Position(x: 0, y: 6), value: 0, userInputted: true), 115 | BoardSquare(position: Position(x: 0, y: 7), value: 0, userInputted: true), 116 | BoardSquare(position: Position(x: 0, y: 8), value: 0, userInputted: true) 117 | ], 118 | [ 119 | BoardSquare(position: Position(x: 1, y: 0), value: 5, userInputted: true), 120 | BoardSquare(position: Position(x: 1, y: 1), value: 8, userInputted: true), 121 | BoardSquare(position: Position(x: 1, y: 2), value: 0, userInputted: true), 122 | BoardSquare(position: Position(x: 1, y: 3), value: 0, userInputted: true), 123 | BoardSquare(position: Position(x: 1, y: 4), value: 0, userInputted: true), 124 | BoardSquare(position: Position(x: 1, y: 5), value: 9, userInputted: true), 125 | BoardSquare(position: Position(x: 1, y: 6), value: 7, userInputted: true), 126 | BoardSquare(position: Position(x: 1, y: 7), value: 0, userInputted: true), 127 | BoardSquare(position: Position(x: 1, y: 8), value: 0, userInputted: true) 128 | ], 129 | [ 130 | BoardSquare(position: Position(x: 2, y: 0), value: 0, userInputted: true), 131 | BoardSquare(position: Position(x: 2, y: 1), value: 0, userInputted: true), 132 | BoardSquare(position: Position(x: 2, y: 2), value: 0, userInputted: true), 133 | BoardSquare(position: Position(x: 2, y: 3), value: 0, userInputted: true), 134 | BoardSquare(position: Position(x: 2, y: 4), value: 4, userInputted: true), 135 | BoardSquare(position: Position(x: 2, y: 5), value: 0, userInputted: true), 136 | BoardSquare(position: Position(x: 2, y: 6), value: 0, userInputted: true), 137 | BoardSquare(position: Position(x: 2, y: 7), value: 0, userInputted: true), 138 | BoardSquare(position: Position(x: 2, y: 8), value: 0, userInputted: true) 139 | ], 140 | [ 141 | BoardSquare(position: Position(x: 3, y: 0), value: 3, userInputted: true), 142 | BoardSquare(position: Position(x: 3, y: 1), value: 7, userInputted: true), 143 | BoardSquare(position: Position(x: 3, y: 2), value: 0, userInputted: true), 144 | BoardSquare(position: Position(x: 3, y: 3), value: 0, userInputted: true), 145 | BoardSquare(position: Position(x: 3, y: 4), value: 0, userInputted: true), 146 | BoardSquare(position: Position(x: 3, y: 5), value: 0, userInputted: true), 147 | BoardSquare(position: Position(x: 3, y: 6), value: 5, userInputted: true), 148 | BoardSquare(position: Position(x: 3, y: 7), value: 0, userInputted: true), 149 | BoardSquare(position: Position(x: 3, y: 8), value: 0, userInputted: true) 150 | ], 151 | [ 152 | BoardSquare(position: Position(x: 4, y: 0), value: 6, userInputted: true), 153 | BoardSquare(position: Position(x: 4, y: 1), value: 0, userInputted: true), 154 | BoardSquare(position: Position(x: 4, y: 2), value: 0, userInputted: true), 155 | BoardSquare(position: Position(x: 4, y: 3), value: 0, userInputted: true), 156 | BoardSquare(position: Position(x: 4, y: 4), value: 0, userInputted: true), 157 | BoardSquare(position: Position(x: 4, y: 5), value: 0, userInputted: true), 158 | BoardSquare(position: Position(x: 4, y: 6), value: 0, userInputted: true), 159 | BoardSquare(position: Position(x: 4, y: 7), value: 0, userInputted: true), 160 | BoardSquare(position: Position(x: 4, y: 8), value: 4, userInputted: true) 161 | ], 162 | [ 163 | BoardSquare(position: Position(x: 5, y: 0), value: 0, userInputted: true), 164 | BoardSquare(position: Position(x: 5, y: 1), value: 0, userInputted: true), 165 | BoardSquare(position: Position(x: 5, y: 2), value: 8, userInputted: true), 166 | BoardSquare(position: Position(x: 5, y: 3), value: 0, userInputted: true), 167 | BoardSquare(position: Position(x: 5, y: 4), value: 0, userInputted: true), 168 | BoardSquare(position: Position(x: 5, y: 5), value: 0, userInputted: true), 169 | BoardSquare(position: Position(x: 5, y: 6), value: 0, userInputted: true), 170 | BoardSquare(position: Position(x: 5, y: 7), value: 1, userInputted: true), 171 | BoardSquare(position: Position(x: 5, y: 8), value: 3, userInputted: true) 172 | ], 173 | [ 174 | BoardSquare(position: Position(x: 6, y: 0), value: 0, userInputted: true), 175 | BoardSquare(position: Position(x: 6, y: 1), value: 0, userInputted: true), 176 | BoardSquare(position: Position(x: 6, y: 2), value: 0, userInputted: true), 177 | BoardSquare(position: Position(x: 6, y: 3), value: 0, userInputted: true), 178 | BoardSquare(position: Position(x: 6, y: 4), value: 2, userInputted: true), 179 | BoardSquare(position: Position(x: 6, y: 5), value: 0, userInputted: true), 180 | BoardSquare(position: Position(x: 6, y: 6), value: 0, userInputted: true), 181 | BoardSquare(position: Position(x: 6, y: 7), value: 0, userInputted: true), 182 | BoardSquare(position: Position(x: 6, y: 8), value: 0, userInputted: true) 183 | ], 184 | [ 185 | BoardSquare(position: Position(x: 7, y: 0), value: 0, userInputted: true), 186 | BoardSquare(position: Position(x: 7, y: 1), value: 0, userInputted: true), 187 | BoardSquare(position: Position(x: 7, y: 2), value: 9, userInputted: true), 188 | BoardSquare(position: Position(x: 7, y: 3), value: 8, userInputted: true), 189 | BoardSquare(position: Position(x: 7, y: 4), value: 0, userInputted: true), 190 | BoardSquare(position: Position(x: 7, y: 5), value: 0, userInputted: true), 191 | BoardSquare(position: Position(x: 7, y: 6), value: 0, userInputted: true), 192 | BoardSquare(position: Position(x: 7, y: 7), value: 3, userInputted: true), 193 | BoardSquare(position: Position(x: 7, y: 8), value: 6, userInputted: true) 194 | ], 195 | [ 196 | BoardSquare(position: Position(x: 8, y: 0), value: 0, userInputted: true), 197 | BoardSquare(position: Position(x: 8, y: 1), value: 0, userInputted: true), 198 | BoardSquare(position: Position(x: 8, y: 2), value: 0, userInputted: true), 199 | BoardSquare(position: Position(x: 8, y: 3), value: 3, userInputted: true), 200 | BoardSquare(position: Position(x: 8, y: 4), value: 0, userInputted: true), 201 | BoardSquare(position: Position(x: 8, y: 5), value: 6, userInputted: true), 202 | BoardSquare(position: Position(x: 8, y: 6), value: 0, userInputted: true), 203 | BoardSquare(position: Position(x: 8, y: 7), value: 9, userInputted: true), 204 | BoardSquare(position: Position(x: 8, y: 8), value: 0, userInputted: true) 205 | ], 206 | ]; 207 | 208 | List> hardBoard = [ 209 | [ 210 | BoardSquare(position: Position(x: 0, y: 0), value: 0, userInputted: true), 211 | BoardSquare(position: Position(x: 0, y: 1), value: 0, userInputted: true), 212 | BoardSquare(position: Position(x: 0, y: 2), value: 0, userInputted: true), 213 | BoardSquare(position: Position(x: 0, y: 3), value: 6, userInputted: true), 214 | BoardSquare(position: Position(x: 0, y: 4), value: 0, userInputted: true), 215 | BoardSquare(position: Position(x: 0, y: 5), value: 0, userInputted: true), 216 | BoardSquare(position: Position(x: 0, y: 6), value: 4, userInputted: true), 217 | BoardSquare(position: Position(x: 0, y: 7), value: 0, userInputted: true), 218 | BoardSquare(position: Position(x: 0, y: 8), value: 0, userInputted: true) 219 | ], 220 | [ 221 | BoardSquare(position: Position(x: 1, y: 0), value: 7, userInputted: true), 222 | BoardSquare(position: Position(x: 1, y: 1), value: 0, userInputted: true), 223 | BoardSquare(position: Position(x: 1, y: 2), value: 0, userInputted: true), 224 | BoardSquare(position: Position(x: 1, y: 3), value: 0, userInputted: true), 225 | BoardSquare(position: Position(x: 1, y: 4), value: 0, userInputted: true), 226 | BoardSquare(position: Position(x: 1, y: 5), value: 3, userInputted: true), 227 | BoardSquare(position: Position(x: 1, y: 6), value: 6, userInputted: true), 228 | BoardSquare(position: Position(x: 1, y: 7), value: 0, userInputted: true), 229 | BoardSquare(position: Position(x: 1, y: 8), value: 0, userInputted: true) 230 | ], 231 | [ 232 | BoardSquare(position: Position(x: 2, y: 0), value: 0, userInputted: true), 233 | BoardSquare(position: Position(x: 2, y: 1), value: 0, userInputted: true), 234 | BoardSquare(position: Position(x: 2, y: 2), value: 0, userInputted: true), 235 | BoardSquare(position: Position(x: 2, y: 3), value: 0, userInputted: true), 236 | BoardSquare(position: Position(x: 2, y: 4), value: 9, userInputted: true), 237 | BoardSquare(position: Position(x: 2, y: 5), value: 1, userInputted: true), 238 | BoardSquare(position: Position(x: 2, y: 6), value: 0, userInputted: true), 239 | BoardSquare(position: Position(x: 2, y: 7), value: 8, userInputted: true), 240 | BoardSquare(position: Position(x: 2, y: 8), value: 0, userInputted: true) 241 | ], 242 | [ 243 | BoardSquare(position: Position(x: 3, y: 0), value: 0, userInputted: true), 244 | BoardSquare(position: Position(x: 3, y: 1), value: 0, userInputted: true), 245 | BoardSquare(position: Position(x: 3, y: 2), value: 0, userInputted: true), 246 | BoardSquare(position: Position(x: 3, y: 3), value: 0, userInputted: true), 247 | BoardSquare(position: Position(x: 3, y: 4), value: 0, userInputted: true), 248 | BoardSquare(position: Position(x: 3, y: 5), value: 0, userInputted: true), 249 | BoardSquare(position: Position(x: 3, y: 6), value: 0, userInputted: true), 250 | BoardSquare(position: Position(x: 3, y: 7), value: 0, userInputted: true), 251 | BoardSquare(position: Position(x: 3, y: 8), value: 0, userInputted: true) 252 | ], 253 | [ 254 | BoardSquare(position: Position(x: 4, y: 0), value: 0, userInputted: true), 255 | BoardSquare(position: Position(x: 4, y: 1), value: 5, userInputted: true), 256 | BoardSquare(position: Position(x: 4, y: 2), value: 0, userInputted: true), 257 | BoardSquare(position: Position(x: 4, y: 3), value: 1, userInputted: true), 258 | BoardSquare(position: Position(x: 4, y: 4), value: 8, userInputted: true), 259 | BoardSquare(position: Position(x: 4, y: 5), value: 0, userInputted: true), 260 | BoardSquare(position: Position(x: 4, y: 6), value: 0, userInputted: true), 261 | BoardSquare(position: Position(x: 4, y: 7), value: 0, userInputted: true), 262 | BoardSquare(position: Position(x: 4, y: 8), value: 3, userInputted: true) 263 | ], 264 | [ 265 | BoardSquare(position: Position(x: 5, y: 0), value: 0, userInputted: true), 266 | BoardSquare(position: Position(x: 5, y: 1), value: 0, userInputted: true), 267 | BoardSquare(position: Position(x: 5, y: 2), value: 0, userInputted: true), 268 | BoardSquare(position: Position(x: 5, y: 3), value: 3, userInputted: true), 269 | BoardSquare(position: Position(x: 5, y: 4), value: 0, userInputted: true), 270 | BoardSquare(position: Position(x: 5, y: 5), value: 6, userInputted: true), 271 | BoardSquare(position: Position(x: 5, y: 6), value: 0, userInputted: true), 272 | BoardSquare(position: Position(x: 5, y: 7), value: 4, userInputted: true), 273 | BoardSquare(position: Position(x: 5, y: 8), value: 5, userInputted: true) 274 | ], 275 | [ 276 | BoardSquare(position: Position(x: 6, y: 0), value: 0, userInputted: true), 277 | BoardSquare(position: Position(x: 6, y: 1), value: 4, userInputted: true), 278 | BoardSquare(position: Position(x: 6, y: 2), value: 0, userInputted: true), 279 | BoardSquare(position: Position(x: 6, y: 3), value: 2, userInputted: true), 280 | BoardSquare(position: Position(x: 6, y: 4), value: 0, userInputted: true), 281 | BoardSquare(position: Position(x: 6, y: 5), value: 0, userInputted: true), 282 | BoardSquare(position: Position(x: 6, y: 6), value: 0, userInputted: true), 283 | BoardSquare(position: Position(x: 6, y: 7), value: 6, userInputted: true), 284 | BoardSquare(position: Position(x: 6, y: 8), value: 0, userInputted: true) 285 | ], 286 | [ 287 | BoardSquare(position: Position(x: 7, y: 0), value: 9, userInputted: true), 288 | BoardSquare(position: Position(x: 7, y: 1), value: 0, userInputted: true), 289 | BoardSquare(position: Position(x: 7, y: 2), value: 3, userInputted: true), 290 | BoardSquare(position: Position(x: 7, y: 3), value: 0, userInputted: true), 291 | BoardSquare(position: Position(x: 7, y: 4), value: 0, userInputted: true), 292 | BoardSquare(position: Position(x: 7, y: 5), value: 0, userInputted: true), 293 | BoardSquare(position: Position(x: 7, y: 6), value: 0, userInputted: true), 294 | BoardSquare(position: Position(x: 7, y: 7), value: 0, userInputted: true), 295 | BoardSquare(position: Position(x: 7, y: 8), value: 0, userInputted: true) 296 | ], 297 | [ 298 | BoardSquare(position: Position(x: 8, y: 0), value: 0, userInputted: true), 299 | BoardSquare(position: Position(x: 8, y: 1), value: 2, userInputted: true), 300 | BoardSquare(position: Position(x: 8, y: 2), value: 0, userInputted: true), 301 | BoardSquare(position: Position(x: 8, y: 3), value: 0, userInputted: true), 302 | BoardSquare(position: Position(x: 8, y: 4), value: 0, userInputted: true), 303 | BoardSquare(position: Position(x: 8, y: 5), value: 0, userInputted: true), 304 | BoardSquare(position: Position(x: 8, y: 6), value: 1, userInputted: true), 305 | BoardSquare(position: Position(x: 8, y: 7), value: 0, userInputted: true), 306 | BoardSquare(position: Position(x: 8, y: 8), value: 0, userInputted: true) 307 | ], 308 | ]; 309 | -------------------------------------------------------------------------------- /lib/constants/ui_constants.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | const Color kPrimaryColor = const Color(0xff006ef4); 4 | const Color kLigherBlueColor = const Color(0xff006bee); 5 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:easy_localization/easy_localization.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/services.dart'; 4 | import 'package:provider/provider.dart'; 5 | import 'package:sudoku_solver/translations/codegen_loader.g.dart'; 6 | 7 | import 'models/sudoku_grid.dart'; 8 | import 'screens/solve_screen.dart'; 9 | 10 | Future main() async { 11 | WidgetsFlutterBinding.ensureInitialized(); 12 | 13 | await EasyLocalization.ensureInitialized(); 14 | 15 | SystemChrome.setSystemUIOverlayStyle( 16 | SystemUiOverlayStyle(statusBarColor: Colors.blueAccent)); 17 | runApp( 18 | EasyLocalization( 19 | supportedLocales: [ 20 | Locale('en'), 21 | Locale('ar'), 22 | ], 23 | fallbackLocale: Locale('en'), 24 | path: 'assets/translations', 25 | assetLoader: CodegenLoader(), 26 | child: MyApp(), 27 | ), 28 | ); 29 | } 30 | 31 | class MyApp extends StatelessWidget { 32 | @override 33 | Widget build(BuildContext context) { 34 | return ChangeNotifierProvider( 35 | create: (context) => SudokuGrid.blank(), 36 | child: MaterialApp( 37 | debugShowCheckedModeBanner: false, 38 | title: 'Sudoku Solver', 39 | theme: ThemeData( 40 | brightness: Brightness.light, 41 | primarySwatch: Colors.blue, 42 | visualDensity: VisualDensity.adaptivePlatformDensity, 43 | ), 44 | locale: context.locale, 45 | supportedLocales: context.supportedLocales, 46 | localizationsDelegates: context.localizationDelegates, 47 | initialRoute: 'solve', 48 | routes: { 49 | 'solve': (context) => SolveScreen(), 50 | }, 51 | ), 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/models/board_square.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'position_model.dart'; 3 | 4 | class BoardSquare extends ChangeNotifier { 5 | Position position; 6 | int value; 7 | bool userInputted; 8 | bool hasError; 9 | 10 | BoardSquare({ 11 | @required this.position, 12 | @required this.value, 13 | this.userInputted = false, 14 | this.hasError = false, 15 | }); 16 | 17 | void updateValue(int i) { 18 | value = i; 19 | userInputted = true; 20 | hasError = false; 21 | notifyListeners(); 22 | } 23 | 24 | String toString() { 25 | return value.toString(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/models/check_legal_fn.dart: -------------------------------------------------------------------------------- 1 | bool checkLegal(List> board) { 2 | // 1. Create a list of rows, columns and 3x3 grids 3 | List> subsets = _createRowSubSet(board) + 4 | _createColumnSubSet(board) + 5 | _createThreeXThreeSubSet(board); 6 | 7 | // 2. Iterate through each subset and make sure there are no duplicate values (except for 0's) 8 | var isUnique = true; // Variable to hold if there are duplicates. 9 | for (List list in subsets) { 10 | // Compare a single value in a list against the other values 11 | List set = []; 12 | for (int square in list) { 13 | // If square value is in set then it means there is a duplicate 14 | // If it is not, add the square to set 15 | // Wanted to do list.contains(square) but it obviously will 16 | if (set.contains(square) && square != 0) { 17 | isUnique = false; 18 | // break used because there's no point carrying on the loop 19 | break; 20 | } else { 21 | set.add(square); 22 | } 23 | } 24 | // Todo: Test this 25 | // If at any point isUnique is equal to false the loop should be broken 26 | if (isUnique == false) { 27 | break; 28 | } 29 | } 30 | return isUnique; 31 | } 32 | 33 | //* Helper functions to generate sublists 34 | // Create a list of rows. Rows are of type List 35 | // board[0] is the first row of a sudoku board 36 | List> _createRowSubSet(List> board) { 37 | List> listOfRows = []; 38 | for (int i = 0; i < 9; i++) { 39 | listOfRows.add(board[i]); 40 | } 41 | return listOfRows; 42 | } 43 | 44 | // Create a list of columns. Columns are of type List 45 | // This is harder because it requires accessing each row then column 46 | // board[0][0] + board[0][1] + ... + board[0][n] is the first column 47 | List> _createColumnSubSet(List> board) { 48 | List> listOfColumns = []; 49 | for (int i = 0; i < 9; i++) { 50 | List listOfSquares = []; 51 | for (int j = 0; j < 9; j++) { 52 | listOfSquares.add(board[j][i]); 53 | } 54 | listOfColumns.add(listOfSquares); 55 | } 56 | return listOfColumns; 57 | } 58 | 59 | // Create a list of 3x3 grids. 3x3 grids are of type List 60 | // board[0][0] + board[0][1] + board[0][2] + 61 | // board[1][0] + board[1][1] + board[1][2] + 62 | // board[2][0] + board[2][1] + board[2][2] is the top left 3x3 grid 63 | List> _createThreeXThreeSubSet(List> board) { 64 | List> listOfThreeXThree = []; 65 | for (int i = 0; i < 9; i += 3) { 66 | for (int j = 0; j < 9; j += 3) { 67 | List subListOfSquares = []; 68 | subListOfSquares.add(board[i][j]); 69 | subListOfSquares.add(board[i][j + 1]); 70 | subListOfSquares.add(board[i][j + 2]); 71 | subListOfSquares.add(board[i + 1][j]); 72 | subListOfSquares.add(board[i + 1][j + 1]); 73 | subListOfSquares.add(board[i + 1][j + 2]); 74 | subListOfSquares.add(board[i + 2][j]); 75 | subListOfSquares.add(board[i + 2][j + 1]); 76 | subListOfSquares.add(board[i + 2][j + 2]); 77 | listOfThreeXThree.add(subListOfSquares); 78 | } 79 | } 80 | return listOfThreeXThree; 81 | } 82 | -------------------------------------------------------------------------------- /lib/models/old_solve_fn.dart: -------------------------------------------------------------------------------- 1 | // Function to fill a given square with numbers 1 - 9 and produce a list of boards 2 | import 'package:sudoku_solver/models/position_model.dart'; 3 | 4 | import 'board_square.dart'; 5 | 6 | List> solveBoard(List> simpleBoard) { 7 | // Convert int board into list of board squares 8 | List> board = convertIntToBoard(simpleBoard); 9 | 10 | // Flatten the board 11 | List flatBoard = board.expand((element) => element).toList(); 12 | 13 | // Get the position of the next blank space 14 | Position position = getNextEmptySquare(flatBoard); 15 | 16 | // Pass the position and board to create new boards 17 | List>> newBoards = createNewBoards(position, board); 18 | 19 | // If board is valid add to valid boards, if not, do recursion 20 | for (List> board in newBoards) { 21 | // First need to make sure they're a legal board 22 | // If legal continue, if not, stop 23 | if (checkLegal(board) == true) { 24 | // See if board is empty 25 | // If empty, iterate. If not the board is complete 26 | if (hasBlanks(board) == false) { 27 | return board; 28 | } else { 29 | List> unsolvedBoard = solveBoard( 30 | List.generate( 31 | 9, 32 | (int row) => List.generate( 33 | 9, 34 | (int column) => board[row][column].value, 35 | ), 36 | ), 37 | ); 38 | if (unsolvedBoard != null) { 39 | return unsolvedBoard; 40 | } 41 | } 42 | } 43 | } 44 | } 45 | 46 | // Helper function to find the position of the next empty square 47 | Position getNextEmptySquare(List flatBoard) { 48 | BoardSquare square = flatBoard.firstWhere((element) => element.value == 0); 49 | return square.position; 50 | } 51 | 52 | // Helper function to fill a given position with the numbers 1 - 9 and return the list of Boards 53 | List>> createNewBoards( 54 | Position position, List> board) { 55 | // Variable to hold new boards 56 | List>> updatedBoards = []; 57 | 58 | // Use position to replace list at given index 59 | for (int i = 1; i < 10; i++) { 60 | // Duplicate board 61 | List> newBoard = createNewBoard(board); 62 | // Update value of new board 63 | newBoard[position.x][position.y].value = i; 64 | // Add new board to list 65 | updatedBoards.add(newBoard); 66 | } 67 | return updatedBoards; 68 | } 69 | 70 | // Function to check if the board has any blanks. Used to check if board is solved. 71 | bool hasBlanks(List> board) { 72 | bool noBlanks = true; 73 | // Iterate through the board to see if any value is equal to 0 74 | // If a 0 is found, it means there is still blank spaces 75 | for (List row in board) { 76 | noBlanks = row.any((element) => element.value == 0); 77 | } 78 | return noBlanks; 79 | } 80 | 81 | // Function to check that each sublist has a unique number 82 | // Returns a List where the 0th element is a bool and the 1st element is 83 | // a list of Position objects 84 | List checkLegal(List> board) { 85 | List> subsets = createFullSublist(board); 86 | var isUnique = [ 87 | true, 88 | [Position(), Position()] 89 | ]; 90 | for (List boardList in subsets) { 91 | List set = []; 92 | List newSet = []; 93 | // Check if values are unique in a given sublist 94 | for (BoardSquare square in boardList) { 95 | for (BoardSquare square1 in newSet) { 96 | if (square.value == square1.value && square.value != 0) { 97 | isUnique[0] = false; 98 | isUnique[1] = [square.position, square1.position]; 99 | break; 100 | } 101 | } 102 | // if (set.contains(square.value) == true && square.value != 0) { 103 | // isUnique[0] = false; 104 | // isUnique[1] = [square.position]; 105 | // break; 106 | // } else { 107 | // set.add(square.value); 108 | // } 109 | newSet.add(square); 110 | } 111 | } 112 | return isUnique; 113 | } 114 | 115 | // Function to join all sub lists into one big list which can iterated over to check for duplicate numbers 116 | List> createFullSublist(List> board) { 117 | List> combinedList = []; 118 | List> rows = getSublistOfRows(board); 119 | List> columns = getSublistOfColumns(board); 120 | List> threeXThree = getSublistThreeXThree(board); 121 | combinedList = rows + columns + threeXThree; 122 | return combinedList; 123 | } 124 | 125 | // Function to get a list of Rows. This is easy. 126 | List> getSublistOfRows(List> board) { 127 | List> _listOfRows = []; 128 | for (int i = 0; i < 9; i++) { 129 | _listOfRows.add(board[i]); 130 | } 131 | return _listOfRows; 132 | } 133 | 134 | // Function to get a list of Columns. This was harder because it required nested for loops 135 | List> getSublistOfColumns(List> board) { 136 | List> _listOfColumns = []; 137 | for (int i = 0; i < 9; i++) { 138 | List _listOfBoardSquares = []; 139 | for (int j = 0; j < 9; j++) { 140 | _listOfBoardSquares.add(board[j][i]); 141 | } 142 | _listOfColumns.add(_listOfBoardSquares); 143 | } 144 | return _listOfColumns; 145 | } 146 | 147 | // Function to create list of the small 3x3 grids 148 | List> getSublistThreeXThree(List> board) { 149 | List> _listOfThreeXThree = []; 150 | for (int i = 0; i < 9; i += 3) { 151 | for (int j = 0; j < 9; j += 3) { 152 | List _subListOfBoardSquares = []; 153 | _subListOfBoardSquares.add(board[i][j]); 154 | _subListOfBoardSquares.add(board[i][j + 1]); 155 | _subListOfBoardSquares.add(board[i][j + 2]); 156 | _subListOfBoardSquares.add(board[i + 1][j]); 157 | _subListOfBoardSquares.add(board[i + 1][j + 1]); 158 | _subListOfBoardSquares.add(board[i + 1][j + 2]); 159 | _subListOfBoardSquares.add(board[i + 2][j]); 160 | _subListOfBoardSquares.add(board[i + 2][j + 1]); 161 | _subListOfBoardSquares.add(board[i + 2][j + 2]); 162 | _listOfThreeXThree.add(_subListOfBoardSquares); 163 | } 164 | } 165 | return _listOfThreeXThree; 166 | } 167 | 168 | // Helper function to create a new board from existing board 169 | List> createNewBoard(List> oldBoard) { 170 | List> newBoard = List.generate( 171 | 9, 172 | (int row) => List.generate( 173 | 9, 174 | (int column) => BoardSquare( 175 | position: Position(x: row, y: column), 176 | value: oldBoard[row][column].value), 177 | ), 178 | ); 179 | return newBoard; 180 | } 181 | 182 | // Helper function to convert an ordered list of numbers into a Board 183 | List> convertIntToBoard(List> simpleBoard) { 184 | List> newBoard = List.generate( 185 | 9, 186 | (int row) => List.generate( 187 | 9, 188 | (int column) => BoardSquare( 189 | position: Position(x: row, y: column), 190 | value: simpleBoard[row][column]), 191 | ), 192 | ); 193 | return newBoard; 194 | } 195 | -------------------------------------------------------------------------------- /lib/models/position_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | class Position extends Equatable { 4 | final int x; 5 | final int y; 6 | 7 | Position({this.x, this.y}); 8 | 9 | @override 10 | String toString() => 'Position(x: $x, y: $y)'; 11 | 12 | @override 13 | List get props => [x, y]; 14 | } 15 | -------------------------------------------------------------------------------- /lib/models/solve_fn.dart: -------------------------------------------------------------------------------- 1 | import 'position_model.dart'; 2 | 3 | import 'check_legal_fn.dart' as legal; 4 | 5 | // Assume `solveBoard` is passed a legal board with an empty space 6 | List> solveBoard(List> board) { 7 | // 1. First we need to find the Position in the board where there is an empty square 8 | var position = _nextEmptySquare(board); 9 | 10 | // 2. Next we want to create 9 new boards with the empty space filled with [1-9] 11 | var newBoards = _createNewBoards(position, board); 12 | 13 | // 3. Iterate through the list of new boards 14 | for (List> board in newBoards) { 15 | // 4. Check if new board is legal 16 | if (legal.checkLegal(board)) { 17 | // 5. See if the board has any blank spaces 18 | if (_hasBlanks(board) == false) { 19 | // 6a. If there are no blanks, the puzzle is solved and board can be returned 20 | return board; 21 | } else { 22 | // 6b. If there are blanks, then it needs to be passed to the next recursion 23 | // Board could be null because null could be returned from some branches or recursion 24 | // and need to be dropped (not returned) 25 | List> unsolvedBoard = solveBoard(board); 26 | if (unsolvedBoard != null) { 27 | return unsolvedBoard; 28 | } 29 | } 30 | } else { 31 | // Drop the board because it is not legal 32 | } 33 | } 34 | return null; 35 | } 36 | 37 | // Helper function to `solveBoard` that finds the [Position] of the next empty (0) square 38 | Position _nextEmptySquare(List> board) { 39 | Position position; 40 | // Nested for loop to generate a 9 x 9 matrix 41 | for (int i = 0; i < 9; i++) { 42 | for (int j = 0; j < 9; j++) { 43 | if (board[i][j] == 0) { 44 | position = Position(x: i, y: j); 45 | // Break the inner for loop 46 | break; 47 | } 48 | } 49 | // Break the outer for loop if a position has been found 50 | if (position != null) { 51 | break; 52 | } 53 | } 54 | return position; 55 | } 56 | 57 | // Return a list of new boards given a board and a position 58 | List>> _createNewBoards( 59 | Position position, List> board) { 60 | List>> newBoards = []; 61 | // Generate numbers [1-9] 62 | for (int i = 1; i < 10; i++) { 63 | List> newBoard = _copyBoard(board); 64 | newBoard[position.x][position.y] = i; 65 | // print('createNewBoards: New Board: $newBoard'); 66 | newBoards.add(newBoard); 67 | } 68 | 69 | return newBoards; 70 | } 71 | 72 | // Helper function to `_createNewBoards` 73 | // To duplicate the board we need to use .from method and on each row 74 | List> _copyBoard(List> board) { 75 | List> newBoard = []; 76 | for (List row in board) { 77 | // newBoard.add(List.from(row)); 78 | List newRow = row.map((e) => e).toList(); 79 | newBoard.add(newRow); 80 | } 81 | // print('Copy Board: new board = $newBoard'); 82 | return newBoard; 83 | } 84 | 85 | // Helper function to see if there are any blank spaces (i.e. int's == 0) 86 | // Iterates through a row and returns true if there are blanks 87 | bool _hasBlanks(List> board) { 88 | // Iterate through the board to see if any value is equal to 0 89 | // If a 0 is found, it means there is still blank spaces 90 | for (List row in board) { 91 | for (int square in row) { 92 | if (square == 0) { 93 | return true; 94 | } 95 | } 96 | } 97 | return false; 98 | } 99 | -------------------------------------------------------------------------------- /lib/models/sudoku_grid.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../constants/enums.dart'; 3 | import 'board_square.dart'; 4 | import 'position_model.dart'; 5 | import 'dart:async'; 6 | import 'package:flutter/foundation.dart'; 7 | import 'solve_fn.dart' as solve; 8 | import 'old_solve_fn.dart' as oldSolve; 9 | 10 | class SudokuGrid extends ChangeNotifier { 11 | List> userBoard; 12 | final int width = 9; 13 | final int height = 9; 14 | int selectedNumber = 0; 15 | SolveScreenStates solveScreenStates = SolveScreenStates.Idle; 16 | BoardErrors boardErrors = BoardErrors.None; 17 | 18 | // Generic constructor 19 | SudokuGrid({this.userBoard}); 20 | 21 | // Named constructor to build a blank board 22 | SudokuGrid.blank() { 23 | this.userBoard = List.generate( 24 | width, 25 | (int row) => List.generate( 26 | height, 27 | (int column) => 28 | BoardSquare(position: Position(x: row, y: column), value: 0), 29 | growable: false, 30 | ), 31 | growable: false, 32 | ); 33 | } 34 | 35 | // Named constructor to build a board from a template (see lib/constants/example_bards.dart) 36 | SudokuGrid.fromTemplate(List> templateBoard) { 37 | userBoard = templateBoard; 38 | } 39 | 40 | // Cycle through each board square and set it's value to 0 41 | // And update any board states; selectedNumber, state, errors 42 | void resetBoard() { 43 | userBoard.forEach((row) { 44 | row.forEach((boardSquare) { 45 | boardSquare.value = 0; 46 | boardSquare.hasError = false; 47 | boardSquare.userInputted = false; 48 | }); 49 | }); 50 | this.selectedNumber = 0; 51 | solveScreenStates = SolveScreenStates.Idle; 52 | boardErrors = BoardErrors.None; 53 | notifyListeners(); 54 | } 55 | 56 | // Setter to update the board variable 57 | set updateUIBoard(List> board) { 58 | this.userBoard = board; 59 | notifyListeners(); 60 | } 61 | 62 | // 'Parent' function to call when a user selects to solve board 63 | // Async function as it will take time to complete 64 | // Returns void as the method updates the instance varable `userBoard` 65 | Future solveButtonPress(List> board) async { 66 | // First and only once convert the board into a list of ints 67 | List> intBoard = _convertBoardToInts(board); 68 | 69 | // Store locations of user inputted numbers 70 | List positions = _userInputtedNumbers(board); 71 | 72 | // If on first pass the board is illegal we should show which numbers 73 | // are the offending ones 74 | if (oldSolve.checkLegal(board)[0] == false) { 75 | // If there are errors we want to update them 76 | // Todo: Store result from `checklegal` call so it doesn't need to be called twice 77 | _updateBoardWithErrors(oldSolve.checkLegal(board)[1], board); 78 | solveScreenStates = SolveScreenStates.Error; 79 | boardErrors = BoardErrors.Duplicate; 80 | notifyListeners(); 81 | } else { 82 | boardErrors = BoardErrors.None; 83 | solveScreenStates = SolveScreenStates.Loading; 84 | notifyListeners(); 85 | 86 | // Run the computation 87 | List> result = await compute(solve.solveBoard, intBoard); 88 | print('Result: $result'); 89 | if (result != null) { 90 | // Convert the list of ints back to a board and set userBoard to result 91 | userBoard = _convertIntsToBoard(result); 92 | 93 | // Iterate through stored numbers and update `userBoard` so user inputted 94 | // numbers can be highlighted 95 | _updateUserInputtedNumbers(positions, userBoard); 96 | 97 | solveScreenStates = SolveScreenStates.Solved; 98 | notifyListeners(); 99 | } else { 100 | // If there is no answer to a puzzle `solveBoard` will return null 101 | solveScreenStates = SolveScreenStates.Error; 102 | boardErrors = BoardErrors.UnSolvable; 103 | notifyListeners(); 104 | } 105 | } 106 | } 107 | 108 | // Helper function to convert board to list of ints 109 | List> _convertBoardToInts(List> board) { 110 | List> intBoard = List.generate( 111 | 9, 112 | (int row) => List.generate( 113 | 9, 114 | (int column) => board[row][column].value, 115 | ), 116 | ); 117 | return intBoard; 118 | } 119 | 120 | // Helper function to convert list of ints to a board 121 | List> _convertIntsToBoard(List> board) { 122 | List> newBoard = List.generate( 123 | 9, 124 | (int row) => List.generate( 125 | 9, 126 | (int column) => BoardSquare( 127 | position: Position(x: row, y: column), value: board[row][column]), 128 | ), 129 | ); 130 | return newBoard; 131 | } 132 | 133 | // Helper function to update board if any positions match that of error position 134 | void _updateBoardWithErrors( 135 | List positions, List> board) { 136 | // Iterate through each board square 137 | for (List row in board) { 138 | for (BoardSquare square in row) { 139 | // Update user inputted to true if the square's position object 140 | // matches a position object in the list (thanks Equatable) 141 | if (positions.contains(square.position)) { 142 | square.hasError = true; 143 | } 144 | } 145 | } 146 | } 147 | 148 | List _userInputtedNumbers(List> board) { 149 | List userInputtedPositions = []; 150 | // Iterate through rows then square to get all squares where there is a value and get it's position 151 | for (List row in board) { 152 | for (BoardSquare square in row) { 153 | if (square.value != 0) { 154 | userInputtedPositions.add(square.position); 155 | } 156 | } 157 | } 158 | return userInputtedPositions; 159 | } 160 | 161 | void _updateUserInputtedNumbers( 162 | List positions, List> board) { 163 | // Iterate through each board square 164 | for (List row in board) { 165 | for (BoardSquare square in row) { 166 | // Update user inputted to true if the square's position object 167 | // matches a position object in the list (thanks Equatable) 168 | if (positions.contains(square.position)) { 169 | square.userInputted = true; 170 | } 171 | } 172 | } 173 | } 174 | 175 | // Get a specific board square given a coordinate 176 | // Currently unused but thought it might be useful 177 | BoardSquare getBoardSquareAtPosition(int x, int y) { 178 | return userBoard[x][y]; 179 | } 180 | 181 | void updateSelectedNumber(int newNumber) { 182 | this.selectedNumber = newNumber; 183 | solveScreenStates = SolveScreenStates.Idle; 184 | boardErrors = BoardErrors.None; 185 | notifyListeners(); 186 | } 187 | 188 | String toString() { 189 | return userBoard[0].toString() + 190 | '\n' + 191 | userBoard[1].toString() + 192 | '\n' + 193 | userBoard[2].toString() + 194 | '\n' + 195 | userBoard[3].toString() + 196 | '\n' + 197 | userBoard[4].toString() + 198 | '\n' + 199 | userBoard[5].toString() + 200 | '\n' + 201 | userBoard[6].toString() + 202 | '\n' + 203 | userBoard[7].toString() + 204 | '\n' + 205 | userBoard[8].toString(); 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /lib/screens/solve_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import '../widgets/horizontal_layout.dart'; 4 | import '../widgets/vertical_layout.dart'; 5 | 6 | class SolveScreen extends StatelessWidget { 7 | @override 8 | Widget build(BuildContext context) { 9 | // If the width is greater than 400px the phone can rotate 10 | var _shortestSide = MediaQuery.of(context).size.width; 11 | var _canRotate = _shortestSide > 400; 12 | if (_canRotate == false) { 13 | SystemChrome.setPreferredOrientations([ 14 | DeviceOrientation.portraitUp, 15 | DeviceOrientation.portraitDown, 16 | ]); 17 | } 18 | return Scaffold( 19 | body: SafeArea( 20 | bottom: false, 21 | child: OrientationBuilder( 22 | builder: (context, orientation) { 23 | if (orientation == Orientation.portrait) { 24 | return VerticalLayout(); 25 | } else { 26 | return HorizontalLayout(); 27 | } 28 | }, 29 | ), 30 | ), 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/translations/codegen_loader.g.dart: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This is code generated via package:easy_localization/generate.dart 2 | 3 | // ignore_for_file: prefer_single_quotes 4 | 5 | import 'dart:ui'; 6 | 7 | import 'package:easy_localization/easy_localization.dart' show AssetLoader; 8 | 9 | class CodegenLoader extends AssetLoader{ 10 | const CodegenLoader(); 11 | 12 | @override 13 | Future> load(String fullPath, Locale locale ) { 14 | return Future.value(mapLocales[locale.toString()]); 15 | } 16 | 17 | static const Map ar = { 18 | "slide_to_solve": "قم بالتمرير لحل اللغز", 19 | "solved": "تم الحل!", 20 | "solving": "جاري الحل...", 21 | "unsolvable": "غير قابلة للحل", 22 | "reset": "إعادة", 23 | "feature_coming_soon": "ستتوفر الميزة قريبًا!", 24 | "take_picture": "التقط صورة", 25 | "invalid_puzzle_unsolvable": "لغز غير صالح - غير قابل للحل", 26 | "invalid_puzzle_duplicate_number": "لغز غير صالح - رقم مكرر", 27 | "solve": "حل", 28 | "invalid_board": "لوحة غير صالحة", 29 | "this_board_connot_be_solved": "لا يمكن حل هذه اللوحة", 30 | "there_is_an_error_on_the_board": "هناك خطأ في لوحة السودوكو!", 31 | "invalid_board_duplicate_number": "لوحة غير صالحة - رقم مكرر", 32 | "sudoku_solver": "Sudoku Solver", 33 | "change_language": "تغيير اللغة" 34 | }; 35 | static const Map en = { 36 | "slide_to_solve": "Slide to solve", 37 | "solved": "SOLVED!", 38 | "solving": "Solving...", 39 | "unsolvable": "Unsolvable", 40 | "reset": "Reset", 41 | "feature_coming_soon": "Feature coming soon!", 42 | "take_picture": "Take picture", 43 | "invalid_puzzle_unsolvable": "Invalid Puzzle - Unsolvable", 44 | "invalid_puzzle_duplicate_number": "Invalid Puzzle - Duplicate Number", 45 | "solve": "Solve", 46 | "invalid_board": "Invalid Board", 47 | "this_board_connot_be_solved": "This board cannot be solved", 48 | "there_is_an_error_on_the_board": "There is an error on the board!", 49 | "invalid_board_duplicate_number": "Invalid Board - Duplicate Number", 50 | "sudoku_solver": "Sudoku Solver", 51 | "change_language": "Change Language" 52 | }; 53 | static const Map> mapLocales = {"ar": ar, "en": en}; 54 | } 55 | -------------------------------------------------------------------------------- /lib/translations/locale_keys.g.dart: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This is code generated via package:easy_localization/generate.dart 2 | 3 | abstract class LocaleKeys { 4 | static const slide_to_solve = 'slide_to_solve'; 5 | static const solved = 'solved'; 6 | static const solving = 'solving'; 7 | static const unsolvable = 'unsolvable'; 8 | static const reset = 'reset'; 9 | static const feature_coming_soon = 'feature_coming_soon'; 10 | static const take_picture = 'take_picture'; 11 | static const invalid_puzzle_unsolvable = 'invalid_puzzle_unsolvable'; 12 | static const invalid_puzzle_duplicate_number = 'invalid_puzzle_duplicate_number'; 13 | static const solve = 'solve'; 14 | static const invalid_board = 'invalid_board'; 15 | static const this_board_connot_be_solved = 'this_board_connot_be_solved'; 16 | static const there_is_an_error_on_the_board = 'there_is_an_error_on_the_board'; 17 | static const invalid_board_duplicate_number = 'invalid_board_duplicate_number'; 18 | static const sudoku_solver = 'sudoku_solver'; 19 | static const change_language = 'change_language'; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /lib/utils/utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:easy_localization/easy_localization.dart'; 3 | 4 | enum SupportedLocale { 5 | en, 6 | ar, 7 | } 8 | 9 | class Utils { 10 | static bool isCurrentLocaleRTL(BuildContext context) { 11 | switch (context.locale.languageCode) { 12 | case 'ar': 13 | return true; 14 | break; 15 | default: 16 | return false; 17 | } 18 | } 19 | 20 | static Locale currentLocale(BuildContext context) { 21 | return context.locale; 22 | } 23 | 24 | static void changeLocale( 25 | BuildContext context, 26 | SupportedLocale supportedLocale, 27 | ) { 28 | Locale locale; 29 | 30 | switch (supportedLocale) { 31 | case SupportedLocale.en: 32 | locale = Locale('en'); 33 | break; 34 | case SupportedLocale.ar: 35 | locale = Locale('ar'); 36 | break; 37 | default: 38 | locale = Locale('en'); 39 | break; 40 | } 41 | 42 | EasyLocalization.of(context).setLocale(locale); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/widgets/animated_solve_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:easy_localization/easy_localization.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:provider/provider.dart'; 4 | 5 | import 'package:sudoku_solver/translations/locale_keys.g.dart'; 6 | 7 | import '../constants/enums.dart'; 8 | import '../models/sudoku_grid.dart'; 9 | import 'custom_slider_thumb_rect.dart'; 10 | 11 | class AnimatedSolveButton extends StatefulWidget { 12 | @override 13 | _AnimatedSolveButtonState createState() => _AnimatedSolveButtonState(); 14 | } 15 | 16 | class _AnimatedSolveButtonState extends State 17 | with TickerProviderStateMixin { 18 | // Final values 19 | final double _threshold = 95; 20 | final double _height = 50; 21 | 22 | // Slider variables 23 | double _currentSliderValue; 24 | String _sliderText; 25 | Color _borderColor; 26 | bool _animationHasPlayed = false; 27 | 28 | // Animations 29 | Animation _sliderThumbAnimation; 30 | AnimationController _sliderThumbController; 31 | Animation _loadingAnimation; 32 | AnimationController _loadingController; 33 | 34 | @override 35 | void initState() { 36 | super.initState(); 37 | // Initialise slider variables 38 | _currentSliderValue = 0; 39 | _sliderText = ' ${LocaleKeys.slide_to_solve.tr()} >>>'; 40 | _borderColor = Colors.blueAccent; 41 | 42 | // Initialise slider thumb animation 43 | _sliderThumbController = 44 | AnimationController(duration: Duration(milliseconds: 500), vsync: this); 45 | _sliderThumbAnimation = Tween(begin: 1.0, end: 0.0).animate(CurvedAnimation( 46 | parent: _sliderThumbController, 47 | curve: Curves.easeOut, 48 | reverseCurve: Curves.easeIn)); 49 | _sliderThumbController.addListener(() { 50 | setState(() {}); 51 | }); 52 | 53 | // Initialise loading text animation 54 | _loadingController = AnimationController( 55 | duration: Duration(milliseconds: 1000), vsync: this); 56 | _loadingAnimation = Tween(begin: 3.0, end: 8.0).animate(CurvedAnimation( 57 | parent: _loadingController, 58 | curve: Curves.easeIn, 59 | reverseCurve: Curves.easeOut)); 60 | _loadingController.addListener(() { 61 | setState(() {}); 62 | }); 63 | _loadingController.addStatusListener((status) { 64 | if (status == AnimationStatus.completed) { 65 | _loadingController.reverse(); 66 | } else if (status == AnimationStatus.dismissed) { 67 | _loadingController.forward(); 68 | } 69 | }); 70 | 71 | // Initialise background slider animation 72 | } 73 | 74 | @override 75 | void dispose() { 76 | super.dispose(); 77 | _sliderThumbController.dispose(); 78 | _loadingController.dispose(); 79 | } 80 | 81 | @override 82 | Widget build(BuildContext context) { 83 | if (context.watch().solveScreenStates == 84 | SolveScreenStates.Idle && 85 | context.watch().boardErrors == BoardErrors.None) { 86 | //! This is definitely a very bad way of doing this! 87 | if (_animationHasPlayed == true) { 88 | _sliderThumbController.reverse(); 89 | _animationHasPlayed = false; 90 | _loadingController.reset(); 91 | } 92 | setState(() { 93 | _sliderText = ' ${LocaleKeys.slide_to_solve.tr()} >>>'; 94 | _borderColor = Colors.blueAccent; 95 | }); 96 | } 97 | 98 | if (context.watch().solveScreenStates == 99 | SolveScreenStates.Solved) { 100 | setState(() { 101 | _borderColor = Colors.green; 102 | _sliderText = LocaleKeys.solved.tr(); 103 | }); 104 | _loadingController.reset(); 105 | } 106 | 107 | if (context.watch().solveScreenStates == 108 | SolveScreenStates.Loading) { 109 | setState(() { 110 | _sliderText = LocaleKeys.solving.tr(); 111 | }); 112 | _loadingController.forward(); 113 | } 114 | 115 | if (context.watch().boardErrors == BoardErrors.Duplicate) { 116 | setState(() { 117 | _borderColor = Colors.red; 118 | _sliderText = LocaleKeys.invalid_board_duplicate_number.tr(); 119 | }); 120 | _loadingController.reset(); 121 | } 122 | 123 | if (context.watch().boardErrors == BoardErrors.UnSolvable) { 124 | setState(() { 125 | _borderColor = Colors.red; 126 | _sliderText = LocaleKeys.unsolvable.tr(); 127 | }); 128 | _loadingController.reset(); 129 | } 130 | return Container( 131 | height: _height, 132 | child: Stack( 133 | children: [ 134 | Center( 135 | child: Container( 136 | margin: EdgeInsets.symmetric(horizontal: 0, vertical: 1), 137 | decoration: BoxDecoration( 138 | color: Colors.white, 139 | border: Border.all( 140 | color: _borderColor, 141 | width: 2, 142 | ), 143 | borderRadius: BorderRadius.circular(10), 144 | boxShadow: [ 145 | BoxShadow( 146 | color: _borderColor.withOpacity(0.20), 147 | offset: Offset(0.0, 0.0), 148 | blurRadius: 5, 149 | spreadRadius: _loadingAnimation.value, 150 | ), 151 | ], 152 | ), 153 | ), 154 | ), 155 | Center( 156 | child: context.watch().solveScreenStates == 157 | SolveScreenStates.Loading 158 | ? SizedBox( 159 | width: 30, 160 | height: 30, 161 | child: Theme( 162 | data: Theme.of(context) 163 | .copyWith(accentColor: Colors.blueAccent), 164 | child: CircularProgressIndicator()), 165 | ) 166 | : Text( 167 | _sliderText, 168 | style: TextStyle( 169 | color: _borderColor, 170 | ), 171 | ), 172 | ), 173 | SliderTheme( 174 | data: SliderThemeData( 175 | activeTrackColor: Colors.transparent, 176 | inactiveTrackColor: Colors.transparent, 177 | overlayColor: Colors.transparent, 178 | thumbShape: CustomSliderThumbRect( 179 | thumbRadius: 20, 180 | thumbHeight: (_height - 8) * _sliderThumbAnimation.value, 181 | min: 0, 182 | max: 100, 183 | thumbColor: Colors.blueAccent, 184 | ), 185 | // CustomSliderThumbCircle( 186 | // thumbRadius: (_height / 2) + 3, thumbColor: Colors.blue), 187 | ), 188 | child: Slider( 189 | value: _currentSliderValue, 190 | min: 0, 191 | max: 100, 192 | onChanged: (double value) { 193 | setState(() { 194 | _currentSliderValue = value; 195 | }); 196 | // _loadingController.animateTo(value); 197 | }, 198 | onChangeEnd: (double value) async { 199 | if (value > _threshold) { 200 | setState(() { 201 | // Move the slider all the way to the end 202 | _currentSliderValue = 100; 203 | }); 204 | _sliderThumbController.forward(); 205 | 206 | // Wait for the animation to complete before calling the function 207 | await Future.delayed(_sliderThumbController.duration); 208 | 209 | // Set the animation to complete 210 | _animationHasPlayed = true; 211 | 212 | // Reset 213 | _currentSliderValue = 0; 214 | 215 | // Call the function to solve 216 | context 217 | .read() 218 | .solveButtonPress(context.read().userBoard); 219 | } 220 | if (value <= _threshold) { 221 | setState(() { 222 | _currentSliderValue = 0; 223 | }); 224 | } 225 | }, 226 | ), 227 | ), 228 | ], 229 | ), 230 | ); 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /lib/widgets/custom_slider_thumb_circle.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CustomSliderThumbCircle extends SliderComponentShape { 4 | final double thumbRadius; 5 | Color thumbColor; 6 | CustomSliderThumbCircle({@required this.thumbRadius, thumbColor}) { 7 | this.thumbColor = thumbColor ?? Colors.grey; 8 | } 9 | 10 | @override 11 | Size getPreferredSize(bool isEnabled, bool isDiscrete) { 12 | return Size.fromRadius(thumbRadius); 13 | } 14 | 15 | @override 16 | void paint(PaintingContext context, Offset center, 17 | {Animation activationAnimation, 18 | Animation enableAnimation, 19 | bool isDiscrete, 20 | TextPainter labelPainter, 21 | RenderBox parentBox, 22 | SliderThemeData sliderTheme, 23 | TextDirection textDirection, 24 | double value, 25 | double textScaleFactor, 26 | Size sizeWithOverflow}) { 27 | final Canvas canvas = context.canvas; 28 | 29 | final paint = Paint() 30 | ..color = thumbColor 31 | ..style = PaintingStyle.fill; 32 | 33 | final shadowPaint = Paint() 34 | ..color = thumbColor.withOpacity(0.4) 35 | ..maskFilter = MaskFilter.blur(BlurStyle.normal, 3 * 0.57735 + 0.5) 36 | ..style = PaintingStyle.fill; 37 | 38 | canvas.drawCircle(center, thumbRadius + 2, shadowPaint); 39 | canvas.drawCircle(center, thumbRadius, paint); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/widgets/custom_slider_thumb_rect.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CustomSliderThumbRect extends SliderComponentShape { 4 | final double thumbRadius; 5 | Color thumbColor; 6 | final double thumbHeight; 7 | final int min; 8 | final int max; 9 | 10 | CustomSliderThumbRect({ 11 | this.thumbRadius, 12 | this.thumbHeight, 13 | this.min, 14 | this.max, 15 | thumbColor, 16 | }) { 17 | this.thumbColor = thumbColor ?? Colors.grey; 18 | } 19 | 20 | @override 21 | Size getPreferredSize(bool isEnabled, bool isDiscrete) { 22 | return Size.fromRadius(thumbRadius); 23 | } 24 | 25 | @override 26 | void paint( 27 | PaintingContext context, 28 | Offset center, { 29 | Animation activationAnimation, 30 | Animation enableAnimation, 31 | bool isDiscrete, 32 | TextPainter labelPainter, 33 | RenderBox parentBox, 34 | SliderThemeData sliderTheme, 35 | TextDirection textDirection, 36 | double value, 37 | double textScaleFactor, 38 | Size sizeWithOverflow, 39 | }) { 40 | final Canvas canvas = context.canvas; 41 | 42 | final rRect = RRect.fromRectAndRadius( 43 | Rect.fromCenter(center: center, width: thumbHeight, height: thumbHeight), 44 | Radius.circular(thumbRadius * .4), 45 | ); 46 | 47 | final paint = Paint() 48 | ..color = thumbColor //Thumb Background Color 49 | ..style = PaintingStyle.fill; 50 | 51 | final shadowPaint = Paint() 52 | ..color = thumbColor.withOpacity(0.4) 53 | ..maskFilter = MaskFilter.blur(BlurStyle.normal, 3 * 0.57735 + 0.5) 54 | ..style = PaintingStyle.fill; 55 | 56 | // TextSpan span = new TextSpan( 57 | // style: new TextStyle( 58 | // fontSize: thumbHeight * .3, 59 | // fontWeight: FontWeight.w700, 60 | // color: Colors.white, 61 | // height: 1), 62 | // text: '${getValue(value)}'); 63 | // TextPainter tp = new TextPainter( 64 | // text: span, 65 | // textAlign: TextAlign.left, 66 | // textDirection: TextDirection.ltr); 67 | // tp.layout(); 68 | // Offset textCenter = 69 | // Offset(center.dx - (tp.width / 2), center.dy - (tp.height / 2)); 70 | 71 | canvas.drawRRect(rRect, shadowPaint); 72 | canvas.drawRRect(rRect, paint); 73 | // tp.paint(canvas, textCenter); 74 | } 75 | 76 | // String getValue(double value) { 77 | // return (min + (max - min) * value).round().toString(); 78 | // } 79 | } 80 | -------------------------------------------------------------------------------- /lib/widgets/horizontal_layout.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'animated_solve_button.dart'; 3 | import 'keypad.dart'; 4 | import 'options_row.dart'; 5 | import 'sudoku_table.dart'; 6 | 7 | class HorizontalLayout extends StatelessWidget { 8 | @override 9 | Widget build(BuildContext context) { 10 | return Stack( 11 | children: [ 12 | FractionallySizedBox( 13 | widthFactor: 1.0, 14 | heightFactor: 0.2, 15 | child: Container( 16 | color: Colors.blueAccent, 17 | ), 18 | ), 19 | Row( 20 | mainAxisSize: MainAxisSize.max, 21 | // crossAxisAlignment: CrossAxisAlignment.stretch, 22 | children: [ 23 | AspectRatio( 24 | aspectRatio: 1.05, 25 | child: FractionallySizedBox( 26 | widthFactor: 0.92, 27 | heightFactor: 0.92, 28 | child: SudokuTable(), 29 | ), 30 | ), 31 | Expanded( 32 | child: Padding( 33 | padding: const EdgeInsets.all(8.0), 34 | child: Column( 35 | children: [ 36 | Container( 37 | child: Padding( 38 | padding: const EdgeInsets.only(top: 8.0), 39 | child: OptionsRow(), 40 | ), 41 | ), 42 | Spacer( 43 | flex: 2, 44 | ), 45 | FractionallySizedBox( 46 | widthFactor: 0.85, 47 | child: KeyPad(), 48 | ), 49 | Spacer(), 50 | FractionallySizedBox( 51 | widthFactor: 0.85, 52 | child: AnimatedSolveButton(), 53 | ), 54 | Spacer(), 55 | ], 56 | ), 57 | ), 58 | ), 59 | ], 60 | ) 61 | ], 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/widgets/keypad.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'keypad_cell.dart'; 3 | 4 | class KeyPad extends StatelessWidget { 5 | static const int rows = 2; 6 | static const int columns = 5; 7 | @override 8 | Widget build(BuildContext context) { 9 | return Wrap( 10 | alignment: WrapAlignment.center, 11 | spacing: 16.0, 12 | // Reduce vertical spacing if the screen is small 13 | runSpacing: MediaQuery.of(context).size.height < 620 ? 8.0 : 16.0, 14 | children: List.generate( 15 | rows * columns, 16 | (index) => KeyPadCell( 17 | numberValue: index, 18 | ), 19 | ), 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/widgets/keypad_cell.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_icons/flutter_icons.dart'; 3 | import 'package:provider/provider.dart'; 4 | import '../models/sudoku_grid.dart'; 5 | 6 | class KeyPadCell extends StatelessWidget { 7 | KeyPadCell({@required this.numberValue}) : assert(numberValue >= 0); 8 | final int numberValue; 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | final bool isSelected = 13 | context.watch().selectedNumber == numberValue; 14 | return GestureDetector( 15 | onTap: () { 16 | context.read().updateSelectedNumber(numberValue); 17 | }, 18 | child: Container( 19 | height: MediaQuery.of(context).size.width >= 800 ? 60 : 35, 20 | width: MediaQuery.of(context).size.width >= 800 ? 60 : 35, 21 | decoration: BoxDecoration( 22 | color: isSelected ? Colors.blueAccent : Colors.transparent, 23 | border: Border.all( 24 | color: Colors.blueAccent, 25 | width: MediaQuery.of(context).size.width >= 800 ? 2 : 1), 26 | borderRadius: BorderRadius.circular(8.0)), 27 | child: Padding( 28 | padding: EdgeInsets.all( 29 | MediaQuery.of(context).size.width >= 800 ? 16.0 : 10.0), 30 | child: FittedBox( 31 | fit: BoxFit.fitWidth, 32 | child: numberValue == 0 33 | ? Icon( 34 | Entypo.eraser, 35 | color: isSelected ? Colors.white : Colors.blue[900], 36 | ) 37 | : Text( 38 | // Instead of showing the number 0, we want the user to be able to a blank space 39 | numberValue.toString(), 40 | style: TextStyle( 41 | color: isSelected ? Colors.white : Colors.blue[900], 42 | // fontSize: 18, 43 | ), 44 | ), 45 | ), 46 | ), 47 | ), 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/widgets/language_bottom_sheet.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:sudoku_solver/utils/utils.dart'; 3 | import 'package:sudoku_solver/translations/locale_keys.g.dart'; 4 | import 'package:easy_localization/easy_localization.dart'; 5 | 6 | class LanguageBottomSheet extends StatelessWidget { 7 | @override 8 | Widget build(BuildContext context) { 9 | return Wrap( 10 | children: [ 11 | Container( 12 | height: MediaQuery.of(context).size.height * 0.6, 13 | padding: EdgeInsets.symmetric(vertical: 24, horizontal: 24), 14 | decoration: BoxDecoration( 15 | borderRadius: BorderRadius.circular(10), 16 | color: Theme.of(context).primaryColor, 17 | ), 18 | child: Column( 19 | crossAxisAlignment: CrossAxisAlignment.center, 20 | children: [ 21 | Row( 22 | mainAxisAlignment: MainAxisAlignment.center, 23 | children: [ 24 | Text( 25 | LocaleKeys.change_language.tr(), 26 | style: TextStyle( 27 | color: Colors.white, 28 | fontSize: 24, 29 | fontWeight: FontWeight.bold, 30 | ), 31 | ), 32 | ], 33 | ), 34 | Expanded( 35 | child: ShaderMask( 36 | shaderCallback: (bounds) { 37 | return LinearGradient( 38 | begin: Alignment.bottomCenter, 39 | end: Alignment.topCenter, 40 | colors: [ 41 | Colors.transparent, 42 | Colors.white, 43 | ], 44 | stops: [0, 0.35], 45 | ).createShader(bounds); 46 | }, 47 | child: ListView( 48 | children: [ 49 | /// emojis from: https://emojipedia.org/flags/ 50 | LanguageListTile( 51 | languageEmoji: '🇬🇧', 52 | languageName: 'English', 53 | showCheck: Utils.currentLocale(context) == Locale('en'), 54 | locale: SupportedLocale.en, 55 | ), 56 | LanguageListTile( 57 | languageEmoji: '🇸🇦', 58 | languageName: 'العربية', 59 | showCheck: Utils.currentLocale(context) == Locale('ar'), 60 | locale: SupportedLocale.ar, 61 | ), 62 | ]..add(SizedBox(height: 80.0)), 63 | ), 64 | ), 65 | ), 66 | ], 67 | ), 68 | ), 69 | ], 70 | ); 71 | } 72 | } 73 | 74 | class LanguageListTile extends StatelessWidget { 75 | const LanguageListTile({ 76 | Key key, 77 | @required this.languageEmoji, 78 | @required this.languageName, 79 | @required this.showCheck, 80 | @required this.locale, 81 | }) : super(key: key); 82 | 83 | final String languageEmoji; 84 | final String languageName; 85 | final bool showCheck; 86 | final SupportedLocale locale; 87 | 88 | @override 89 | Widget build(BuildContext context) { 90 | return ListTile( 91 | leading: Text( 92 | languageEmoji, 93 | style: TextStyle( 94 | color: Colors.white, 95 | fontSize: 24, 96 | fontWeight: FontWeight.bold, 97 | ), 98 | ), 99 | title: Text( 100 | languageName, 101 | style: TextStyle( 102 | color: Colors.white, 103 | fontSize: 24, 104 | fontWeight: FontWeight.bold, 105 | ), 106 | ), 107 | trailing: showCheck 108 | ? Icon( 109 | Icons.check, 110 | color: Colors.white, 111 | ) 112 | : null, 113 | onTap: () async { 114 | Utils.changeLocale(context, locale); 115 | await Future.delayed(const Duration(milliseconds: 400)); 116 | Navigator.of(context).pop(); 117 | }, 118 | ); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /lib/widgets/options_row.dart: -------------------------------------------------------------------------------- 1 | import 'package:easy_localization/easy_localization.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:provider/provider.dart'; 4 | 5 | import 'package:sudoku_solver/translations/locale_keys.g.dart'; 6 | import 'package:sudoku_solver/widgets/language_bottom_sheet.dart'; 7 | 8 | import '../models/sudoku_grid.dart'; 9 | import 'secondary_button.dart'; 10 | 11 | class OptionsRow extends StatelessWidget { 12 | @override 13 | Widget build(BuildContext context) { 14 | return Row( 15 | children: [ 16 | Expanded( 17 | child: SecondaryButton( 18 | onTap: () => context.read().resetBoard(), 19 | label: LocaleKeys.reset.tr(), 20 | icon: Icons.refresh_outlined, 21 | ), 22 | ), 23 | SizedBox( 24 | width: 8.0, 25 | ), 26 | SecondaryButton( 27 | onTap: () { 28 | showModalBottomSheet( 29 | backgroundColor: Colors.transparent, 30 | context: context, 31 | builder: (BuildContext context) { 32 | return LanguageBottomSheet(); 33 | }, 34 | ); 35 | }, 36 | icon: Icons.language, 37 | ), 38 | SizedBox( 39 | width: 8.0, 40 | ), 41 | Expanded( 42 | child: SecondaryButton( 43 | onTap: () { 44 | // ignore: deprecated_member_use 45 | Scaffold.of(context).showSnackBar( 46 | SnackBar(content: Text(LocaleKeys.feature_coming_soon.tr()))); 47 | // Todo: Implement image recognition functionality 48 | }, 49 | label: LocaleKeys.take_picture.tr(), 50 | icon: Icons.camera, 51 | ), 52 | ), 53 | ], 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/widgets/secondary_button.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | class SecondaryButton extends StatelessWidget { 6 | final Function onTap; 7 | final String label; 8 | final IconData icon; 9 | SecondaryButton({ 10 | @required this.onTap, 11 | this.label, 12 | @required this.icon, 13 | }); 14 | @override 15 | Widget build(BuildContext context) { 16 | return InkWell( 17 | onTap: onTap, 18 | child: Container( 19 | padding: EdgeInsets.all(8.0), 20 | decoration: BoxDecoration( 21 | borderRadius: BorderRadius.circular(6), 22 | border: Border.all(color: Colors.white.withOpacity(0.7)), 23 | ), 24 | child: Center( 25 | child: Row( 26 | mainAxisAlignment: MainAxisAlignment.center, 27 | crossAxisAlignment: CrossAxisAlignment.center, 28 | children: [ 29 | Icon( 30 | icon, 31 | size: 20, 32 | color: Colors.white.withOpacity(0.7), 33 | ), 34 | if (label != null) 35 | SizedBox( 36 | width: 4, 37 | ), 38 | if (label != null) 39 | Text( 40 | label, 41 | style: TextStyle(color: Colors.white.withOpacity(0.7)), 42 | ), 43 | ], 44 | ), 45 | ), 46 | ), 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/widgets/solve_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | import 'package:sudoku_solver/translations/locale_keys.g.dart'; 4 | import 'package:easy_localization/easy_localization.dart'; 5 | import '../constants/enums.dart'; 6 | import '../models/sudoku_grid.dart'; 7 | 8 | class SolveButton extends StatefulWidget { 9 | @override 10 | _SolveButtonState createState() => _SolveButtonState(); 11 | } 12 | 13 | class _SolveButtonState extends State { 14 | Widget _buttonWidget; 15 | Color _buttonColor; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | if (context.watch().boardErrors == BoardErrors.UnSolvable) { 20 | setState(() { 21 | _buttonWidget = Text( 22 | LocaleKeys.invalid_puzzle_unsolvable.tr(), 23 | style: TextStyle( 24 | color: Colors.white, 25 | fontSize: 18, 26 | fontWeight: FontWeight.bold, 27 | ), 28 | ); 29 | _buttonColor = Colors.red; 30 | }); 31 | } else if (context.watch().boardErrors == 32 | BoardErrors.Duplicate) { 33 | setState(() { 34 | _buttonWidget = Text( 35 | LocaleKeys.invalid_puzzle_duplicate_number.tr(), 36 | style: TextStyle( 37 | color: Colors.white, 38 | fontSize: 18, 39 | fontWeight: FontWeight.bold, 40 | ), 41 | ); 42 | _buttonColor = Colors.red; 43 | }); 44 | } else if (context.watch().solveScreenStates == 45 | SolveScreenStates.Loading) { 46 | setState(() { 47 | _buttonWidget = SizedBox( 48 | height: 25, 49 | width: 25, 50 | child: CircularProgressIndicator( 51 | strokeWidth: 3, 52 | backgroundColor: Colors.white, 53 | ), 54 | ); 55 | }); 56 | } else if (context.watch().solveScreenStates == 57 | SolveScreenStates.Solved) { 58 | setState(() { 59 | _buttonWidget = Text( 60 | LocaleKeys.solved.tr(), 61 | style: TextStyle( 62 | color: Colors.white, 63 | fontSize: 18, 64 | fontWeight: FontWeight.bold, 65 | ), 66 | ); 67 | _buttonColor = Colors.green; 68 | }); 69 | } else { 70 | setState(() { 71 | _buttonWidget = Text( 72 | LocaleKeys.solve.tr(), 73 | style: TextStyle( 74 | color: Colors.white, 75 | fontSize: 18, 76 | fontWeight: FontWeight.bold, 77 | ), 78 | ); 79 | _buttonColor = Colors.blueAccent; 80 | }); 81 | } 82 | return GestureDetector( 83 | onTap: () { 84 | context 85 | .read() 86 | .solveButtonPress(context.read().userBoard); 87 | }, 88 | child: Container( 89 | padding: EdgeInsets.all(12.0), 90 | width: double.infinity, 91 | height: 48, 92 | decoration: BoxDecoration( 93 | color: _buttonColor, 94 | borderRadius: BorderRadius.circular(4.0), 95 | ), 96 | child: Center(child: _buttonWidget), 97 | ), 98 | ); 99 | } 100 | } 101 | // Todo: Need a way of resetting state when the board is edited 102 | 103 | //* Old version for reference 104 | class BottomButton extends StatelessWidget { 105 | @override 106 | Widget build(BuildContext context) { 107 | Widget buttonContent() { 108 | if (Provider.of(context).solveScreenStates == 109 | SolveScreenStates.Idle && 110 | Provider.of(context).boardErrors == BoardErrors.None) { 111 | return Text( 112 | '${LocaleKeys.solve.tr()}!', 113 | style: TextStyle(color: Colors.white, fontSize: 20), 114 | ); 115 | } else if (Provider.of(context).boardErrors == 116 | BoardErrors.Duplicate) { 117 | return Text( 118 | LocaleKeys.invalid_board.tr(), 119 | style: TextStyle(color: Colors.red, fontSize: 20), 120 | ); 121 | } else if (Provider.of(context).boardErrors == 122 | BoardErrors.UnSolvable) { 123 | return Text( 124 | LocaleKeys.this_board_connot_be_solved.tr(), 125 | style: TextStyle(color: Colors.red, fontSize: 20), 126 | ); 127 | } else if (Provider.of(context).solveScreenStates == 128 | SolveScreenStates.Solved) { 129 | return Text( 130 | LocaleKeys.solved.tr(), 131 | style: TextStyle(color: Colors.green, fontSize: 20), 132 | ); 133 | } else { 134 | return CircularProgressIndicator( 135 | backgroundColor: Colors.white, 136 | ); 137 | } 138 | } 139 | 140 | return RaisedButton( 141 | color: Colors.blueAccent, 142 | onPressed: () { 143 | final snackBar = SnackBar( 144 | duration: Duration(seconds: 2), 145 | content: Text( 146 | LocaleKeys.there_is_an_error_on_the_board.tr(), 147 | ), 148 | ); 149 | 150 | Provider.of(context, listen: false).solveButtonPress( 151 | Provider.of(context, listen: false).userBoard); 152 | }, 153 | child: buttonContent(), 154 | ); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /lib/widgets/sudoku_cell.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | import '../constants/enums.dart'; 4 | import '../models/board_square.dart'; 5 | import '../models/sudoku_grid.dart'; 6 | 7 | class SudokuCell extends StatelessWidget { 8 | // We are using the value of 0 to mean blank, therefore if the value of the board square is 0 we need to show nothing 9 | String getCellValue(int cellValue) { 10 | if (cellValue == 0) { 11 | return ''; 12 | } else { 13 | return cellValue.toString(); 14 | } 15 | } 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return InkWell( 20 | onTap: () { 21 | // Update the value of the board square based on the current selected number from the keypad 22 | context 23 | .read() 24 | .updateValue(context.read().selectedNumber); 25 | context.read().boardErrors = BoardErrors.None; 26 | context.read().solveScreenStates = SolveScreenStates.Idle; 27 | }, 28 | child: AspectRatio( 29 | // Find this aspect ratio looks a little better than 1.00 30 | aspectRatio: 1.05, 31 | child: Container( 32 | padding: EdgeInsets.all(8.0), 33 | decoration: BoxDecoration( 34 | color: Colors.white, 35 | border: Border( 36 | // Right and bottom border to be thicker for 2nd and 5th index 37 | // Along with the table outer border this creates the inner and outer grids 38 | // Todo: Figure out a way to not have a border on bottom and right side of table 39 | right: BorderSide( 40 | width: (context.watch().position.y == 2 || 41 | context.watch().position.y == 5) 42 | ? 1.5 43 | : 0.5, 44 | color: Colors.blueAccent, 45 | ), 46 | bottom: BorderSide( 47 | width: (context.watch().position.x == 2 || 48 | context.watch().position.x == 5) 49 | ? 1.5 50 | : 0.5, 51 | color: Colors.blueAccent, 52 | ), 53 | ), 54 | ), 55 | child: FittedBox( 56 | fit: BoxFit.fitHeight, 57 | child: Center( 58 | child: Text( 59 | getCellValue(context.watch().value), 60 | style: TextStyle( 61 | // fontSize: 18, 62 | color: context.watch().hasError 63 | ? Colors.red 64 | : Colors.blue[900], 65 | fontWeight: context.watch().userInputted 66 | ? FontWeight.w900 67 | : FontWeight.normal), 68 | ), 69 | ), 70 | ), 71 | ), 72 | ), 73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/widgets/sudoku_table.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../models/board_square.dart'; 3 | import 'sudoku_cell.dart'; 4 | import 'package:provider/provider.dart'; 5 | import '../models/sudoku_grid.dart'; 6 | 7 | class SudokuTable extends StatelessWidget { 8 | static const _borderRadius = 15.0; 9 | @override 10 | Widget build(BuildContext context) { 11 | return Container( 12 | decoration: BoxDecoration( 13 | color: Colors.green[100], 14 | borderRadius: BorderRadius.all( 15 | Radius.circular(_borderRadius), 16 | ), 17 | // border: Border.all(color: Colors.blueAccent, width: 2), 18 | boxShadow: [ 19 | BoxShadow( 20 | color: Colors.blue[900].withOpacity(0.3), 21 | offset: Offset(0, 12), 22 | blurRadius: 12, 23 | spreadRadius: 5, 24 | ), 25 | ], 26 | ), 27 | // [ClipRRect] so that the border of Container isn't cut off 28 | child: ClipRRect( 29 | borderRadius: BorderRadius.all( 30 | Radius.circular(_borderRadius), 31 | ), 32 | child: Directionality( 33 | textDirection: TextDirection.ltr, 34 | child: Table( 35 | defaultVerticalAlignment: TableCellVerticalAlignment.middle, 36 | children: List.generate( 37 | 9, 38 | (int rowNumber) => TableRow( 39 | children: List.generate( 40 | 9, 41 | (int columnNumber) => 42 | ChangeNotifierProvider.value( 43 | value: context.watch().userBoard[rowNumber] 44 | [columnNumber], 45 | child: SudokuCell(), 46 | ), 47 | ), 48 | ), 49 | ), 50 | ), 51 | ), 52 | ), 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/widgets/vertical_layout.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'sudoku_table.dart'; 3 | 4 | import 'animated_solve_button.dart'; 5 | import 'keypad.dart'; 6 | import 'options_row.dart'; 7 | 8 | class VerticalLayout extends StatelessWidget { 9 | @override 10 | Widget build(BuildContext context) { 11 | return Stack( 12 | children: [ 13 | // Blue background at top of the screen 14 | // Using [FractionallySizedBox] so that is always takes up ~1/3 of screen height 15 | FractionallySizedBox( 16 | widthFactor: 1.0, 17 | heightFactor: 0.33, 18 | child: Container( 19 | color: Colors.blueAccent, 20 | ), 21 | ), 22 | Center( 23 | child: Column( 24 | children: [ 25 | Spacer(flex: 3), 26 | FractionallySizedBox( 27 | widthFactor: 0.92, 28 | child: OptionsRow(), 29 | ), 30 | Spacer(), 31 | FractionallySizedBox( 32 | widthFactor: 0.92, 33 | child: SudokuTable(), 34 | ), 35 | Spacer(flex: 2), 36 | FractionallySizedBox( 37 | widthFactor: 0.75, 38 | child: KeyPad(), 39 | ), 40 | Spacer(flex: 2), 41 | FractionallySizedBox( 42 | widthFactor: 0.75, 43 | child: AnimatedSolveButton(), 44 | ), 45 | Spacer(), 46 | ], 47 | ), 48 | ), 49 | ], 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | args: 5 | dependency: transitive 6 | description: 7 | name: args 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.0.0" 11 | async: 12 | dependency: transitive 13 | description: 14 | name: async 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.5.0" 18 | boolean_selector: 19 | dependency: transitive 20 | description: 21 | name: boolean_selector 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.1.0" 25 | characters: 26 | dependency: transitive 27 | description: 28 | name: characters 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.1.0" 32 | charcode: 33 | dependency: transitive 34 | description: 35 | name: charcode 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.2.0" 39 | clock: 40 | dependency: transitive 41 | description: 42 | name: clock 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.1.0" 46 | collection: 47 | dependency: transitive 48 | description: 49 | name: collection 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.15.0" 53 | cupertino_icons: 54 | dependency: "direct main" 55 | description: 56 | name: cupertino_icons 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "1.0.0" 60 | easy_localization: 61 | dependency: "direct main" 62 | description: 63 | name: easy_localization 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "3.0.0" 67 | easy_logger: 68 | dependency: transitive 69 | description: 70 | name: easy_logger 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "0.0.2" 74 | equatable: 75 | dependency: "direct main" 76 | description: 77 | name: equatable 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "1.2.6" 81 | fake_async: 82 | dependency: transitive 83 | description: 84 | name: fake_async 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "1.2.0" 88 | ffi: 89 | dependency: transitive 90 | description: 91 | name: ffi 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "1.0.0" 95 | file: 96 | dependency: transitive 97 | description: 98 | name: file 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "6.1.0" 102 | flutter: 103 | dependency: "direct main" 104 | description: flutter 105 | source: sdk 106 | version: "0.0.0" 107 | flutter_icons: 108 | dependency: "direct main" 109 | description: 110 | name: flutter_icons 111 | url: "https://pub.dartlang.org" 112 | source: hosted 113 | version: "1.1.0" 114 | flutter_localizations: 115 | dependency: transitive 116 | description: flutter 117 | source: sdk 118 | version: "0.0.0" 119 | flutter_test: 120 | dependency: "direct dev" 121 | description: flutter 122 | source: sdk 123 | version: "0.0.0" 124 | flutter_web_plugins: 125 | dependency: transitive 126 | description: flutter 127 | source: sdk 128 | version: "0.0.0" 129 | intl: 130 | dependency: transitive 131 | description: 132 | name: intl 133 | url: "https://pub.dartlang.org" 134 | source: hosted 135 | version: "0.17.0" 136 | js: 137 | dependency: transitive 138 | description: 139 | name: js 140 | url: "https://pub.dartlang.org" 141 | source: hosted 142 | version: "0.6.3" 143 | matcher: 144 | dependency: transitive 145 | description: 146 | name: matcher 147 | url: "https://pub.dartlang.org" 148 | source: hosted 149 | version: "0.12.10" 150 | meta: 151 | dependency: transitive 152 | description: 153 | name: meta 154 | url: "https://pub.dartlang.org" 155 | source: hosted 156 | version: "1.3.0" 157 | nested: 158 | dependency: transitive 159 | description: 160 | name: nested 161 | url: "https://pub.dartlang.org" 162 | source: hosted 163 | version: "0.0.4" 164 | path: 165 | dependency: transitive 166 | description: 167 | name: path 168 | url: "https://pub.dartlang.org" 169 | source: hosted 170 | version: "1.8.0" 171 | path_provider_linux: 172 | dependency: transitive 173 | description: 174 | name: path_provider_linux 175 | url: "https://pub.dartlang.org" 176 | source: hosted 177 | version: "2.0.0" 178 | path_provider_platform_interface: 179 | dependency: transitive 180 | description: 181 | name: path_provider_platform_interface 182 | url: "https://pub.dartlang.org" 183 | source: hosted 184 | version: "2.0.1" 185 | path_provider_windows: 186 | dependency: transitive 187 | description: 188 | name: path_provider_windows 189 | url: "https://pub.dartlang.org" 190 | source: hosted 191 | version: "2.0.0" 192 | platform: 193 | dependency: transitive 194 | description: 195 | name: platform 196 | url: "https://pub.dartlang.org" 197 | source: hosted 198 | version: "3.0.0" 199 | plugin_platform_interface: 200 | dependency: transitive 201 | description: 202 | name: plugin_platform_interface 203 | url: "https://pub.dartlang.org" 204 | source: hosted 205 | version: "2.0.0" 206 | process: 207 | dependency: transitive 208 | description: 209 | name: process 210 | url: "https://pub.dartlang.org" 211 | source: hosted 212 | version: "4.1.0" 213 | provider: 214 | dependency: "direct main" 215 | description: 216 | name: provider 217 | url: "https://pub.dartlang.org" 218 | source: hosted 219 | version: "4.3.2+2" 220 | shared_preferences: 221 | dependency: transitive 222 | description: 223 | name: shared_preferences 224 | url: "https://pub.dartlang.org" 225 | source: hosted 226 | version: "2.0.4" 227 | shared_preferences_linux: 228 | dependency: transitive 229 | description: 230 | name: shared_preferences_linux 231 | url: "https://pub.dartlang.org" 232 | source: hosted 233 | version: "2.0.0" 234 | shared_preferences_macos: 235 | dependency: transitive 236 | description: 237 | name: shared_preferences_macos 238 | url: "https://pub.dartlang.org" 239 | source: hosted 240 | version: "2.0.0" 241 | shared_preferences_platform_interface: 242 | dependency: transitive 243 | description: 244 | name: shared_preferences_platform_interface 245 | url: "https://pub.dartlang.org" 246 | source: hosted 247 | version: "2.0.0" 248 | shared_preferences_web: 249 | dependency: transitive 250 | description: 251 | name: shared_preferences_web 252 | url: "https://pub.dartlang.org" 253 | source: hosted 254 | version: "2.0.0" 255 | shared_preferences_windows: 256 | dependency: transitive 257 | description: 258 | name: shared_preferences_windows 259 | url: "https://pub.dartlang.org" 260 | source: hosted 261 | version: "2.0.0" 262 | sky_engine: 263 | dependency: transitive 264 | description: flutter 265 | source: sdk 266 | version: "0.0.99" 267 | source_span: 268 | dependency: transitive 269 | description: 270 | name: source_span 271 | url: "https://pub.dartlang.org" 272 | source: hosted 273 | version: "1.8.0" 274 | stack_trace: 275 | dependency: transitive 276 | description: 277 | name: stack_trace 278 | url: "https://pub.dartlang.org" 279 | source: hosted 280 | version: "1.10.0" 281 | stream_channel: 282 | dependency: transitive 283 | description: 284 | name: stream_channel 285 | url: "https://pub.dartlang.org" 286 | source: hosted 287 | version: "2.1.0" 288 | string_scanner: 289 | dependency: transitive 290 | description: 291 | name: string_scanner 292 | url: "https://pub.dartlang.org" 293 | source: hosted 294 | version: "1.1.0" 295 | term_glyph: 296 | dependency: transitive 297 | description: 298 | name: term_glyph 299 | url: "https://pub.dartlang.org" 300 | source: hosted 301 | version: "1.2.0" 302 | test_api: 303 | dependency: transitive 304 | description: 305 | name: test_api 306 | url: "https://pub.dartlang.org" 307 | source: hosted 308 | version: "0.2.19" 309 | typed_data: 310 | dependency: transitive 311 | description: 312 | name: typed_data 313 | url: "https://pub.dartlang.org" 314 | source: hosted 315 | version: "1.3.0" 316 | vector_math: 317 | dependency: transitive 318 | description: 319 | name: vector_math 320 | url: "https://pub.dartlang.org" 321 | source: hosted 322 | version: "2.1.0" 323 | win32: 324 | dependency: transitive 325 | description: 326 | name: win32 327 | url: "https://pub.dartlang.org" 328 | source: hosted 329 | version: "2.0.4" 330 | xdg_directories: 331 | dependency: transitive 332 | description: 333 | name: xdg_directories 334 | url: "https://pub.dartlang.org" 335 | source: hosted 336 | version: "0.2.0" 337 | sdks: 338 | dart: ">=2.12.0 <3.0.0" 339 | flutter: ">=1.20.0" 340 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: sudoku_solver 2 | description: A flutter application for solving sudoku puzzles 3 | 4 | publish_to: "none" # Remove this line if you wish to publish to pub.dev 5 | 6 | version: 1.0.1+2 7 | 8 | environment: 9 | sdk: ">=2.7.0 <3.0.0" 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | cupertino_icons: ^1.0.0 15 | equatable: ^1.2.6 16 | flutter_icons: ^1.1.0 17 | provider: ^4.3.2+2 18 | easy_localization: ^3.0.0 19 | 20 | dev_dependencies: 21 | flutter_test: 22 | sdk: flutter 23 | 24 | flutter: 25 | uses-material-design: true 26 | 27 | assets: 28 | - assets/translations/ 29 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. 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:sudoku_solver/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(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 | --------------------------------------------------------------------------------