├── .gitignore ├── .metadata ├── README.md ├── android ├── app │ ├── build.gradle │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── vanethos │ │ │ └── writingprompt │ │ │ └── MainActivity.java │ │ └── res │ │ ├── drawable │ │ └── launch_background.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ └── values │ │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── ios ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── WorkspaceSettings.xcsettings └── Runner │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── 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 │ └── main.m ├── lib ├── common │ └── mapper.dart ├── data │ ├── local │ │ ├── database │ │ │ └── prompt_provider.dart │ │ └── model │ │ │ └── prompt_local.dart │ └── remote │ │ ├── api │ │ └── prompt_api.dart │ │ ├── model │ │ ├── prompt_remote.dart │ │ └── prompt_remote.g.dart │ │ └── serializers │ │ ├── serializers.dart │ │ └── serializers.g.dart ├── domain │ ├── bloc │ │ └── prompt_bloc.dart │ ├── managers │ │ └── prompt_manager.dart │ ├── mapper │ │ └── prompt_mappers.dart │ └── models │ │ └── prompt.dart ├── main.dart └── presentation │ ├── styles │ ├── colors.dart │ ├── dimensions.dart │ ├── strings.dart │ └── text_styles.dart │ ├── ui │ ├── home.dart │ ├── prompt_list.dart │ └── single_prompt.dart │ └── utils │ └── refresh_button.dart ├── pubspec.yaml ├── test ├── api_test.dart ├── mocks.dart └── widget_test.dart └── test_driver ├── app.dart └── app_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.lock 4 | *.log 5 | *.pyc 6 | *.swp 7 | .DS_Store 8 | .atom/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # Visual Studio Code related 20 | .vscode/ 21 | 22 | # Flutter/Dart/Pub related 23 | **/doc/api/ 24 | .dart_tool/ 25 | .flutter-plugins 26 | .packages 27 | .pub-cache/ 28 | .pub/ 29 | build/ 30 | 31 | # Android related 32 | **/android/**/gradle-wrapper.jar 33 | **/android/.gradle 34 | **/android/captures/ 35 | **/android/gradlew 36 | **/android/gradlew.bat 37 | **/android/local.properties 38 | **/android/**/GeneratedPluginRegistrant.java 39 | 40 | # iOS/XCode related 41 | **/ios/**/*.mode1v3 42 | **/ios/**/*.mode2v3 43 | **/ios/**/*.moved-aside 44 | **/ios/**/*.pbxuser 45 | **/ios/**/*.perspectivev3 46 | **/ios/**/*sync/ 47 | **/ios/**/.sconsign.dblite 48 | **/ios/**/.tags* 49 | **/ios/**/.vagrant/ 50 | **/ios/**/DerivedData/ 51 | **/ios/**/Icon? 52 | **/ios/**/Pods/ 53 | **/ios/**/.symlinks/ 54 | **/ios/**/profile 55 | **/ios/**/xcuserdata 56 | **/ios/.generated/ 57 | **/ios/Flutter/App.framework 58 | **/ios/Flutter/Flutter.framework 59 | **/ios/Flutter/Generated.xcconfig 60 | **/ios/Flutter/app.flx 61 | **/ios/Flutter/app.zip 62 | **/ios/Flutter/flutter_assets/ 63 | **/ios/ServiceDefinitions.json 64 | **/ios/Runner/GeneratedPluginRegistrant.* 65 | 66 | # Exceptions to above rules. 67 | !**/ios/**/default.mode1v3 68 | !**/ios/**/default.mode2v3 69 | !**/ios/**/default.pbxuser 70 | !**/ios/**/default.perspectivev3 71 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 72 | -------------------------------------------------------------------------------- /.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: 5391447fae6209bb21a89e6a5a6583cac1af9b4b 8 | channel: beta 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Writing Prompt 2 | 3 | A writing prompt application designed to showcase an approach to a "Clean"er architecture in Flutter with Bloc and RxDart, including Unit, Widget and Integration Testing 4 | 5 | ## Project Overview 6 | 7 | ![](https://media.giphy.com/media/2uIfed7omEos98lXST/giphy.gif) 8 | 9 | This app fetches data from `https://ineedaprompt.com` API and displays it on screen. The user can then ask for a new prompt, or see a history of prompts. 10 | 11 | ## Project Structure 12 | 13 | The project is structured as follows: 14 | 15 | ``` 16 | |_data 17 | \__local 18 | \__remote 19 | |_domain 20 | \__bloc 21 | \__managers 22 | \__mappers 23 | \__models 24 | |_presentation 25 | \__styles 26 | \__ui 27 | \__utils 28 | main.dart 29 | ``` 30 | 31 | ### Data 32 | 33 | The data stores both the API and local database information that will be displayed in the app. 34 | All the API endpoints, models, serialized classes and database helpers should be put here. 35 | 36 | ### Domain 37 | 38 | This layer connects the data layer to the presentation, preparing the information received from the local database or the server and managing the app state (i.e., if we need to fetch new data, fetch new data) 39 | 40 | To communicate with the widgets, we use the [BLOC architecture](https://medium.com/flutter-io/build-reactive-mobile-apps-in-flutter-companion-article-13950959e381) and `StreamBuilders`. 41 | 42 | ### Presentation 43 | 44 | Since Flutter does not have a `resources` folder as we see in Android, we need to declare each resource on a file. As such, I created the `styles` (naming to be changed) folder which include information about colors, strings and dimensions used in the app. 45 | 46 | Here we have all the widgets of the app, and their connection to the domain layer via the `bloc`. 47 | 48 | ### main.dart 49 | 50 | Since I chose not to use a dependency injection framework, this is where I create all the classes to be used in the app, inherited by each widget. 51 | 52 | ## Testing 53 | 54 | Though the app is not fully tested, I strived to show how to: 55 | - `Write Unit Tests` that test a small module (in this case method) of a class. They tested the network layer and domain layer 56 | - `Widget Tests` that assure that the `bloc` is providing the correct information to the widgets 57 | - `Integration Test` that test a normal app use, expecting a new prompt to be shown on screen. 58 | 59 | ## To-Do List 60 | - [ ] Create more App animations 61 | - [ ] Include more app features, such as share 62 | - [ ] Create more tests 63 | -------------------------------------------------------------------------------- /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 from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | android { 28 | compileSdkVersion 27 29 | 30 | lintOptions { 31 | disable 'InvalidPackage' 32 | } 33 | 34 | defaultConfig { 35 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 36 | applicationId "com.vanethos.writingprompt" 37 | minSdkVersion 16 38 | targetSdkVersion 27 39 | versionCode flutterVersionCode.toInteger() 40 | versionName flutterVersionName 41 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 42 | } 43 | 44 | buildTypes { 45 | release { 46 | // TODO: Add your own signing config for the release build. 47 | // Signing with the debug keys for now, so `flutter run --release` works. 48 | signingConfig signingConfigs.debug 49 | } 50 | } 51 | } 52 | 53 | flutter { 54 | source '../..' 55 | } 56 | 57 | dependencies { 58 | testImplementation 'junit:junit:4.12' 59 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 60 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 61 | } 62 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 15 | 19 | 26 | 30 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/vanethos/writingprompt/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.vanethos.writingprompt; 2 | 3 | import android.os.Bundle; 4 | import io.flutter.app.FlutterActivity; 5 | import io.flutter.plugins.GeneratedPluginRegistrant; 6 | 7 | public class MainActivity extends FlutterActivity { 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | GeneratedPluginRegistrant.registerWith(this); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vanethos/flutter-writting-prompt/e6244ecf99e271e3db48aebf7fe4d3a6a049b54b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vanethos/flutter-writting-prompt/e6244ecf99e271e3db48aebf7fe4d3a6a049b54b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vanethos/flutter-writting-prompt/e6244ecf99e271e3db48aebf7fe4d3a6a049b54b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vanethos/flutter-writting-prompt/e6244ecf99e271e3db48aebf7fe4d3a6a049b54b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vanethos/flutter-writting-prompt/e6244ecf99e271e3db48aebf7fe4d3a6a049b54b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.2.1' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | } 23 | subprojects { 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /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 parse_KV_file(file, separator='=') 14 | file_abs_path = File.expand_path(file) 15 | if !File.exists? file_abs_path 16 | return []; 17 | end 18 | pods_ary = [] 19 | skip_line_start_symbols = ["#", "/"] 20 | File.foreach(file_abs_path) { |line| 21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 22 | plugin = line.split(pattern=separator) 23 | if plugin.length == 2 24 | podname = plugin[0].strip() 25 | path = plugin[1].strip() 26 | podpath = File.expand_path("#{path}", file_abs_path) 27 | pods_ary.push({:name => podname, :path => podpath}); 28 | else 29 | puts "Invalid plugin specification: #{line}" 30 | end 31 | } 32 | return pods_ary 33 | end 34 | 35 | target 'Runner' do 36 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 37 | # referring to absolute paths on developers' machines. 38 | system('rm -rf .symlinks') 39 | system('mkdir -p .symlinks/plugins') 40 | 41 | # Flutter Pods 42 | generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') 43 | if generated_xcode_build_settings.empty? 44 | puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first." 45 | end 46 | generated_xcode_build_settings.map { |p| 47 | if p[:name] == 'FLUTTER_FRAMEWORK_DIR' 48 | symlink = File.join('.symlinks', 'flutter') 49 | File.symlink(File.dirname(p[:path]), symlink) 50 | pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) 51 | end 52 | } 53 | 54 | # Plugin Pods 55 | plugin_pods = parse_KV_file('../.flutter-plugins') 56 | plugin_pods.map { |p| 57 | symlink = File.join('.symlinks', 'plugins', p[:name]) 58 | File.symlink(p[:path], symlink) 59 | pod p[:name], :path => File.join(symlink, 'ios') 60 | } 61 | end 62 | 63 | post_install do |installer| 64 | installer.pods_project.targets.each do |target| 65 | target.build_configurations.each do |config| 66 | config.build_settings['ENABLE_BITCODE'] = 'NO' 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /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 | 231CE2EFDC78C91147F94983 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9755D482945FBD87A76CFFAE /* libPods-Runner.a */; }; 12 | 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */ = {isa = PBXBuildFile; fileRef = 2D5378251FAA1A9400D5DBA9 /* flutter_assets */; }; 13 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 14 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 15 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 16 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 17 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 18 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; 19 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 20 | 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 21 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 22 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 23 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXCopyFilesBuildPhase section */ 27 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 28 | isa = PBXCopyFilesBuildPhase; 29 | buildActionMask = 2147483647; 30 | dstPath = ""; 31 | dstSubfolderSpec = 10; 32 | files = ( 33 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, 34 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, 35 | ); 36 | name = "Embed Frameworks"; 37 | runOnlyForDeploymentPostprocessing = 0; 38 | }; 39 | /* End PBXCopyFilesBuildPhase section */ 40 | 41 | /* Begin PBXFileReference section */ 42 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 43 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 44 | 2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = Flutter/flutter_assets; sourceTree = SOURCE_ROOT; }; 45 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 46 | 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 47 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 48 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 49 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 50 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 51 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 52 | 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 53 | 9755D482945FBD87A76CFFAE /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 55 | 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 56 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 57 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 58 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 59 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 60 | /* End PBXFileReference section */ 61 | 62 | /* Begin PBXFrameworksBuildPhase section */ 63 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 64 | isa = PBXFrameworksBuildPhase; 65 | buildActionMask = 2147483647; 66 | files = ( 67 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 68 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 69 | 231CE2EFDC78C91147F94983 /* libPods-Runner.a in Frameworks */, 70 | ); 71 | runOnlyForDeploymentPostprocessing = 0; 72 | }; 73 | /* End PBXFrameworksBuildPhase section */ 74 | 75 | /* Begin PBXGroup section */ 76 | 635BEB36E3234598F4C74B7A /* Frameworks */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | 9755D482945FBD87A76CFFAE /* libPods-Runner.a */, 80 | ); 81 | name = Frameworks; 82 | sourceTree = ""; 83 | }; 84 | 9740EEB11CF90186004384FC /* Flutter */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | 2D5378251FAA1A9400D5DBA9 /* flutter_assets */, 88 | 3B80C3931E831B6300D905FE /* App.framework */, 89 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 90 | 9740EEBA1CF902C7004384FC /* Flutter.framework */, 91 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 92 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 93 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 94 | ); 95 | name = Flutter; 96 | sourceTree = ""; 97 | }; 98 | 97C146E51CF9000F007C117D = { 99 | isa = PBXGroup; 100 | children = ( 101 | 9740EEB11CF90186004384FC /* Flutter */, 102 | 97C146F01CF9000F007C117D /* Runner */, 103 | 97C146EF1CF9000F007C117D /* Products */, 104 | CBF59BFB2242B9CFB85133B8 /* Pods */, 105 | 635BEB36E3234598F4C74B7A /* Frameworks */, 106 | ); 107 | sourceTree = ""; 108 | }; 109 | 97C146EF1CF9000F007C117D /* Products */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 97C146EE1CF9000F007C117D /* Runner.app */, 113 | ); 114 | name = Products; 115 | sourceTree = ""; 116 | }; 117 | 97C146F01CF9000F007C117D /* Runner */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 121 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 122 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 123 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 124 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 125 | 97C147021CF9000F007C117D /* Info.plist */, 126 | 97C146F11CF9000F007C117D /* Supporting Files */, 127 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 128 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 129 | ); 130 | path = Runner; 131 | sourceTree = ""; 132 | }; 133 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | 97C146F21CF9000F007C117D /* main.m */, 137 | ); 138 | name = "Supporting Files"; 139 | sourceTree = ""; 140 | }; 141 | CBF59BFB2242B9CFB85133B8 /* Pods */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | ); 145 | name = Pods; 146 | sourceTree = ""; 147 | }; 148 | /* End PBXGroup section */ 149 | 150 | /* Begin PBXNativeTarget section */ 151 | 97C146ED1CF9000F007C117D /* Runner */ = { 152 | isa = PBXNativeTarget; 153 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 154 | buildPhases = ( 155 | 48284AE3518D92EB037ABFB0 /* [CP] Check Pods Manifest.lock */, 156 | 9740EEB61CF901F6004384FC /* Run Script */, 157 | 97C146EA1CF9000F007C117D /* Sources */, 158 | 97C146EB1CF9000F007C117D /* Frameworks */, 159 | 97C146EC1CF9000F007C117D /* Resources */, 160 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 161 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 162 | 6E2552A6B4AB1FF8CB3DB34E /* [CP] Embed Pods Frameworks */, 163 | ); 164 | buildRules = ( 165 | ); 166 | dependencies = ( 167 | ); 168 | name = Runner; 169 | productName = Runner; 170 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 171 | productType = "com.apple.product-type.application"; 172 | }; 173 | /* End PBXNativeTarget section */ 174 | 175 | /* Begin PBXProject section */ 176 | 97C146E61CF9000F007C117D /* Project object */ = { 177 | isa = PBXProject; 178 | attributes = { 179 | LastUpgradeCheck = 0910; 180 | ORGANIZATIONNAME = "The Chromium Authors"; 181 | TargetAttributes = { 182 | 97C146ED1CF9000F007C117D = { 183 | CreatedOnToolsVersion = 7.3.1; 184 | }; 185 | }; 186 | }; 187 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 188 | compatibilityVersion = "Xcode 3.2"; 189 | developmentRegion = English; 190 | hasScannedForEncodings = 0; 191 | knownRegions = ( 192 | en, 193 | Base, 194 | ); 195 | mainGroup = 97C146E51CF9000F007C117D; 196 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 197 | projectDirPath = ""; 198 | projectRoot = ""; 199 | targets = ( 200 | 97C146ED1CF9000F007C117D /* Runner */, 201 | ); 202 | }; 203 | /* End PBXProject section */ 204 | 205 | /* Begin PBXResourcesBuildPhase section */ 206 | 97C146EC1CF9000F007C117D /* Resources */ = { 207 | isa = PBXResourcesBuildPhase; 208 | buildActionMask = 2147483647; 209 | files = ( 210 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 211 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 212 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 213 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 214 | 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */, 215 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 216 | ); 217 | runOnlyForDeploymentPostprocessing = 0; 218 | }; 219 | /* End PBXResourcesBuildPhase section */ 220 | 221 | /* Begin PBXShellScriptBuildPhase section */ 222 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 223 | isa = PBXShellScriptBuildPhase; 224 | buildActionMask = 2147483647; 225 | files = ( 226 | ); 227 | inputPaths = ( 228 | ); 229 | name = "Thin Binary"; 230 | outputPaths = ( 231 | ); 232 | runOnlyForDeploymentPostprocessing = 0; 233 | shellPath = /bin/sh; 234 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; 235 | }; 236 | 48284AE3518D92EB037ABFB0 /* [CP] Check Pods Manifest.lock */ = { 237 | isa = PBXShellScriptBuildPhase; 238 | buildActionMask = 2147483647; 239 | files = ( 240 | ); 241 | inputFileListPaths = ( 242 | ); 243 | inputPaths = ( 244 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 245 | "${PODS_ROOT}/Manifest.lock", 246 | ); 247 | name = "[CP] Check Pods Manifest.lock"; 248 | outputFileListPaths = ( 249 | ); 250 | outputPaths = ( 251 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 252 | ); 253 | runOnlyForDeploymentPostprocessing = 0; 254 | shellPath = /bin/sh; 255 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 256 | showEnvVarsInLog = 0; 257 | }; 258 | 6E2552A6B4AB1FF8CB3DB34E /* [CP] Embed Pods Frameworks */ = { 259 | isa = PBXShellScriptBuildPhase; 260 | buildActionMask = 2147483647; 261 | files = ( 262 | ); 263 | inputFileListPaths = ( 264 | ); 265 | inputPaths = ( 266 | "${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", 267 | "${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework", 268 | ); 269 | name = "[CP] Embed Pods Frameworks"; 270 | outputFileListPaths = ( 271 | ); 272 | outputPaths = ( 273 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", 274 | ); 275 | runOnlyForDeploymentPostprocessing = 0; 276 | shellPath = /bin/sh; 277 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 278 | showEnvVarsInLog = 0; 279 | }; 280 | 9740EEB61CF901F6004384FC /* Run Script */ = { 281 | isa = PBXShellScriptBuildPhase; 282 | buildActionMask = 2147483647; 283 | files = ( 284 | ); 285 | inputPaths = ( 286 | ); 287 | name = "Run Script"; 288 | outputPaths = ( 289 | ); 290 | runOnlyForDeploymentPostprocessing = 0; 291 | shellPath = /bin/sh; 292 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 293 | }; 294 | /* End PBXShellScriptBuildPhase section */ 295 | 296 | /* Begin PBXSourcesBuildPhase section */ 297 | 97C146EA1CF9000F007C117D /* Sources */ = { 298 | isa = PBXSourcesBuildPhase; 299 | buildActionMask = 2147483647; 300 | files = ( 301 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 302 | 97C146F31CF9000F007C117D /* main.m in Sources */, 303 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 304 | ); 305 | runOnlyForDeploymentPostprocessing = 0; 306 | }; 307 | /* End PBXSourcesBuildPhase section */ 308 | 309 | /* Begin PBXVariantGroup section */ 310 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 311 | isa = PBXVariantGroup; 312 | children = ( 313 | 97C146FB1CF9000F007C117D /* Base */, 314 | ); 315 | name = Main.storyboard; 316 | sourceTree = ""; 317 | }; 318 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 319 | isa = PBXVariantGroup; 320 | children = ( 321 | 97C147001CF9000F007C117D /* Base */, 322 | ); 323 | name = LaunchScreen.storyboard; 324 | sourceTree = ""; 325 | }; 326 | /* End PBXVariantGroup section */ 327 | 328 | /* Begin XCBuildConfiguration section */ 329 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 330 | isa = XCBuildConfiguration; 331 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 332 | buildSettings = { 333 | ALWAYS_SEARCH_USER_PATHS = NO; 334 | CLANG_ANALYZER_NONNULL = YES; 335 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 336 | CLANG_CXX_LIBRARY = "libc++"; 337 | CLANG_ENABLE_MODULES = YES; 338 | CLANG_ENABLE_OBJC_ARC = YES; 339 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 340 | CLANG_WARN_BOOL_CONVERSION = YES; 341 | CLANG_WARN_COMMA = YES; 342 | CLANG_WARN_CONSTANT_CONVERSION = YES; 343 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 344 | CLANG_WARN_EMPTY_BODY = YES; 345 | CLANG_WARN_ENUM_CONVERSION = YES; 346 | CLANG_WARN_INFINITE_RECURSION = YES; 347 | CLANG_WARN_INT_CONVERSION = YES; 348 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 349 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 350 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 351 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 352 | CLANG_WARN_STRICT_PROTOTYPES = YES; 353 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 354 | CLANG_WARN_UNREACHABLE_CODE = YES; 355 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 356 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 357 | COPY_PHASE_STRIP = NO; 358 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 359 | ENABLE_NS_ASSERTIONS = NO; 360 | ENABLE_STRICT_OBJC_MSGSEND = YES; 361 | GCC_C_LANGUAGE_STANDARD = gnu99; 362 | GCC_NO_COMMON_BLOCKS = YES; 363 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 364 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 365 | GCC_WARN_UNDECLARED_SELECTOR = YES; 366 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 367 | GCC_WARN_UNUSED_FUNCTION = YES; 368 | GCC_WARN_UNUSED_VARIABLE = YES; 369 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 370 | MTL_ENABLE_DEBUG_INFO = NO; 371 | SDKROOT = iphoneos; 372 | TARGETED_DEVICE_FAMILY = "1,2"; 373 | VALIDATE_PRODUCT = YES; 374 | }; 375 | name = Profile; 376 | }; 377 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 378 | isa = XCBuildConfiguration; 379 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 380 | buildSettings = { 381 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 382 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 383 | DEVELOPMENT_TEAM = S8QB4VV633; 384 | ENABLE_BITCODE = NO; 385 | FRAMEWORK_SEARCH_PATHS = ( 386 | "$(inherited)", 387 | "$(PROJECT_DIR)/Flutter", 388 | ); 389 | INFOPLIST_FILE = Runner/Info.plist; 390 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 391 | LIBRARY_SEARCH_PATHS = ( 392 | "$(inherited)", 393 | "$(PROJECT_DIR)/Flutter", 394 | ); 395 | PRODUCT_BUNDLE_IDENTIFIER = com.vanethos.writingPrompt; 396 | PRODUCT_NAME = "$(TARGET_NAME)"; 397 | VERSIONING_SYSTEM = "apple-generic"; 398 | }; 399 | name = Profile; 400 | }; 401 | 97C147031CF9000F007C117D /* Debug */ = { 402 | isa = XCBuildConfiguration; 403 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 404 | buildSettings = { 405 | ALWAYS_SEARCH_USER_PATHS = NO; 406 | CLANG_ANALYZER_NONNULL = YES; 407 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 408 | CLANG_CXX_LIBRARY = "libc++"; 409 | CLANG_ENABLE_MODULES = YES; 410 | CLANG_ENABLE_OBJC_ARC = YES; 411 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 412 | CLANG_WARN_BOOL_CONVERSION = YES; 413 | CLANG_WARN_COMMA = YES; 414 | CLANG_WARN_CONSTANT_CONVERSION = YES; 415 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 416 | CLANG_WARN_EMPTY_BODY = YES; 417 | CLANG_WARN_ENUM_CONVERSION = YES; 418 | CLANG_WARN_INFINITE_RECURSION = YES; 419 | CLANG_WARN_INT_CONVERSION = YES; 420 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 421 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 422 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 423 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 424 | CLANG_WARN_STRICT_PROTOTYPES = YES; 425 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 426 | CLANG_WARN_UNREACHABLE_CODE = YES; 427 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 428 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 429 | COPY_PHASE_STRIP = NO; 430 | DEBUG_INFORMATION_FORMAT = dwarf; 431 | ENABLE_STRICT_OBJC_MSGSEND = YES; 432 | ENABLE_TESTABILITY = YES; 433 | GCC_C_LANGUAGE_STANDARD = gnu99; 434 | GCC_DYNAMIC_NO_PIC = NO; 435 | GCC_NO_COMMON_BLOCKS = YES; 436 | GCC_OPTIMIZATION_LEVEL = 0; 437 | GCC_PREPROCESSOR_DEFINITIONS = ( 438 | "DEBUG=1", 439 | "$(inherited)", 440 | ); 441 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 442 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 443 | GCC_WARN_UNDECLARED_SELECTOR = YES; 444 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 445 | GCC_WARN_UNUSED_FUNCTION = YES; 446 | GCC_WARN_UNUSED_VARIABLE = YES; 447 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 448 | MTL_ENABLE_DEBUG_INFO = YES; 449 | ONLY_ACTIVE_ARCH = YES; 450 | SDKROOT = iphoneos; 451 | TARGETED_DEVICE_FAMILY = "1,2"; 452 | }; 453 | name = Debug; 454 | }; 455 | 97C147041CF9000F007C117D /* Release */ = { 456 | isa = XCBuildConfiguration; 457 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 458 | buildSettings = { 459 | ALWAYS_SEARCH_USER_PATHS = NO; 460 | CLANG_ANALYZER_NONNULL = YES; 461 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 462 | CLANG_CXX_LIBRARY = "libc++"; 463 | CLANG_ENABLE_MODULES = YES; 464 | CLANG_ENABLE_OBJC_ARC = YES; 465 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 466 | CLANG_WARN_BOOL_CONVERSION = YES; 467 | CLANG_WARN_COMMA = YES; 468 | CLANG_WARN_CONSTANT_CONVERSION = YES; 469 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 470 | CLANG_WARN_EMPTY_BODY = YES; 471 | CLANG_WARN_ENUM_CONVERSION = YES; 472 | CLANG_WARN_INFINITE_RECURSION = YES; 473 | CLANG_WARN_INT_CONVERSION = YES; 474 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 475 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 476 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 477 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 478 | CLANG_WARN_STRICT_PROTOTYPES = YES; 479 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 480 | CLANG_WARN_UNREACHABLE_CODE = YES; 481 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 482 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 483 | COPY_PHASE_STRIP = NO; 484 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 485 | ENABLE_NS_ASSERTIONS = NO; 486 | ENABLE_STRICT_OBJC_MSGSEND = YES; 487 | GCC_C_LANGUAGE_STANDARD = gnu99; 488 | GCC_NO_COMMON_BLOCKS = YES; 489 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 490 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 491 | GCC_WARN_UNDECLARED_SELECTOR = YES; 492 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 493 | GCC_WARN_UNUSED_FUNCTION = YES; 494 | GCC_WARN_UNUSED_VARIABLE = YES; 495 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 496 | MTL_ENABLE_DEBUG_INFO = NO; 497 | SDKROOT = iphoneos; 498 | TARGETED_DEVICE_FAMILY = "1,2"; 499 | VALIDATE_PRODUCT = YES; 500 | }; 501 | name = Release; 502 | }; 503 | 97C147061CF9000F007C117D /* Debug */ = { 504 | isa = XCBuildConfiguration; 505 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 506 | buildSettings = { 507 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 508 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 509 | ENABLE_BITCODE = NO; 510 | FRAMEWORK_SEARCH_PATHS = ( 511 | "$(inherited)", 512 | "$(PROJECT_DIR)/Flutter", 513 | ); 514 | INFOPLIST_FILE = Runner/Info.plist; 515 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 516 | LIBRARY_SEARCH_PATHS = ( 517 | "$(inherited)", 518 | "$(PROJECT_DIR)/Flutter", 519 | ); 520 | PRODUCT_BUNDLE_IDENTIFIER = com.vanethos.writingPrompt; 521 | PRODUCT_NAME = "$(TARGET_NAME)"; 522 | VERSIONING_SYSTEM = "apple-generic"; 523 | }; 524 | name = Debug; 525 | }; 526 | 97C147071CF9000F007C117D /* Release */ = { 527 | isa = XCBuildConfiguration; 528 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 529 | buildSettings = { 530 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 531 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 532 | ENABLE_BITCODE = NO; 533 | FRAMEWORK_SEARCH_PATHS = ( 534 | "$(inherited)", 535 | "$(PROJECT_DIR)/Flutter", 536 | ); 537 | INFOPLIST_FILE = Runner/Info.plist; 538 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 539 | LIBRARY_SEARCH_PATHS = ( 540 | "$(inherited)", 541 | "$(PROJECT_DIR)/Flutter", 542 | ); 543 | PRODUCT_BUNDLE_IDENTIFIER = com.vanethos.writingPrompt; 544 | PRODUCT_NAME = "$(TARGET_NAME)"; 545 | VERSIONING_SYSTEM = "apple-generic"; 546 | }; 547 | name = Release; 548 | }; 549 | /* End XCBuildConfiguration section */ 550 | 551 | /* Begin XCConfigurationList section */ 552 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 553 | isa = XCConfigurationList; 554 | buildConfigurations = ( 555 | 97C147031CF9000F007C117D /* Debug */, 556 | 97C147041CF9000F007C117D /* Release */, 557 | 249021D3217E4FDB00AE95B9 /* Profile */, 558 | ); 559 | defaultConfigurationIsVisible = 0; 560 | defaultConfigurationName = Release; 561 | }; 562 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 563 | isa = XCConfigurationList; 564 | buildConfigurations = ( 565 | 97C147061CF9000F007C117D /* Debug */, 566 | 97C147071CF9000F007C117D /* Release */, 567 | 249021D4217E4FDB00AE95B9 /* Profile */, 568 | ); 569 | defaultConfigurationIsVisible = 0; 570 | defaultConfigurationName = Release; 571 | }; 572 | /* End XCConfigurationList section */ 573 | }; 574 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 575 | } 576 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildSystemType 6 | Original 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 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/Vanethos/flutter-writting-prompt/e6244ecf99e271e3db48aebf7fe4d3a6a049b54b/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/Vanethos/flutter-writting-prompt/e6244ecf99e271e3db48aebf7fe4d3a6a049b54b/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/Vanethos/flutter-writting-prompt/e6244ecf99e271e3db48aebf7fe4d3a6a049b54b/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/Vanethos/flutter-writting-prompt/e6244ecf99e271e3db48aebf7fe4d3a6a049b54b/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/Vanethos/flutter-writting-prompt/e6244ecf99e271e3db48aebf7fe4d3a6a049b54b/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/Vanethos/flutter-writting-prompt/e6244ecf99e271e3db48aebf7fe4d3a6a049b54b/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/Vanethos/flutter-writting-prompt/e6244ecf99e271e3db48aebf7fe4d3a6a049b54b/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/Vanethos/flutter-writting-prompt/e6244ecf99e271e3db48aebf7fe4d3a6a049b54b/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/Vanethos/flutter-writting-prompt/e6244ecf99e271e3db48aebf7fe4d3a6a049b54b/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/Vanethos/flutter-writting-prompt/e6244ecf99e271e3db48aebf7fe4d3a6a049b54b/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/Vanethos/flutter-writting-prompt/e6244ecf99e271e3db48aebf7fe4d3a6a049b54b/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/Vanethos/flutter-writting-prompt/e6244ecf99e271e3db48aebf7fe4d3a6a049b54b/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/Vanethos/flutter-writting-prompt/e6244ecf99e271e3db48aebf7fe4d3a6a049b54b/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/Vanethos/flutter-writting-prompt/e6244ecf99e271e3db48aebf7fe4d3a6a049b54b/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/Vanethos/flutter-writting-prompt/e6244ecf99e271e3db48aebf7fe4d3a6a049b54b/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/Vanethos/flutter-writting-prompt/e6244ecf99e271e3db48aebf7fe4d3a6a049b54b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vanethos/flutter-writting-prompt/e6244ecf99e271e3db48aebf7fe4d3a6a049b54b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vanethos/flutter-writting-prompt/e6244ecf99e271e3db48aebf7fe4d3a6a049b54b/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 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | writing_prompt 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/common/mapper.dart: -------------------------------------------------------------------------------- 1 | abstract class Mapper { 2 | To map(From value); 3 | 4 | List mapList(List values) { 5 | if (values == null) return new List(0); 6 | 7 | List returnValues = new List(); 8 | for (var value in values) { 9 | returnValues.add(map(value)); 10 | } 11 | return returnValues; 12 | } 13 | } -------------------------------------------------------------------------------- /lib/data/local/database/prompt_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:path/path.dart'; 2 | import 'package:sqflite/sqflite.dart'; 3 | import 'package:writing_prompt/data/local/model/prompt_local.dart'; 4 | 5 | final String tablePrompt = 'prompt'; 6 | final String columnId = '_id'; 7 | final String columnTitle = 'title'; 8 | final String columnDone = 'done'; 9 | 10 | class DBHelper { 11 | Database _db; 12 | 13 | Future get db async { 14 | if(_db != null) 15 | return _db; 16 | _db = await initDb(); 17 | return _db; 18 | } 19 | 20 | Future initDb() async { 21 | String path = join(await getDatabasesPath(), "test.db"); 22 | return await openDatabase(path, version: 1, 23 | onCreate: (Database db, int version) async { 24 | await db.execute(''' 25 | create table $tablePrompt ( 26 | $columnId integer primary key autoincrement, 27 | $columnTitle text not null, 28 | $columnDone integer not null) 29 | '''); 30 | }); 31 | } 32 | 33 | Future insert(PromptLocal prompt) async { 34 | var dbClient = await db; 35 | return await dbClient.insert(tablePrompt, prompt.toMap()); 36 | } 37 | 38 | Future> getPrompts() async { 39 | var dbClient = await db; 40 | List maps = await dbClient.query(tablePrompt); 41 | List prompts =List(); 42 | for (var value in maps) { 43 | prompts.add(PromptLocal.fromMap(value)); 44 | } 45 | return prompts; 46 | } 47 | 48 | Future delete(int id) async { 49 | var dbClient = await db; 50 | return await dbClient.delete(tablePrompt, where: '$columnId = ?', whereArgs: [id]); 51 | } 52 | 53 | Future update(PromptLocal prompt) async { 54 | var dbClient = await db; 55 | return await dbClient.update(tablePrompt, prompt.toMap(), 56 | where: '$columnId = ?', whereArgs: [prompt.id]); 57 | } 58 | 59 | Future close() async { 60 | await db 61 | ..close(); 62 | } 63 | 64 | DBHelper(); 65 | } -------------------------------------------------------------------------------- /lib/data/local/model/prompt_local.dart: -------------------------------------------------------------------------------- 1 | import 'package:writing_prompt/data/local/database/prompt_provider.dart'; 2 | 3 | class PromptLocal { 4 | int id; 5 | String prompt; 6 | bool done; 7 | 8 | PromptLocal(this.id, this.prompt, this.done); 9 | 10 | Map toMap() { 11 | var map = { 12 | columnTitle: prompt, 13 | columnDone: done == true ? 1 : 0 14 | }; 15 | if (id != null) { 16 | map[columnId] = id; 17 | } 18 | return map; 19 | } 20 | 21 | PromptLocal.fromMap(Map map) { 22 | id = map[columnId]; 23 | prompt = map[columnTitle]; 24 | done = map[columnDone] == 1; 25 | } 26 | } -------------------------------------------------------------------------------- /lib/data/remote/api/prompt_api.dart: -------------------------------------------------------------------------------- 1 | import 'package:writing_prompt/data/remote/model/prompt_remote.dart'; 2 | import 'dart:async'; 3 | 4 | import 'package:http/http.dart' as http; 5 | 6 | class PromptApi { 7 | Future fetchPrompt() async { 8 | final response = await http.get('https://ineedaprompt.com/dictionary/default/prompt?q=adj+noun+adv+verb+noun+location'); 9 | 10 | if (response.statusCode == 200) { 11 | // If server returns an OK response, parse the JSON 12 | return fromJson(response.body); 13 | } else { 14 | // If that response was not OK, throw an error. 15 | throw Exception('Failed to load post'); 16 | } 17 | } 18 | } 19 | 20 | 21 | -------------------------------------------------------------------------------- /lib/data/remote/model/prompt_remote.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert' as json; 2 | 3 | import 'package:built_collection/built_collection.dart'; 4 | import 'package:built_value/built_value.dart'; 5 | import 'package:built_value/serializer.dart'; 6 | import 'package:writing_prompt/data/remote/serializers/serializers.dart'; 7 | 8 | part 'prompt_remote.g.dart'; 9 | 10 | // we need to run in the terminal: 11 | // flutter packages pub run build_runner watch 12 | 13 | abstract class PromptRemote implements Built { 14 | static Serializer get serializer => _$promptRemoteSerializer; 15 | 16 | String get english; 17 | 18 | int get count; 19 | 20 | PromptRemote._(); 21 | 22 | factory PromptRemote([updates(PromptRemoteBuilder b)]) = _$PromptRemote; 23 | } 24 | 25 | PromptRemote fromJson(String jsonStr) { 26 | final parsed = json.jsonDecode(jsonStr); 27 | PromptRemote article = standardSerializers.deserializeWith(PromptRemote.serializer, parsed); 28 | return article; 29 | } 30 | -------------------------------------------------------------------------------- /lib/data/remote/model/prompt_remote.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'prompt_remote.dart'; 4 | 5 | // ************************************************************************** 6 | // BuiltValueGenerator 7 | // ************************************************************************** 8 | 9 | // ignore_for_file: always_put_control_body_on_new_line 10 | // ignore_for_file: annotate_overrides 11 | // ignore_for_file: avoid_annotating_with_dynamic 12 | // ignore_for_file: avoid_catches_without_on_clauses 13 | // ignore_for_file: avoid_returning_this 14 | // ignore_for_file: lines_longer_than_80_chars 15 | // ignore_for_file: omit_local_variable_types 16 | // ignore_for_file: prefer_expression_function_bodies 17 | // ignore_for_file: sort_constructors_first 18 | // ignore_for_file: unnecessary_const 19 | // ignore_for_file: unnecessary_new 20 | // ignore_for_file: test_types_in_equals 21 | 22 | Serializer _$promptRemoteSerializer = 23 | new _$PromptRemoteSerializer(); 24 | 25 | class _$PromptRemoteSerializer implements StructuredSerializer { 26 | @override 27 | final Iterable types = const [PromptRemote, _$PromptRemote]; 28 | @override 29 | final String wireName = 'PromptRemote'; 30 | 31 | @override 32 | Iterable serialize(Serializers serializers, PromptRemote object, 33 | {FullType specifiedType = FullType.unspecified}) { 34 | final result = [ 35 | 'english', 36 | serializers.serialize(object.english, 37 | specifiedType: const FullType(String)), 38 | 'count', 39 | serializers.serialize(object.count, specifiedType: const FullType(int)), 40 | ]; 41 | 42 | return result; 43 | } 44 | 45 | @override 46 | PromptRemote deserialize(Serializers serializers, Iterable serialized, 47 | {FullType specifiedType = FullType.unspecified}) { 48 | final result = new PromptRemoteBuilder(); 49 | 50 | final iterator = serialized.iterator; 51 | while (iterator.moveNext()) { 52 | final key = iterator.current as String; 53 | iterator.moveNext(); 54 | final dynamic value = iterator.current; 55 | switch (key) { 56 | case 'english': 57 | result.english = serializers.deserialize(value, 58 | specifiedType: const FullType(String)) as String; 59 | break; 60 | case 'count': 61 | result.count = serializers.deserialize(value, 62 | specifiedType: const FullType(int)) as int; 63 | break; 64 | } 65 | } 66 | 67 | return result.build(); 68 | } 69 | } 70 | 71 | class _$PromptRemote extends PromptRemote { 72 | @override 73 | final String english; 74 | @override 75 | final int count; 76 | 77 | factory _$PromptRemote([void updates(PromptRemoteBuilder b)]) => 78 | (new PromptRemoteBuilder()..update(updates)).build(); 79 | 80 | _$PromptRemote._({this.english, this.count}) : super._() { 81 | if (english == null) { 82 | throw new BuiltValueNullFieldError('PromptRemote', 'english'); 83 | } 84 | if (count == null) { 85 | throw new BuiltValueNullFieldError('PromptRemote', 'count'); 86 | } 87 | } 88 | 89 | @override 90 | PromptRemote rebuild(void updates(PromptRemoteBuilder b)) => 91 | (toBuilder()..update(updates)).build(); 92 | 93 | @override 94 | PromptRemoteBuilder toBuilder() => new PromptRemoteBuilder()..replace(this); 95 | 96 | @override 97 | bool operator ==(Object other) { 98 | if (identical(other, this)) return true; 99 | return other is PromptRemote && 100 | english == other.english && 101 | count == other.count; 102 | } 103 | 104 | @override 105 | int get hashCode { 106 | return $jf($jc($jc(0, english.hashCode), count.hashCode)); 107 | } 108 | 109 | @override 110 | String toString() { 111 | return (newBuiltValueToStringHelper('PromptRemote') 112 | ..add('english', english) 113 | ..add('count', count)) 114 | .toString(); 115 | } 116 | } 117 | 118 | class PromptRemoteBuilder 119 | implements Builder { 120 | _$PromptRemote _$v; 121 | 122 | String _english; 123 | String get english => _$this._english; 124 | set english(String english) => _$this._english = english; 125 | 126 | int _count; 127 | int get count => _$this._count; 128 | set count(int count) => _$this._count = count; 129 | 130 | PromptRemoteBuilder(); 131 | 132 | PromptRemoteBuilder get _$this { 133 | if (_$v != null) { 134 | _english = _$v.english; 135 | _count = _$v.count; 136 | _$v = null; 137 | } 138 | return this; 139 | } 140 | 141 | @override 142 | void replace(PromptRemote other) { 143 | if (other == null) { 144 | throw new ArgumentError.notNull('other'); 145 | } 146 | _$v = other as _$PromptRemote; 147 | } 148 | 149 | @override 150 | void update(void updates(PromptRemoteBuilder b)) { 151 | if (updates != null) updates(this); 152 | } 153 | 154 | @override 155 | _$PromptRemote build() { 156 | final _$result = 157 | _$v ?? new _$PromptRemote._(english: english, count: count); 158 | replace(_$result); 159 | return _$result; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /lib/data/remote/serializers/serializers.dart: -------------------------------------------------------------------------------- 1 | library serializers; 2 | 3 | import 'package:built_value/serializer.dart'; 4 | import 'package:built_value/standard_json_plugin.dart'; 5 | import 'package:writing_prompt/data/remote/model/prompt_remote.dart'; 6 | 7 | part 'serializers.g.dart'; 8 | 9 | 10 | /// Example of how to use built_value serialization. 11 | /// 12 | /// Declare a top level [Serializers] field called serializers. Annotate it 13 | /// with [SerializersFor] and provide a `const` `List` of types you want to 14 | /// be serializable. 15 | /// 16 | /// The built_value code generator will provide the implementation. It will 17 | /// contain serializers for all the types asked for explicitly plus all the 18 | /// types needed transitively via fields. 19 | /// 20 | /// You usually only need to do this once per project. 21 | @SerializersFor(const [ 22 | PromptRemote, 23 | ]) 24 | 25 | Serializers serializers = _$serializers; 26 | 27 | Serializers standardSerializers = 28 | (serializers.toBuilder()..addPlugin(StandardJsonPlugin())).build(); -------------------------------------------------------------------------------- /lib/data/remote/serializers/serializers.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of serializers; 4 | 5 | // ************************************************************************** 6 | // BuiltValueGenerator 7 | // ************************************************************************** 8 | 9 | // ignore_for_file: always_put_control_body_on_new_line 10 | // ignore_for_file: annotate_overrides 11 | // ignore_for_file: avoid_annotating_with_dynamic 12 | // ignore_for_file: avoid_catches_without_on_clauses 13 | // ignore_for_file: avoid_returning_this 14 | // ignore_for_file: lines_longer_than_80_chars 15 | // ignore_for_file: omit_local_variable_types 16 | // ignore_for_file: prefer_expression_function_bodies 17 | // ignore_for_file: sort_constructors_first 18 | // ignore_for_file: unnecessary_const 19 | // ignore_for_file: unnecessary_new 20 | // ignore_for_file: test_types_in_equals 21 | 22 | Serializers _$serializers = 23 | (new Serializers().toBuilder()..add(PromptRemote.serializer)).build(); 24 | -------------------------------------------------------------------------------- /lib/domain/bloc/prompt_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:rxdart/rxdart.dart'; 2 | import 'package:writing_prompt/domain/managers/prompt_manager.dart'; 3 | import 'dart:async'; 4 | 5 | import 'package:writing_prompt/domain/models/prompt.dart'; 6 | 7 | class PromptBloc { 8 | PromptManager _promptManager; 9 | 10 | final _promptSubject = BehaviorSubject(); 11 | final _promptHistorySubject = BehaviorSubject>(); 12 | final _promptUpdateSubject = PublishSubject(); 13 | final _promptInsertSubject = PublishSubject(); 14 | final _fetchPromptSubject = PublishSubject(); 15 | 16 | 17 | 18 | Stream get prompt => _promptSubject.stream; 19 | Stream> get promptHistory => _promptHistorySubject.stream; 20 | Sink get promptUpdate => _promptUpdateSubject.sink; 21 | Sink get promptInsert => _promptInsertSubject.sink; 22 | Sink get fetchPrompt => _fetchPromptSubject.sink; 23 | 24 | PromptBloc(this._promptManager) { 25 | _getPromptHistory(); 26 | 27 | _promptUpdateSubject 28 | .listen(_updatePrompt); 29 | 30 | _promptInsertSubject 31 | .listen(_insertPrompt); 32 | 33 | _fetchPromptSubject 34 | .listen((_) => _fetchPrompt()); 35 | 36 | // fetches first prompt 37 | fetchPrompt.add(0); 38 | } 39 | 40 | void _updatePrompt(Prompt prompt) { 41 | _promptManager.updatePrompt(prompt) 42 | .listen((_) => _); 43 | } 44 | 45 | void _insertPrompt(Prompt prompt) { 46 | _promptManager.insertPrompt(prompt) 47 | .flatMap((_) => _getPromptHistory()) 48 | .listen((_) => _); 49 | } 50 | 51 | Observable> _getPromptHistory() { 52 | return _promptManager.getListOfPrompts() 53 | .map((prompts) => prompts.reversed.toList()) 54 | .map(_addHistoryToSubject); 55 | } 56 | 57 | List _addHistoryToSubject(List prompts) { 58 | _promptHistorySubject.add(prompts); 59 | return prompts; 60 | } 61 | 62 | void _fetchPrompt() { 63 | _promptManager.getPrompt() 64 | .map((prompt) { 65 | _promptSubject.add(prompt); 66 | return prompt; 67 | }) 68 | .listen(_promptInsertSubject.add); 69 | } 70 | } -------------------------------------------------------------------------------- /lib/domain/managers/prompt_manager.dart: -------------------------------------------------------------------------------- 1 | import 'package:rxdart/rxdart.dart'; 2 | import 'package:writing_prompt/data/local/database/prompt_provider.dart'; 3 | import 'package:writing_prompt/data/remote/api/prompt_api.dart'; 4 | import 'package:writing_prompt/domain/mapper/prompt_mappers.dart'; 5 | import 'package:writing_prompt/domain/models/prompt.dart'; 6 | 7 | class PromptManager { 8 | PromptApi _api; 9 | PromptRemoteMapper _remoteMapper; 10 | PromptLocalInverseMapper _localInverseMapper; 11 | PromptLocalMapper _localMapper; 12 | DBHelper _dbHelper; 13 | 14 | PromptManager(this._api, this._remoteMapper, this._localInverseMapper, this._localMapper, this._dbHelper); 15 | 16 | Observable getPrompt() => 17 | Observable.fromFuture(_api.fetchPrompt()) 18 | .map((article) => _remoteMapper.map(article)); 19 | 20 | Observable insertPrompt(Prompt prompt) => 21 | Observable.fromFuture( 22 | _dbHelper.insert(_localInverseMapper.map(prompt)) 23 | ); 24 | 25 | Observable> getListOfPrompts() => 26 | Observable.fromFuture(_dbHelper.getPrompts()) 27 | .map((prompts) => _localMapper.mapList(prompts)); 28 | 29 | Observable updatePrompt(Prompt prompt) { 30 | var local = _localInverseMapper.map(prompt); 31 | return Observable.fromFuture( 32 | _dbHelper.update(local) 33 | ); 34 | } 35 | } -------------------------------------------------------------------------------- /lib/domain/mapper/prompt_mappers.dart: -------------------------------------------------------------------------------- 1 | import 'package:writing_prompt/common/mapper.dart'; 2 | import 'package:writing_prompt/data/local/model/prompt_local.dart'; 3 | import 'package:writing_prompt/data/remote/model/prompt_remote.dart'; 4 | import 'package:writing_prompt/domain/models/prompt.dart'; 5 | 6 | class PromptRemoteMapper extends Mapper { 7 | @override 8 | Prompt map(PromptRemote value) { 9 | if (value == null) { 10 | return null; 11 | } 12 | return new Prompt(value.english, value.count, false); 13 | } 14 | } 15 | 16 | class PromptLocalInverseMapper extends Mapper { 17 | @override 18 | PromptLocal map(Prompt value) { 19 | if (value == null) { 20 | return null; 21 | } 22 | return new PromptLocal(value.count, value.prompt, value.done); 23 | } 24 | } 25 | 26 | class PromptLocalMapper extends Mapper { 27 | @override 28 | Prompt map(PromptLocal value) { 29 | if (value == null) { 30 | return null; 31 | } 32 | return new Prompt(value.prompt, value.id, value.done); 33 | } 34 | } 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /lib/domain/models/prompt.dart: -------------------------------------------------------------------------------- 1 | class Prompt { 2 | String prompt; 3 | int count; 4 | bool done; 5 | 6 | Prompt(this.prompt, this.count, this.done); 7 | } -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:writing_prompt/data/local/database/prompt_provider.dart'; 3 | import 'package:writing_prompt/data/remote/api/prompt_api.dart'; 4 | import 'package:writing_prompt/domain/bloc/prompt_bloc.dart'; 5 | import 'package:writing_prompt/domain/managers/prompt_manager.dart'; 6 | import 'package:writing_prompt/domain/mapper/prompt_mappers.dart'; 7 | import 'package:writing_prompt/presentation/styles/colors.dart'; 8 | import 'package:writing_prompt/presentation/styles/strings.dart'; 9 | import 'package:writing_prompt/presentation/ui/home.dart'; 10 | import 'package:writing_prompt/presentation/ui/single_prompt.dart'; 11 | 12 | void main() { 13 | // poor-man's injection 14 | var _remoteMapper = PromptRemoteMapper(); 15 | var _localInverseMapper = PromptLocalInverseMapper(); 16 | var _localMapper = PromptLocalMapper(); 17 | var _dbHelper = DBHelper(); 18 | var _api = PromptApi(); 19 | var _manager = PromptManager(_api, _remoteMapper, _localInverseMapper, _localMapper, _dbHelper); 20 | var _bloc = PromptBloc(_manager); 21 | runApp(WritingPromptApp(bloc: _bloc)); 22 | } 23 | 24 | class WritingPromptApp extends StatelessWidget { 25 | final PromptBloc bloc; 26 | 27 | WritingPromptApp({ 28 | Key key, 29 | this.bloc 30 | }) : super(key: key); 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return MaterialApp( 35 | title: appName, 36 | theme: ThemeData( 37 | scaffoldBackgroundColor: titleBarBackground, 38 | canvasColor: bottomBarBackground, 39 | primaryColor: titleBarBackground, 40 | textTheme: Theme.of(context).textTheme.copyWith( 41 | caption: TextStyle(color: Colors.white54), 42 | subhead: TextStyle(fontFamily: 'Garamond', fontSize: 10.0)) 43 | ), 44 | home: HomePage(bloc: bloc), 45 | ); 46 | } 47 | } -------------------------------------------------------------------------------- /lib/presentation/styles/colors.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | const Color promptTextColor = Colors.grey; 4 | const Color titleBarTextColor = Colors.black; 5 | const Color titleBarBackground = Colors.white; 6 | const Color bottomBarBackground = Colors.black; 7 | -------------------------------------------------------------------------------- /lib/presentation/styles/dimensions.dart: -------------------------------------------------------------------------------- 1 | const double promptTextSize = 25; 2 | const double screenPadding = 20; 3 | const double listPadding = 8; 4 | const double refreshButtonSize = 40; -------------------------------------------------------------------------------- /lib/presentation/styles/strings.dart: -------------------------------------------------------------------------------- 1 | const navigationSinglePrompt = "Home"; 2 | const navigationPromptList = "List"; 3 | const appName = "Writing Prompt"; 4 | const emptyPrompt = "Fetching a Prompt..."; 5 | const emptyHistory = "No Entries"; 6 | 7 | const key_bottom_bar = "key_bottom_bar"; 8 | const key_prompt_text = "key_prompt_text"; 9 | const key_history_list = "key_history_list"; 10 | const key_empty_history_text = "key_empty_history_text"; -------------------------------------------------------------------------------- /lib/presentation/styles/text_styles.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:writing_prompt/presentation/styles/colors.dart'; 3 | import 'package:writing_prompt/presentation/styles/dimensions.dart'; 4 | 5 | TextStyle promptTextStyle() => 6 | TextStyle( 7 | color: promptTextColor, 8 | fontSize: promptTextSize, 9 | fontStyle: FontStyle.italic, 10 | ); 11 | 12 | TextStyle titleBarTextStyle() => 13 | TextStyle( 14 | color: titleBarTextColor, 15 | ); 16 | 17 | -------------------------------------------------------------------------------- /lib/presentation/ui/home.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:writing_prompt/domain/bloc/prompt_bloc.dart'; 3 | import 'package:writing_prompt/presentation/styles/strings.dart'; 4 | import 'package:writing_prompt/presentation/ui/prompt_list.dart'; 5 | import 'package:writing_prompt/presentation/ui/single_prompt.dart'; 6 | 7 | class HomePage extends StatefulWidget { 8 | final PromptBloc bloc; 9 | 10 | HomePage({Key key, this.bloc}) : super(key: key); 11 | 12 | @override 13 | State createState() { 14 | return _HomePageState(); 15 | } 16 | } 17 | 18 | class _HomePageState extends State { 19 | int _currentIndex = 0; 20 | List _children = []; 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | if (_children.isEmpty) { 25 | buildChildren(context); 26 | } 27 | return Scaffold( 28 | body: _children[_currentIndex], 29 | bottomNavigationBar: BottomNavigationBar( 30 | key: Key(key_bottom_bar), 31 | onTap: onTabTapped, 32 | currentIndex: _currentIndex, 33 | items: [ 34 | BottomNavigationBarItem( 35 | icon: new Icon(Icons.home), 36 | title: new Text(navigationSinglePrompt), 37 | ), 38 | BottomNavigationBarItem( 39 | icon: new Icon(Icons.list), 40 | title: new Text(navigationPromptList), 41 | ) 42 | ], 43 | ), 44 | ); 45 | } 46 | 47 | void onTabTapped(int index) { 48 | setState(() { 49 | _currentIndex = index; 50 | }); 51 | } 52 | 53 | void buildChildren(BuildContext context) { 54 | _children = [ 55 | SinglePromptPage(bloc: widget.bloc, title: appName), 56 | PromptListPage(bloc: widget.bloc, title: appName), 57 | ]; 58 | } 59 | } -------------------------------------------------------------------------------- /lib/presentation/ui/prompt_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:writing_prompt/domain/bloc/prompt_bloc.dart'; 3 | import 'package:writing_prompt/domain/models/prompt.dart'; 4 | import 'package:writing_prompt/presentation/styles/colors.dart'; 5 | import 'package:writing_prompt/presentation/styles/dimensions.dart'; 6 | import 'package:writing_prompt/presentation/styles/strings.dart'; 7 | import 'package:writing_prompt/presentation/styles/text_styles.dart'; 8 | 9 | class PromptListPage extends StatefulWidget { 10 | final PromptBloc bloc; 11 | 12 | PromptListPage({Key key, this.title, this.bloc}) : super(key: key); 13 | final String title; 14 | 15 | @override 16 | _PromptListPageState createState() => _PromptListPageState(); 17 | } 18 | 19 | class _PromptListPageState extends State { 20 | @override 21 | Widget build(BuildContext context) { 22 | return Scaffold( 23 | appBar: AppBar( 24 | title: Text(widget.title, style: titleBarTextStyle(),), 25 | elevation: 0.0, 26 | ), 27 | body: Center( 28 | child: Padding( 29 | padding: const EdgeInsets.only(top: screenPadding, left: screenPadding, right: screenPadding), 30 | child: StreamBuilder>( 31 | stream: widget.bloc.promptHistory, 32 | builder: (context, snapshot) => 33 | snapshot.data == null ? 34 | Text( 35 | emptyHistory, 36 | key : Key(key_empty_history_text) 37 | ) : 38 | ListView( 39 | key: Key(key_history_list), 40 | children: snapshot.data.map(_buildItem).toList(), 41 | ), 42 | ), 43 | ), 44 | ), 45 | backgroundColor: titleBarBackground, 46 | // This trailing comma makes auto-formatting nicer for build methods. 47 | ); 48 | } 49 | 50 | Widget _buildItem(Prompt prompt) { 51 | return Container( 52 | child: Padding( 53 | padding: const EdgeInsets.only(left: screenPadding, right: screenPadding, bottom: listPadding, top: listPadding), 54 | child: Row( 55 | children: [ 56 | Expanded( 57 | child: Text(prompt.prompt) 58 | ), 59 | Checkbox( 60 | value: prompt.done == null ? false : prompt.done, 61 | onChanged: (bool newValue) { 62 | setState(() { 63 | prompt.done = newValue; 64 | widget.bloc.promptUpdate.add(prompt); 65 | }); 66 | } 67 | ) 68 | ], 69 | ), 70 | ), 71 | decoration: BoxDecoration( 72 | border: new Border( 73 | bottom: new BorderSide() 74 | ) 75 | ), 76 | ); 77 | } 78 | } -------------------------------------------------------------------------------- /lib/presentation/ui/single_prompt.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:writing_prompt/domain/bloc/prompt_bloc.dart'; 3 | import 'package:writing_prompt/domain/models/prompt.dart'; 4 | import 'package:writing_prompt/presentation/styles/colors.dart'; 5 | import 'package:writing_prompt/presentation/styles/dimensions.dart'; 6 | import 'package:writing_prompt/presentation/styles/strings.dart'; 7 | import 'package:writing_prompt/presentation/styles/text_styles.dart'; 8 | import 'package:writing_prompt/presentation/utils/refresh_button.dart'; 9 | 10 | class SinglePromptPage extends StatefulWidget { 11 | final PromptBloc bloc; 12 | 13 | SinglePromptPage({Key key, this.title, this.bloc}) : super(key: key); 14 | final String title; 15 | 16 | @override 17 | _SinglePromptPageState createState() => _SinglePromptPageState(); 18 | } 19 | 20 | class _SinglePromptPageState extends State { 21 | @override 22 | Widget build(BuildContext context) { 23 | return Scaffold( 24 | appBar: AppBar( 25 | title: Text(widget.title, style: titleBarTextStyle(),), 26 | elevation: 0.0, 27 | ), 28 | body: Padding( 29 | padding: const EdgeInsets.all(screenPadding), 30 | child: Column( 31 | children: [ 32 | Expanded( 33 | child: Container( 34 | child: StreamBuilder( 35 | stream: widget.bloc.prompt, 36 | builder: (context, snapshot) => 37 | Text( 38 | snapshot.data == null ? emptyPrompt : snapshot.data.prompt, 39 | style: promptTextStyle(), 40 | textAlign: TextAlign.center, 41 | key: Key(key_prompt_text), 42 | ), 43 | ), 44 | alignment: Alignment(0, 1), 45 | ), 46 | ), 47 | Expanded( 48 | child: Container( 49 | child: RefreshPrompt(widget.bloc), 50 | alignment: Alignment(0, -1), 51 | ), 52 | ), 53 | ], 54 | ), 55 | ), 56 | backgroundColor: titleBarBackground, 57 | // This trailing comma makes auto-formatting nicer for build methods. 58 | ); 59 | } 60 | } -------------------------------------------------------------------------------- /lib/presentation/utils/refresh_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:writing_prompt/domain/bloc/prompt_bloc.dart'; 3 | import 'package:writing_prompt/presentation/styles/dimensions.dart'; 4 | 5 | class RefreshPrompt extends StatefulWidget { 6 | PromptBloc bloc; 7 | 8 | RefreshPrompt(this.bloc); 9 | 10 | @override 11 | _RefreshPromptState createState() => new _RefreshPromptState(); 12 | } 13 | 14 | class _RefreshPromptState extends State 15 | with SingleTickerProviderStateMixin { 16 | AnimationController _animationController; 17 | 18 | @override 19 | void initState() { 20 | super.initState(); 21 | _animationController = new AnimationController( 22 | vsync: this, 23 | duration: const Duration(milliseconds: 500), 24 | ); 25 | _animationController.addStatusListener((state) { 26 | if (state == AnimationStatus.completed) { 27 | _animationController.reset(); 28 | } 29 | }); 30 | } 31 | 32 | @override 33 | void dispose() { 34 | _animationController.dispose(); 35 | super.dispose(); 36 | } 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | return new RotationTransition( 41 | turns: _animationController, 42 | child: IconButton( 43 | iconSize: refreshButtonSize, 44 | icon: Icon(Icons.refresh), 45 | onPressed: (() { 46 | widget.bloc.fetchPrompt.add(0); 47 | if (!_animationController.isAnimating) { 48 | _animationController.forward(); 49 | } 50 | })) 51 | ); 52 | } 53 | } -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: writing_prompt 2 | description: A writing prompt application 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # Read more about versioning at semver.org. 10 | version: 1.0.0+1 11 | 12 | environment: 13 | sdk: ">=2.0.0-dev.68.0 <3.0.0" 14 | 15 | dependencies: 16 | flutter: 17 | sdk: flutter 18 | 19 | built_collection: '>=2.0.0 <4.0.0' 20 | built_value: ^6.1.3 21 | http: ^0.12.0 22 | rxdart: ^0.19.0 23 | cupertino_icons: ^0.1.2 24 | test: ^1.5.1+1 25 | mockito: ^4.0.0 26 | animated_text_kit: ^1.2.0 27 | sqflite: 0.13.0 28 | 29 | dev_dependencies: 30 | build_runner: ^1.0.0 31 | built_value_generator: 32 | git: 33 | url: https://github.com/efortuna/built_value.dart.git/ 34 | path: built_value_generator 35 | 36 | flutter_test: 37 | sdk: flutter 38 | 39 | flutter_driver: 40 | sdk: flutter 41 | 42 | 43 | 44 | # For information on the generic Dart part of this file, see the 45 | # following page: https://www.dartlang.org/tools/pub/pubspec 46 | 47 | # The following section is specific to Flutter. 48 | flutter: 49 | 50 | # The following line ensures that the Material Icons font is 51 | # included with your application, so that you can use the icons in 52 | # the material Icons class. 53 | uses-material-design: true 54 | 55 | # To add assets to your application, add an assets section, like this: 56 | # assets: 57 | # - images/a_dot_burr.jpeg 58 | # - images/a_dot_ham.jpeg 59 | 60 | # An image asset can refer to one or more resolution-specific "variants", see 61 | # https://flutter.io/assets-and-images/#resolution-aware. 62 | 63 | # For details regarding adding assets from package dependencies, see 64 | # https://flutter.io/assets-and-images/#from-packages 65 | 66 | # To add custom fonts to your application, add a fonts section here, 67 | # in this "flutter" section. Each entry in this list should have a 68 | # "family" key with the font family name, and a "fonts" key with a 69 | # list giving the asset and other descriptors for the font. For 70 | # example: 71 | # fonts: 72 | # - family: Schyler 73 | # fonts: 74 | # - asset: fonts/Schyler-Regular.ttf 75 | # - asset: fonts/Schyler-Italic.ttf 76 | # style: italic 77 | # - family: Trajan Pro 78 | # fonts: 79 | # - asset: fonts/TrajanPro.ttf 80 | # - asset: fonts/TrajanPro_Bold.ttf 81 | # weight: 700 82 | # 83 | # For details regarding fonts from package dependencies, 84 | # see https://flutter.io/custom-fonts/#from-packages 85 | -------------------------------------------------------------------------------- /test/api_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | import 'dart:async'; 3 | import 'package:writing_prompt/data/remote/api/prompt_api.dart'; 4 | import 'package:writing_prompt/data/remote/model/prompt_remote.dart'; 5 | import 'package:writing_prompt/domain/managers/prompt_manager.dart'; 6 | import 'package:writing_prompt/domain/mapper/prompt_mappers.dart'; 7 | import 'package:mockito/mockito.dart'; 8 | import 'package:writing_prompt/domain/models/prompt.dart'; 9 | 10 | 11 | import 'mocks.dart'; 12 | 13 | void main() { 14 | test("parse a simple Prompt object", () { 15 | const name = "name"; 16 | const count = 10; 17 | const jsonString = 18 | '{"english": "$name", "count": $count}'; 19 | 20 | expect(fromJson(jsonString).english, name); 21 | expect(fromJson(jsonString).count, count); 22 | }); 23 | 24 | test("network testing", () async { 25 | PromptApi api = PromptApi(); 26 | PromptRemote prompt = await api.fetchPrompt(); 27 | expect(prompt.english, isNotNull); 28 | expect(prompt.count, isNotNull); 29 | }); 30 | 31 | test("testing mapper", () { 32 | const name = "name"; 33 | const count = 10; 34 | var mapper = PromptRemoteMapper(); 35 | var promptRemote = MockPromptRemote(); 36 | when(promptRemote.count).thenReturn(count); 37 | when(promptRemote.english).thenReturn(name); 38 | 39 | Prompt prompt = mapper.map(promptRemote); 40 | expect(prompt.count, count); 41 | expect(prompt.prompt, name); 42 | }); 43 | 44 | test("manager test", () async { 45 | const name = "name"; 46 | const count = 10; 47 | var remoteMapper = MockRemoteMapper(); 48 | var api = MockApi(); 49 | var localMapper = MockLocalMapper(); 50 | var localInverseMapper = MockLocalInverseMapper(); 51 | var db = MockDbHelper(); 52 | 53 | var promptRemote = MockPromptRemote(); 54 | when(promptRemote.count).thenReturn(count); 55 | when(promptRemote.english).thenReturn(name); 56 | 57 | when(api.fetchPrompt()).thenAnswer((_) => Future(() => promptRemote)); 58 | var prompt = Prompt(name, count, false); 59 | when(remoteMapper.map(any)).thenReturn(prompt); 60 | 61 | var manager = PromptManager(api, remoteMapper, localInverseMapper, localMapper, db); 62 | 63 | manager.getPrompt().listen( 64 | expectAsync1((value) { 65 | expect(value.prompt, prompt.prompt); 66 | }, count: 1)); 67 | }); 68 | } -------------------------------------------------------------------------------- /test/mocks.dart: -------------------------------------------------------------------------------- 1 | import 'package:writing_prompt/data/local/database/prompt_provider.dart'; 2 | import 'package:writing_prompt/data/remote/api/prompt_api.dart'; 3 | import 'package:writing_prompt/data/remote/model/prompt_remote.dart'; 4 | import 'package:mockito/mockito.dart'; 5 | import 'package:writing_prompt/domain/bloc/prompt_bloc.dart'; 6 | import 'package:writing_prompt/domain/mapper/prompt_mappers.dart'; 7 | 8 | class MockPromptRemote extends Mock implements PromptRemote {} 9 | class MockRemoteMapper extends Mock implements PromptRemoteMapper {} 10 | class MockLocalMapper extends Mock implements PromptLocalMapper {} 11 | class MockLocalInverseMapper extends Mock implements PromptLocalInverseMapper {} 12 | class MockDbHelper extends Mock implements DBHelper {} 13 | class MockApi extends Mock implements PromptApi {} 14 | class MockBloc extends Mock implements PromptBloc {} -------------------------------------------------------------------------------- /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 | import 'package:writing_prompt/domain/models/prompt.dart'; 11 | 12 | import 'package:writing_prompt/main.dart'; 13 | import 'package:writing_prompt/presentation/styles/strings.dart'; 14 | import 'package:mockito/mockito.dart'; 15 | 16 | 17 | import 'mocks.dart'; 18 | 19 | void main() { 20 | const bottomBarKey = Key(key_bottom_bar); 21 | 22 | testWidgets('When we open app, home is selected', (WidgetTester tester) async { 23 | var bloc = MockBloc(); 24 | // Build our app and trigger a frame. 25 | await tester.pumpWidget(WritingPromptApp(bloc: bloc)); 26 | 27 | expect(find.byKey(Key(key_bottom_bar)), findsOneWidget); 28 | expect(find.byIcon(Icons.home), findsOneWidget); 29 | expect(find.byIcon(Icons.list), findsOneWidget); 30 | // current index is 0 31 | BottomNavigationBar bar = tester.firstWidget(find.byKey(bottomBarKey)); 32 | expect(bar.currentIndex, 0); 33 | }); 34 | 35 | 36 | testWidgets('When we click on lists, the index changes and the new page appears', (WidgetTester tester) async { 37 | var bloc = MockBloc(); 38 | // Build our app and trigger a frame. 39 | await tester.pumpWidget(WritingPromptApp(bloc: bloc)); 40 | 41 | await tester.tap( 42 | find.byIcon(Icons.list) 43 | ); 44 | await tester.pump(); 45 | // current index is 1 46 | BottomNavigationBar bar = tester.firstWidget(find.byKey(Key(key_bottom_bar))); 47 | expect(bar.currentIndex, 1); 48 | }); 49 | 50 | testWidgets('Correct text appears on widget', (WidgetTester tester) async { 51 | var bloc = MockBloc(); 52 | const text = "String test"; 53 | List list = List(); 54 | list.add(Prompt(text, 0, false)); 55 | when(bloc.prompt).thenAnswer((_) => Stream.fromIterable(list)); 56 | // Build our app and trigger a frame. 57 | await tester.pumpWidget(WritingPromptApp(bloc: bloc)); 58 | await tester.pump(); 59 | 60 | Text textWidget = tester.firstWidget(find.byKey(Key(key_prompt_text))); 61 | expect(textWidget.data, text); 62 | }); 63 | 64 | testWidgets('When we cannot fetch a prompt, display empty message', (WidgetTester tester) async { 65 | var bloc = MockBloc(); 66 | // Build our app and trigger a frame. 67 | await tester.pumpWidget(WritingPromptApp(bloc: bloc)); 68 | await tester.pump(); 69 | 70 | Text textWidget = tester.firstWidget(find.byKey(Key(key_prompt_text))); 71 | expect(textWidget.data, emptyPrompt); 72 | }); 73 | 74 | testWidgets('When no history, display empty message', (WidgetTester tester) async { 75 | var bloc = MockBloc(); 76 | // Build our app and trigger a frame. 77 | await tester.pumpWidget(WritingPromptApp(bloc: bloc)); 78 | 79 | await tester.tap( 80 | find.byIcon(Icons.list) 81 | ); 82 | await tester.pump(); 83 | 84 | Text text = tester.firstWidget(find.byKey(Key(key_empty_history_text))); 85 | expect(text.data, emptyHistory); 86 | }); 87 | 88 | testWidgets('Correct number of items on history', (WidgetTester tester) async { 89 | var bloc = MockBloc(); 90 | const text = "String test"; 91 | List> promptLists = List>(); 92 | List list = List(); 93 | list.add(Prompt(text, 0, false)); 94 | promptLists.add(list); 95 | when(bloc.promptHistory).thenAnswer((_) => Stream.fromIterable(promptLists)); 96 | // Build our app and trigger a frame. 97 | await tester.pumpWidget(WritingPromptApp(bloc: bloc)); 98 | await tester.pump(); 99 | 100 | await tester.tap( 101 | find.byIcon(Icons.list) 102 | ); 103 | 104 | await tester.pump(); 105 | // second pump is to guarantee that the stream is sent 106 | await tester.pump(); 107 | 108 | ListView listView = tester.firstWidget(find.byKey(Key(key_history_list))); 109 | expect(listView.childrenDelegate.estimatedChildCount, 1); 110 | }); 111 | } 112 | -------------------------------------------------------------------------------- /test_driver/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_driver/driver_extension.dart'; 2 | import 'package:writing_prompt/main.dart' as app; 3 | 4 | 5 | 6 | void main() { 7 | // This line enables the extension 8 | enableFlutterDriverExtension(); 9 | app.main(); 10 | } 11 | -------------------------------------------------------------------------------- /test_driver/app_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | // Imports the Flutter Driver API 4 | import 'package:flutter_driver/flutter_driver.dart'; 5 | import 'package:test/test.dart'; 6 | import 'package:writing_prompt/presentation/styles/strings.dart'; 7 | 8 | void main() { 9 | FlutterDriver driver; 10 | 11 | SerializableFinder promptWidget = find.byValueKey(key_prompt_text); 12 | 13 | setUpAll(() async { 14 | // Connects to the app 15 | driver = await FlutterDriver.connect(); 16 | }); 17 | 18 | tearDownAll(() async { 19 | if (driver != null) { 20 | // Closes the connection 21 | driver.close(); 22 | } 23 | }); 24 | 25 | test('At the beginning we have the empty text string', () async { 26 | String widgetText = await driver.getText(promptWidget); 27 | expect(widgetText, emptyPrompt); 28 | }); 29 | 30 | test('After waiting for a time, we get a new message', () async { 31 | await Future.delayed(Duration(seconds: 10)); 32 | String widgetText = await driver.getText(promptWidget); 33 | expect(widgetText, isNot(emptyPrompt)); 34 | }); 35 | 36 | } --------------------------------------------------------------------------------