diff --git a/wire/.gitignore b/wire/.gitignore new file mode 100644 index 00000000..2ddde2a5 --- /dev/null +++ b/wire/.gitignore @@ -0,0 +1,73 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.packages +.pub-cache/ +.pub/ +/build/ + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/wire/.metadata b/wire/.metadata new file mode 100644 index 00000000..1b5cec02 --- /dev/null +++ b/wire/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 27321ebbad34b0a3fafe99fac037102196d655ff + channel: stable + +project_type: app diff --git a/wire/README.md b/wire/README.md new file mode 100644 index 00000000..e561deae --- /dev/null +++ b/wire/README.md @@ -0,0 +1,197 @@ +# wire_flutter_todo example + +The wire_flutter_todo example uses only the core Widgets and Classes that Flutter provides out of the box to manage state. The most important of these: the `StatefulWidget`. + +## Key Concepts + + * Share State by Lifting State Up - If two Widgets need access to the same state (aka data), "lift" the state up to a parent `StatefulWidget` that passes the state down to each child Widget that needs it. + * Updating State with callbacks - To update state, pass a callback function from the Parent `StatefulWidget` to the child widgets. + * Local persistence - The list of todos is serialized to JSON and stored as a file on disk whenever the State is updated. + * Testing - Pull Business logic out of Widgets into Plain Old Dart Object (PODOs). + +## Share State by Lifting State Up + +Let's start with a Simple Example. Say our app had only 1 Tab: The `List of Todos Tab`. + + ``` ++------------+ +| | +| List | +| of | +| Todos | +| Tab | +| | ++------------+ +``` + +Now, we add a sibling Widget: The `Stats Tab`! But wait, it needs access to the List of Todos so it can calculate how many of them are active and how many are complete. So how do we share that data? + +It can be difficult for siblings to pass their state to each other. For example, say both Widgets were displayed side-by-side at the same time: How would Flutter know when to re-build the `Stats Tab` to reflect the latest count when the List of Todos changes? + +``` ++-------------+ +-------------+ +| | | | +| | Gimme dem Todos | | +| List of | <-------------- + Stats | +| Todos | | Tab | +| Tab | No. Mine. | | +| + --------------> | | +| | | | ++-------------+ +-------------+ +``` + +So how do we share state between these two sibling Widgets? Let's Lift the state up to a Parent Widget and pass it down to each child that needs it! + +``` + +-------------------------+ + | Keeper of the Todos | + | (StatefulWidget) | + +-----+--------------+----+ + | | ++----------|--+ +---|---------+ +| v | | v | +| List of | | Stats | +| Todos | | Tab | +| | | | ++-------------+ +-------------+ +``` + +Now, when we change the List of Todos in the `Keeper of the Todos` widget, both children will reflect the updated State! This concept scales to an entire app. Any time you need to share State between Widgets or Routes, lift it up to a common parent Widget. Here's a diagram of what our app state actually looks like! + +``` + +------------------------------------------+ + | | + | VanillaApp (StatefulWidget) | + | | + | Manages List and "isLoading" | + | | + +---------+---------------------------+----+ + | | ++------------|---------------+ +--------|--------+ +| v | | v | +| Main Tabs Screen | | Add Todo Screen | +| (Stateful) | | (Stateless) | +| | | | +| Manages current tab and | | | +| Visibility filter | | | +| | | | +| +----------+ +----------+ | | | +| | | | | | | | +| | List | | | | | | +| | of | | Stats | | | | +| | Todos | | Tab | | | | +| | Tab | | | | | | +| +----+-----+ +----------+ | | | +| | | | | ++------|---------------------+ +-----------------+ + | ++------|---------+ +| v | +| | +| Todo Details | +| Screen | +| | ++------+---------+ + | ++------|---------+ +| v | +| | +| Edit Todo | +| Screen | +| | ++----------------+ +``` + +Careful Observers might note: We don't lift *all* state up to the parent in this pattern. Only the State that's shared! The Main Tabs Screen can handle which tab is currently active on its own, for example, because this state isn't relevant to other Widgets! + +## Updating State with callbacks + +Ok, so now we have an idea of how to pass data down from parent to child, but how do we update the list of Todos from these different screens / Routes / Widgets? + +We'll also **pass callback functions** from the parent to the children as well. These callback functions are responsible for updating the State at the Parent Widget and calling `setState` so Flutter knows it needs to rebuild! + +In this app, we have a few callbacks we will pass down: + + 1. AddTodo callback + 2. RemoveTodo Callback + 3. UpdateTodo Callback + 4. MarkAllComplete callback + 5. ClearComplete callback + +In this app, we pass the `AddTodo` callback from our `VanillaApp` Widget to the `Add Todo Screen`. Now all our `Add Todo Screen` needs to do is call the `AddTodo` callback with a new `Todo` when a user finishes filling in the form. This will send the `Todo` up to the `VanillaApp` so it can handle how it adds the new todo to the list! + +This demonstrates a core concept of State management in Flutter: Pass data and callback functions down from a parent to a child, and use invoke those callbacks in the Child to send data back up to the parent. + +To make this concrete, Let's see how our callbacks flow in the this app. + +``` + +----------------------------------------------------+ + | | + | VanillaApp (StatefulWidget) | + | | + | Manages List and "isLoading" | + | ^ | + +---------+----------------------+------------|------+ + | | | + | UpdateTodo | AddTodo | Invoke AddTodo + | RemoveTodo | | Callback, sending + | MarkAllComplete | | Data up to the + | ClearComplete | | parent. + | | | ++------------|---------------+ +---|------------+-+ +| v | | v | +| Main Tabs Screen | | Add Todo Screen | +| (Stateful) | | (Stateless) | +| | | | +| Manages current tab and | | | +| Visibility filter | | | +| | | | +| +----------+ +----------+ | | | +| | | | | | | | +| | List | | | | | | +| | of | | Stats | | | | +| | Todos | | Tab | | | | +| | Tab | | | | | | +| +----+-----+ +----------+ | | | +| | | | | ++------|---------------------+ +------------------+ + | + | UpdateTodo + | RemoveTodo + | ++------|---------+ +| v | +| | +| Todo Details | +| Screen | +| | ++------+---------+ + | + | UpdateTodo + | ++------|---------+ +| v | +| | +| Edit Todo | +| Screen | +| | ++----------------+ +``` + +## Testing + +There are a few Strategies for testing: + + 1. Store state and the state mutations (methods) within a `StatefulWidget` + 2. Extract this State and logic out into a "Plain Old Dart Objects" (PODOs) and test those. The `StatefulWidget` can then delegate to this object. + +So which should you choose? For View-related State: Option #1. For App State / Business Logic: Option #2. + +While Option #1 works because Flutter provides a nice set of Widget testing utilities out of the box, Option #2 will generally prove easier to test because it has no Flutter dependencies and does not require you to test against a Widget tree, but simply against an Object. + +## Addendum + +Since Flutter is quite similar to React with regards to State management, many of the resources on the React site are pertinent when thinking about State in flutter. In fact, the ideas in this example were lifted directly from the React site: + + * [Lifting State Up in React](https://reactjs.org/docs/lifting-state-up.html) + * [Thinking in React](https://reactjs.org/docs/thinking-in-react.html) diff --git a/wire/android/.gitignore b/wire/android/.gitignore new file mode 100644 index 00000000..bc2100d8 --- /dev/null +++ b/wire/android/.gitignore @@ -0,0 +1,7 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java diff --git a/wire/android/app/build.gradle b/wire/android/app/build.gradle new file mode 100644 index 00000000..80a76d63 --- /dev/null +++ b/wire/android/app/build.gradle @@ -0,0 +1,67 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 28 + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + lintOptions { + disable 'InvalidPackage' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.wire_flutter_todo" + minSdkVersion 16 + targetSdkVersion 28 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test:runner:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' +} diff --git a/wire/android/app/src/debug/AndroidManifest.xml b/wire/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 00000000..b38a056a --- /dev/null +++ b/wire/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/wire/android/app/src/main/AndroidManifest.xml b/wire/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..f8468cd9 --- /dev/null +++ b/wire/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + diff --git a/wire/android/app/src/main/kotlin/com/example/vanilla/MainActivity.kt b/wire/android/app/src/main/kotlin/com/example/vanilla/MainActivity.kt new file mode 100644 index 00000000..a2fe75d6 --- /dev/null +++ b/wire/android/app/src/main/kotlin/com/example/vanilla/MainActivity.kt @@ -0,0 +1,12 @@ +package com.example.wire_flutter_todo + +import androidx.annotation.NonNull; +import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugins.GeneratedPluginRegistrant + +class MainActivity: FlutterActivity() { + override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { + GeneratedPluginRegistrant.registerWith(flutterEngine); + } +} diff --git a/wire/android/app/src/main/kotlin/com/example/wire/MainActivity.kt b/wire/android/app/src/main/kotlin/com/example/wire/MainActivity.kt new file mode 100644 index 00000000..eb7d1bff --- /dev/null +++ b/wire/android/app/src/main/kotlin/com/example/wire/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.wire + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/wire/android/app/src/main/res/drawable/launch_background.xml b/wire/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 00000000..304732f8 --- /dev/null +++ b/wire/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/wire/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/wire/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..a3f285f9 Binary files /dev/null and b/wire/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/wire/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/wire/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..5e6f3ac6 Binary files /dev/null and b/wire/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/wire/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/wire/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..144d60be Binary files /dev/null and b/wire/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/wire/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/wire/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..deafae2d Binary files /dev/null and b/wire/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/wire/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/wire/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..d5614ac8 Binary files /dev/null and b/wire/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/wire/android/app/src/main/res/values/styles.xml b/wire/android/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..00fa4417 --- /dev/null +++ b/wire/android/app/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + diff --git a/wire/android/app/src/profile/AndroidManifest.xml b/wire/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 00000000..b38a056a --- /dev/null +++ b/wire/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/wire/android/build.gradle b/wire/android/build.gradle new file mode 100644 index 00000000..3100ad2d --- /dev/null +++ b/wire/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.3.50' + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.5.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/wire/android/gradle.properties b/wire/android/gradle.properties new file mode 100644 index 00000000..38c8d454 --- /dev/null +++ b/wire/android/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx1536M +android.enableR8=true +android.useAndroidX=true +android.enableJetifier=true diff --git a/wire/android/gradle/wrapper/gradle-wrapper.properties b/wire/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..296b146b --- /dev/null +++ b/wire/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip diff --git a/wire/android/settings.gradle b/wire/android/settings.gradle new file mode 100644 index 00000000..5a2f14fb --- /dev/null +++ b/wire/android/settings.gradle @@ -0,0 +1,15 @@ +include ':app' + +def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() + +def plugins = new Properties() +def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') +if (pluginsFile.exists()) { + pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } +} + +plugins.each { name, path -> + def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() + include ":$name" + project(":$name").projectDir = pluginDirectory +} diff --git a/wire/ios/.gitignore b/wire/ios/.gitignore new file mode 100644 index 00000000..e96ef602 --- /dev/null +++ b/wire/ios/.gitignore @@ -0,0 +1,32 @@ +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/wire/ios/Flutter/AppFrameworkInfo.plist b/wire/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 00000000..6b4c0f78 --- /dev/null +++ b/wire/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 8.0 + + diff --git a/wire/ios/Flutter/Debug.xcconfig b/wire/ios/Flutter/Debug.xcconfig new file mode 100644 index 00000000..e8efba11 --- /dev/null +++ b/wire/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/wire/ios/Flutter/Release.xcconfig b/wire/ios/Flutter/Release.xcconfig new file mode 100644 index 00000000..399e9340 --- /dev/null +++ b/wire/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/wire/ios/Podfile b/wire/ios/Podfile new file mode 100644 index 00000000..b30a428b --- /dev/null +++ b/wire/ios/Podfile @@ -0,0 +1,90 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '9.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def parse_KV_file(file, separator='=') + file_abs_path = File.expand_path(file) + if !File.exists? file_abs_path + return []; + end + generated_key_values = {} + skip_line_start_symbols = ["#", "/"] + File.foreach(file_abs_path) do |line| + next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } + plugin = line.split(pattern=separator) + if plugin.length == 2 + podname = plugin[0].strip() + path = plugin[1].strip() + podpath = File.expand_path("#{path}", file_abs_path) + generated_key_values[podname] = podpath + else + puts "Invalid plugin specification: #{line}" + end + end + generated_key_values +end + +target 'Runner' do + use_frameworks! + use_modular_headers! + + # Flutter Pod + + copied_flutter_dir = File.join(__dir__, 'Flutter') + copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') + copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') + unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) + # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. + # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. + # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. + + generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') + unless File.exist?(generated_xcode_build_settings_path) + raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) + cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; + + unless File.exist?(copied_framework_path) + FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) + end + unless File.exist?(copied_podspec_path) + FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) + end + end + + # Keep pod path relative so it can be checked into Podfile.lock. + pod 'Flutter', :path => 'Flutter' + + # Plugin Pods + + # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock + # referring to absolute paths on developers' machines. + system('rm -rf .symlinks') + system('mkdir -p .symlinks/plugins') + plugin_pods = parse_KV_file('../.flutter-plugins') + plugin_pods.each do |name, path| + symlink = File.join('.symlinks', 'plugins', name) + File.symlink(path, symlink) + pod name, :path => File.join(symlink, 'ios') + end +end + +# Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. +install! 'cocoapods', :disable_input_output_paths => true + +post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['ENABLE_BITCODE'] = 'NO' + end + end +end diff --git a/wire/ios/Runner.xcodeproj/project.pbxproj b/wire/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..d6e3e13e --- /dev/null +++ b/wire/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,518 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; + 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; + 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, + 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, + 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B80C3931E831B6300D905FE /* App.framework */, + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEBA1CF902C7004384FC /* Flutter.framework */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 97C146F11CF9000F007C117D /* Supporting Files */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + 97C146F11CF9000F007C117D /* Supporting Files */ = { + isa = PBXGroup; + children = ( + ); + name = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1020; + ORGANIZATIONNAME = "The Chromium Authors"; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.wire_flutter_todo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.wire_flutter_todo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.wire_flutter_todo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/wire/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/wire/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..1d526a16 --- /dev/null +++ b/wire/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/wire/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/wire/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/wire/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/wire/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/wire/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/wire/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/wire/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/wire/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..a28140cf --- /dev/null +++ b/wire/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wire/ios/Runner.xcworkspace/contents.xcworkspacedata b/wire/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..1d526a16 --- /dev/null +++ b/wire/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/wire/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/wire/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/wire/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/wire/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/wire/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/wire/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/wire/ios/Runner/AppDelegate.swift b/wire/ios/Runner/AppDelegate.swift new file mode 100644 index 00000000..70693e4a --- /dev/null +++ b/wire/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..d22f10b2 --- /dev/null +++ b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,116 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 00000000..dc9ada47 Binary files /dev/null and b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 00000000..980e5ad6 Binary files /dev/null and b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 00000000..fd870289 Binary files /dev/null and b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 00000000..75e84cd1 Binary files /dev/null and b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 00000000..03ab8a84 Binary files /dev/null and b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 00000000..a03431cb Binary files /dev/null and b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 00000000..f47613ee Binary files /dev/null and b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 00000000..7f2230a9 Binary files /dev/null and b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 00000000..42315c6d Binary files /dev/null and b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 00000000..f9882cc0 Binary files /dev/null and b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@1x.png b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@1x.png new file mode 100644 index 00000000..8c552e23 Binary files /dev/null and b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@1x.png differ diff --git a/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 00000000..45537513 Binary files /dev/null and b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 00000000..6360ea17 Binary files /dev/null and b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 00000000..152d5e12 Binary files /dev/null and b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 00000000..310b0b8f Binary files /dev/null and b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@3x.png b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@3x.png new file mode 100644 index 00000000..40ac4ea7 Binary files /dev/null and b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@3x.png differ diff --git a/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@1x.png b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@1x.png new file mode 100644 index 00000000..dfc408df Binary files /dev/null and b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@1x.png differ diff --git a/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 00000000..092b7bfe Binary files /dev/null and b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@3x.png b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@3x.png new file mode 100644 index 00000000..521c3e2a Binary files /dev/null and b/wire/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@3x.png differ diff --git a/wire/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/wire/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 00000000..0bedcf2f --- /dev/null +++ b/wire/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/wire/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/wire/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/wire/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/wire/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/wire/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/wire/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/wire/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/wire/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/wire/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/wire/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/wire/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 00000000..89c2725b --- /dev/null +++ b/wire/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +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. \ No newline at end of file diff --git a/wire/ios/Runner/Base.lproj/LaunchScreen.storyboard b/wire/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..f2e259c7 --- /dev/null +++ b/wire/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wire/ios/Runner/Base.lproj/Main.storyboard b/wire/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 00000000..f3c28516 --- /dev/null +++ b/wire/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wire/ios/Runner/Info.plist b/wire/ios/Runner/Info.plist new file mode 100644 index 00000000..9a1b826d --- /dev/null +++ b/wire/ios/Runner/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + wire_flutter_todo + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/wire/ios/Runner/Runner-Bridging-Header.h b/wire/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 00000000..7335fdf9 --- /dev/null +++ b/wire/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" \ No newline at end of file diff --git a/wire/lib/app.dart b/wire/lib/app.dart new file mode 100644 index 00000000..8f020fb0 --- /dev/null +++ b/wire/lib/app.dart @@ -0,0 +1,27 @@ +// Copyright 2018 The Flutter Architecture Sample Authors. All rights reserved. +// Use of this source code is governed by the MIT license that can be found +// in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:todos_app_core/todos_app_core.dart'; +import 'package:wire_flutter_todo/localization.dart'; +import 'package:wire_flutter_todo/screens/add_edit_screen.dart'; +import 'package:wire_flutter_todo/screens/home_screen.dart'; + +class WireApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + onGenerateTitle: (context) => VanillaLocalizations.of(context).appTitle, + theme: ArchSampleTheme.theme, + localizationsDelegates: [ + ArchSampleLocalizationsDelegate(), + VanillaLocalizationsDelegate(), + ], + routes: { + ArchSampleRoutes.home: (context) => HomeScreen(), + ArchSampleRoutes.addTodo: (context) => AddEditScreen(), + }, + ); + } +} diff --git a/wire/lib/const/TodoApplicationState.dart b/wire/lib/const/TodoApplicationState.dart new file mode 100644 index 00000000..f0864f9e --- /dev/null +++ b/wire/lib/const/TodoApplicationState.dart @@ -0,0 +1,4 @@ +enum TodoApplicationState { + LOADING, + READY +} \ No newline at end of file diff --git a/wire/lib/const/TodoDataParams.dart b/wire/lib/const/TodoDataParams.dart new file mode 100644 index 00000000..12779d7b --- /dev/null +++ b/wire/lib/const/TodoDataParams.dart @@ -0,0 +1,9 @@ +class TodoDataParams { + static const String + LIST = 'DATA_TODO__ALL_LIST', + COUNT = 'DATA_TODO__NOT_COMPLETED_COUNT', + FILTER = 'DATA_TODO__FILTER', + STATE = 'DATA_TODO__STATE' + ; +} + diff --git a/wire/lib/const/TodoFilterValues.dart b/wire/lib/const/TodoFilterValues.dart new file mode 100644 index 00000000..3ee05ac4 --- /dev/null +++ b/wire/lib/const/TodoFilterValues.dart @@ -0,0 +1,5 @@ +enum TodoFilterValue { + ALL, + ACTIVE, + COMPLETED +} \ No newline at end of file diff --git a/wire/lib/const/TodoViewSignal.dart b/wire/lib/const/TodoViewSignal.dart new file mode 100644 index 00000000..06780343 --- /dev/null +++ b/wire/lib/const/TodoViewSignal.dart @@ -0,0 +1,14 @@ +class TodoViewSignal { + static const String + INPUT = 'SIGNAL__INPUT_TODO', + EDIT = 'SIGNAL__EDIT_TODO', + DELETE = 'SIGNAL__DELETE_TODO', + DISMISSED = 'SIGNAL__DISMISSED_TODO', + TOGGLE = 'SIGNAL__TOGGLE_TODO', + FILTER = 'SIGNAL__FILTER_TODO', + CLEAR_INPUT = 'SIGNAL__CLEAR_TODO', + CLEAR_COMPLETED = 'SIGNAL__CLEAR_COMPLETED', + COMPLETE_ALL = 'SIGNAL__COMPLETE_ALL' + ; +} + diff --git a/wire/lib/controller/RouteController.dart b/wire/lib/controller/RouteController.dart new file mode 100644 index 00000000..8cb2c307 --- /dev/null +++ b/wire/lib/controller/RouteController.dart @@ -0,0 +1,25 @@ +import 'dart:html'; + +import 'package:wire/wire.dart'; + +import '../const/TodoFilterValues.dart'; +import '../const/TodoViewSignal.dart'; + +class RouteController { + RouteController() { + window.onHashChange.listen((e) { + checkFilterRouterChanged(); + }); + checkFilterRouterChanged(); + } + + void checkFilterRouterChanged () { + var filter; + switch (window.location.hash) { + case '#/': filter = TodoFilterValue.ALL; break; + case '#/active': filter = TodoFilterValue.ACTIVE; break; + case '#/completed': filter = TodoFilterValue.COMPLETED; break; + } + if (filter != null) Wire.send(TodoViewSignal.FILTER, filter); + } +} \ No newline at end of file diff --git a/wire/lib/controller/TodoController.dart b/wire/lib/controller/TodoController.dart new file mode 100644 index 00000000..c5e3ea7d --- /dev/null +++ b/wire/lib/controller/TodoController.dart @@ -0,0 +1,87 @@ +import 'package:wire/wire.dart'; + +import '../const/TodoFilterValues.dart'; +import '../const/TodoViewSignal.dart'; +import '../data/dto/CreateDTO.dart'; +import '../model/TodoModel.dart'; +import '../data/dto/EditDTO.dart'; + +class TodoController { + TodoModel todoModel; + TodoController(this.todoModel) { + + /* + Wire.add(this, TodoViewSignal.INPUT, (data) {s + var text = data as String; + print('> TodoProcessor -> TodoViewOutputSignal.INPUT: ' + text); + if (text != null && text.isNotEmpty) { + todoModel.create(text); + Wire.send(TodoViewSignal.CLEAR); + } + }); + + Wire.add(this, TodoViewSignal.DELETE, (data) { + var todoId = data as String; + print('> TodoProcessor -> TodoViewOutputSignal.DELETE: ' + todoId); + todoModel.remove(todoId); + }); + * */ + + Wire.add(this, TodoViewSignal.INPUT, _SignalProcessor); + Wire.add(this, TodoViewSignal.EDIT, _SignalProcessor); + Wire.add(this, TodoViewSignal.DELETE, _SignalProcessor); + Wire.add(this, TodoViewSignal.TOGGLE, _SignalProcessor); + Wire.add(this, TodoViewSignal.FILTER, _SignalProcessor); + Wire.add(this, TodoViewSignal.CLEAR_COMPLETED, _SignalProcessor); + Wire.add(this, TodoViewSignal.COMPLETE_ALL, _SignalProcessor); + + print('Processor Ready'); + } + + void _SignalProcessor(dynamic data, int wid) { + var wire = Wire.get(wid: wid).single; + print('> TodoProcessor -> ${wire.signal}: data = ' + data.toString()); + switch (wire.signal) { + case TodoViewSignal.INPUT: + var createDTO = data as CreateDTO; + var text = createDTO.text; + var note = createDTO.note; + var completed = createDTO.completed; + if (text != null && text.isNotEmpty) { + todoModel.create(text, note, completed); + Wire.send(TodoViewSignal.CLEAR_INPUT); + } + break; + case TodoViewSignal.EDIT: + var editTodoDTO = data as EditDTO; + var todoText = editTodoDTO.text; + var todoNote = editTodoDTO.note; + var todoId = editTodoDTO.id; + if (todoText.isEmpty) { + todoModel.remove(todoId); + } else { + todoModel.update(todoId, todoText, todoNote); + } + break; + case TodoViewSignal.TOGGLE: + var todoId = data as String; + todoModel.toggle(todoId); + break; + case TodoViewSignal.DELETE: + var todoId = data as String; + todoModel.remove(todoId); + break; + case TodoViewSignal.FILTER: + var filter = data as TodoFilterValue; + todoModel.filter(filter); + break; + case TodoViewSignal.CLEAR_COMPLETED: + todoModel.clearCompleted(); + break; + case TodoViewSignal.COMPLETE_ALL: + var completed = data as bool; + todoModel.setCompletionToAll(completed); + break; + } + } +} \ No newline at end of file diff --git a/wire/lib/data/dto/CreateDTO.dart b/wire/lib/data/dto/CreateDTO.dart new file mode 100644 index 00000000..21ba9679 --- /dev/null +++ b/wire/lib/data/dto/CreateDTO.dart @@ -0,0 +1,7 @@ +class CreateDTO { + final String text; + final String note; + final bool completed; + + CreateDTO(this.text, this.note, [this.completed = false]); +} \ No newline at end of file diff --git a/wire/lib/data/dto/EditDTO.dart b/wire/lib/data/dto/EditDTO.dart new file mode 100644 index 00000000..23ff5749 --- /dev/null +++ b/wire/lib/data/dto/EditDTO.dart @@ -0,0 +1,7 @@ +class EditDTO { + final String text; + final String note; + final String id; + + EditDTO(this.id, this.text, this.note); +} \ No newline at end of file diff --git a/wire/lib/data/vo/TodoVO.dart b/wire/lib/data/vo/TodoVO.dart new file mode 100644 index 00000000..02e4f5f4 --- /dev/null +++ b/wire/lib/data/vo/TodoVO.dart @@ -0,0 +1,25 @@ +class TodoVO { + bool completed; + String text; + String note; + String id; + + bool visible = true; + + TodoVO(this.id, this.text, this.note, this.completed); + + TodoVO.fromJson(Map json): + id = json['id'], + text = json['text'], + note = json['note'], + completed = json['completed'] + ; + + Map toJson() => + { + 'id': id, + 'text': text, + 'note': note, + 'completed': completed + }; +} \ No newline at end of file diff --git a/wire/lib/localization.dart b/wire/lib/localization.dart new file mode 100644 index 00000000..6bb0bedb --- /dev/null +++ b/wire/lib/localization.dart @@ -0,0 +1,30 @@ +// Copyright 2018 The Flutter Architecture Sample Authors. All rights reserved. +// Use of this source code is governed by the MIT license that can be found +// in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter/material.dart'; + +class VanillaLocalizations { + static VanillaLocalizations of(BuildContext context) { + return Localizations.of( + context, VanillaLocalizations); + } + + String get appTitle => 'Wire Flutter Example'; +} + +class VanillaLocalizationsDelegate + extends LocalizationsDelegate { + @override + Future load(Locale locale) => + Future(() => VanillaLocalizations()); + + @override + bool shouldReload(VanillaLocalizationsDelegate old) => false; + + @override + bool isSupported(Locale locale) => + locale.languageCode.toLowerCase().contains('en'); +} diff --git a/wire/lib/main.dart b/wire/lib/main.dart new file mode 100644 index 00000000..1183a594 --- /dev/null +++ b/wire/lib/main.dart @@ -0,0 +1,32 @@ +// Copyright 2018 The Flutter Architecture Sample Authors. All rights reserved. +// Use of this source code is governed by the MIT license that can be found +// in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:wire/wire.dart'; + +import 'app.dart'; +import 'const/TodoDataParams.dart'; +import 'const/TodoApplicationState.dart'; +import 'controller/TodoController.dart'; +import 'model/TodoModel.dart'; +import 'service/MobileDatabaseService.dart'; + +var todoModel; +var todoController; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + + Wire.data(TodoDataParams.STATE, TodoApplicationState.LOADING); + + var databaseService = MobileDatabaseService(); + await databaseService.init(TodoModel.LOCAL_STORAGE_KEY); + + todoModel = TodoModel(databaseService); + todoController = TodoController(todoModel); + + runApp(WireApp()); + + Wire.data(TodoDataParams.STATE, TodoApplicationState.READY); +} diff --git a/wire/lib/model/TodoModel.dart b/wire/lib/model/TodoModel.dart new file mode 100644 index 00000000..ee3f9f51 --- /dev/null +++ b/wire/lib/model/TodoModel.dart @@ -0,0 +1,162 @@ +import 'package:wire/wire.dart'; +import '../const/TodoDataParams.dart'; +import '../const/TodoFilterValues.dart'; +import '../service/IDatabaseService.dart'; +import '../data/vo/TodoVO.dart'; + +class TodoModel { + static const String LOCAL_STORAGE_KEY = 'todo-mvc-dart-wire'; + + final IDatabaseService _dbService; + + TodoModel(this._dbService) { + var idsList = []; + var notCompletedCount = 0; + if (_dbService.exist(LOCAL_STORAGE_KEY)) { + try { + _dbService.retrieve(LOCAL_STORAGE_KEY).forEach((obj){ + if (obj != null) { + var todoVO = TodoVO.fromJson(obj); + Wire.data(todoVO.id, todoVO); + idsList.add(todoVO.id); + if (!todoVO.completed) notCompletedCount++; + } + }); + } catch (e) { + print('Error loading form local storage: ' + e.toString()); + } + } + print('> TodoModel list: ${idsList.length}'); + print('> TodoModel count: ${notCompletedCount}'); + Wire.data(TodoDataParams.LIST, idsList); + Wire.data(TodoDataParams.COUNT, notCompletedCount); + } + + TodoVO create(String text, String note, bool completed) { + final time = DateTime.now().millisecondsSinceEpoch; + final id = time.toString(); + final todoVO = TodoVO(id, text, note, completed); + final listData = Wire.data(TodoDataParams.LIST); + final todoList = listData.value as List; + final count = Wire.data(TodoDataParams.COUNT).value as int; + + todoList.add(todoVO.id); + Wire.data(todoVO.id, todoVO); + Wire.data(TodoDataParams.LIST, todoList); + Wire.data(TodoDataParams.COUNT, count + (completed ? 0 : 1)); + + _save(); + + print('> TodoModel -> created: ' + todoVO.id + ' - ' + todoVO.text); + return todoVO; + } + + TodoVO remove(String id) { + final todoList = Wire.data(TodoDataParams.LIST).value as List; + final count = Wire.data(TodoDataParams.COUNT).value as int; + final todoWireData = Wire.data(id); + final todoVO = todoWireData.value as TodoVO; + + todoList.remove(id); + todoWireData.remove(); + + if (todoVO.completed == false) { + Wire.data(TodoDataParams.COUNT, count - 1); + } + // Only difference with web version in Wire repositories (example TodoMVC) + Wire.data(TodoDataParams.LIST, todoList); + + _save(); + + print('> TodoModel -> removed: ' + id); + return todoVO; + } + + TodoVO update(String id, String text, String note) { + final todoWireData = Wire.data(id); + final todoVO = todoWireData.value as TodoVO; + todoVO.text = text; + todoVO.note = note; + Wire.data(id, todoVO); + _save(); + + print('> TodoModel -> updated: ' + todoVO.id + ' - ' + todoVO.text); + return todoVO; + } + + TodoVO toggle(String id) { + final todoWireData = Wire.data(id); + final todoVO = todoWireData.value as TodoVO; + final count = Wire.data(TodoDataParams.COUNT).value as int; + + todoVO.completed = !todoVO.completed; + + Wire.data(id, todoVO); + Wire.data(TodoDataParams.COUNT, count + (todoVO.completed ? -1 : 1)); + + _save(); + + print('> TodoModel -> toggled: ' + todoVO.id + ' - ' + todoVO.text); + return null; + } + + void filter(TodoFilterValue filter) { + final todoList = Wire.data(TodoDataParams.LIST).value as List; + todoList.forEach((id) { + var todoWireData = Wire.data(id); + var todoVO = todoWireData.value as TodoVO; + var todoVisible = todoVO.visible; + switch (filter) { + case TodoFilterValue.ALL: todoVisible = true; break; + case TodoFilterValue.ACTIVE: todoVisible = !todoVO.completed; break; + case TodoFilterValue.COMPLETED: todoVisible = todoVO.completed; break; + } + if (todoVO.visible != todoVisible) { + todoVO.visible = todoVisible; + Wire.data(id, todoVO); + } + }); + Wire.data(TodoDataParams.FILTER, filter); + print('> TodoModel -> filtered: ' + filter.toString()); + } + + void setCompletionToAll(value) { + final todoList = Wire.data(TodoDataParams.LIST).value as List; + var count = Wire.data(TodoDataParams.COUNT).value as int; + print('> TodoModel -> setCompletionToAll: $value - ' + count.toString()); + todoList.forEach((id) { + var todoWireData = Wire.data(id); + var todoVO = todoWireData.value as TodoVO; + if (todoVO.completed != value) { + count += value ? -1 : 1; + todoVO.completed = value; + Wire.data(id, todoVO); + } + }); + Wire.data(TodoDataParams.COUNT, count); + _save(); + } + + void clearCompleted() { + final todoList = Wire.data(TodoDataParams.LIST).value as List; + todoList.removeWhere((id) { + var todoWireData = Wire.data(id); + var todoVO = todoWireData.value as TodoVO; + if (todoVO.completed) { + todoWireData.remove(); + } + return todoVO.completed; + }); + Wire.data(TodoDataParams.LIST, todoList); + _save(); + print('> TodoModel -> clearCompleted: length = ' + todoList.length.toString()); + } + + void _save() { + var listToSave = []; + (Wire.data(TodoDataParams.LIST).value as List).forEach((id) => + listToSave.add(Wire.data(id).value) + ); + _dbService.save(LOCAL_STORAGE_KEY, listToSave); + } +} \ No newline at end of file diff --git a/wire/lib/models.dart b/wire/lib/models.dart new file mode 100644 index 00000000..ae77f7d5 --- /dev/null +++ b/wire/lib/models.dart @@ -0,0 +1,54 @@ +// Copyright 2018 The Flutter Architecture Sample Authors. All rights reserved. +// Use of this source code is governed by the MIT license that can be found +// in the LICENSE file. + +import 'package:todos_app_core/todos_app_core.dart'; +import 'package:todos_repository_core/todos_repository_core.dart'; + +enum AppTab { todos, stats } + +enum ExtraAction { toggleAllComplete, clearCompleted } + +class Todo { + bool complete; + String id; + String note; + String task; + + Todo(this.task, {this.complete = false, this.note = '', String id}) + : id = id ?? Uuid().generateV4(); + + @override + int get hashCode => + complete.hashCode ^ task.hashCode ^ note.hashCode ^ id.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is Todo && + runtimeType == other.runtimeType && + complete == other.complete && + task == other.task && + note == other.note && + id == other.id; + + @override + String toString() { + return 'Todo{complete: $complete, task: $task, note: $note, id: $id}'; + } + + TodoEntity toEntity() { + return TodoEntity(task, id, note, complete); + } + + static Todo fromEntity(TodoEntity entity) { + return Todo( + entity.task, + complete: entity.complete ?? false, + note: entity.note, + id: entity.id ?? Uuid().generateV4(), + ); + } +} + +enum VisibilityFilter { all, active, completed } diff --git a/wire/lib/screens/add_edit_screen.dart b/wire/lib/screens/add_edit_screen.dart new file mode 100644 index 00000000..238cbac8 --- /dev/null +++ b/wire/lib/screens/add_edit_screen.dart @@ -0,0 +1,106 @@ +// Copyright 2018 The Flutter Architecture Sample Authors. All rights reserved. +// Use of this source code is governed by the MIT license that can be found +// in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:todos_app_core/todos_app_core.dart'; +import 'package:wire_flutter/wire_flutter.dart'; +import 'package:wire_flutter_todo/data/vo/TodoVO.dart'; +import 'package:wire_flutter_todo/data/dto/EditDTO.dart'; +import 'package:wire_flutter_todo/const/TodoViewSignal.dart'; +import 'package:wire_flutter_todo/data/dto/CreateDTO.dart'; +import 'package:wire/wire.dart'; + +class AddEditScreen extends StatefulWidget { + final String id; + + AddEditScreen({ + this.id, + }) : super(key: ArchSampleKeys.addTodoScreen); + + @override + _AddEditScreenState createState() => _AddEditScreenState(); +} + +class _AddEditScreenState extends State { + static final GlobalKey formKey = GlobalKey(); + + String _text; + String _note; + + @override + Widget build(BuildContext context) { + print('isEditing ${isEditing}'); + return Scaffold( + appBar: AppBar( + title: Text(isEditing + ? ArchSampleLocalizations.of(context).editTodo + : ArchSampleLocalizations.of(context).addTodo), + ), + body: Padding( + padding: EdgeInsets.all(16.0), + child: Form( + key: formKey, + autovalidate: false, + onWillPop: () { + return Future(() => true); + }, + child: !isEditing ? ListViewWidget(context, null) : + WireDataBuilder( dataKey: widget.id, + builder: (ctx, todoVO) => ListViewWidget(ctx, todoVO), + ), + ), + ), + floatingActionButton: FloatingActionButton( + key: isEditing + ? ArchSampleKeys.saveTodoFab + : ArchSampleKeys.saveNewTodo, + tooltip: isEditing + ? ArchSampleLocalizations.of(context).saveChanges + : ArchSampleLocalizations.of(context).addTodo, + child: Icon(isEditing ? Icons.check : Icons.add), + onPressed: () { + final form = formKey.currentState; + if (form.validate()) { + form.save(); + + if (isEditing) { + Wire.send(TodoViewSignal.EDIT, EditDTO(widget.id, _text, _note)); + } else { + Wire.send(TodoViewSignal.INPUT, CreateDTO(_text, _note)); + } + + Navigator.pop(context); + } + }), + ); + } + + Widget ListViewWidget(context, todoVO) => ListView( + children: [ + TextFormField( + initialValue: todoVO?.text ?? '', + key: ArchSampleKeys.taskField, + autofocus: isEditing ? false : true, + style: Theme.of(context).textTheme.headline6, + decoration: InputDecoration(hintText: ArchSampleLocalizations.of(context).newTodoHint), + validator: (val) => val.trim().isEmpty + ? ArchSampleLocalizations.of(context).emptyTodoError + : null, + onSaved: (value) => _text = value, + ), + TextFormField( + initialValue: todoVO?.note ?? '', + key: ArchSampleKeys.noteField, + maxLines: 10, + style: Theme.of(context).textTheme.subtitle1, + decoration: InputDecoration(hintText: ArchSampleLocalizations.of(context).notesHint,), + onSaved: (value) => _note = value, + ) + ], + ); + + bool get isEditing => widget.id != null; +} diff --git a/wire/lib/screens/detail_screen.dart b/wire/lib/screens/detail_screen.dart new file mode 100644 index 00000000..28cbc74d --- /dev/null +++ b/wire/lib/screens/detail_screen.dart @@ -0,0 +1,89 @@ +// Copyright 2018 The Flutter Architecture Sample Authors. All rights reserved. +// Use of this source code is governed by the MIT license that can be found +// in the LICENSE file. + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:todos_app_core/todos_app_core.dart'; +import 'package:wire_flutter_todo/const/TodoViewSignal.dart'; +import 'package:wire_flutter_todo/data/vo/TodoVO.dart'; +import 'package:wire_flutter_todo/screens/add_edit_screen.dart'; +import 'package:wire/wire.dart'; +import 'package:wire_flutter/wire_flutter.dart'; + +class DetailScreen extends StatelessWidget { + final String id; + final Function onDelete; + + DetailScreen({ + @required this.id, + @required this.onDelete, + }) : super(key: ArchSampleKeys.todoDetailsScreen); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(ArchSampleLocalizations.of(context).todoDetails), + actions: [ + IconButton( + key: ArchSampleKeys.deleteTodoButton, + tooltip: ArchSampleLocalizations.of(context).deleteTodo, + icon: Icon(Icons.delete), + onPressed: () { + Navigator.pop(context); + onDelete(); + }, + ) + ], + ), + body: Padding( + padding: EdgeInsets.all(16.0), + child: WireDataBuilder( + dataKey: id, + builder: (context, todoVO) => ListView( + children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only(right: 8.0), + child: Checkbox( value: todoVO.completed, + key: ArchSampleKeys.detailsTodoItemCheckbox, + onChanged: (value) => Wire.send(TodoViewSignal.TOGGLE, id), + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only(top: 8.0, bottom: 16.0,), + child: Text(todoVO.text, + key: ArchSampleKeys.detailsTodoItemTask, + style: Theme.of(context).textTheme.headline6, + ), + ), + Text(todoVO.note, + key: ArchSampleKeys.detailsTodoItemNote, + style: Theme.of(context).textTheme.subtitle1, + ) + ], + ), + ), + ]), + ], + ), + ), + ), + floatingActionButton: FloatingActionButton( + tooltip: ArchSampleLocalizations.of(context).editTodo, + child: Icon(Icons.edit), + key: ArchSampleKeys.editTodoFab, + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute(builder: (context) => AddEditScreen(id: id)), + ); + }, + ), + ); + } +} diff --git a/wire/lib/screens/home_screen.dart b/wire/lib/screens/home_screen.dart new file mode 100644 index 00000000..2a325164 --- /dev/null +++ b/wire/lib/screens/home_screen.dart @@ -0,0 +1,99 @@ +// Copyright 2018 The Flutter Architecture Sample Authors. All rights reserved. +// Use of this source code is governed by the MIT license that can be found +// in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:wire/wire.dart'; +import 'package:todos_app_core/todos_app_core.dart'; +import 'package:wire_flutter/wire_flutter.dart'; +import 'package:wire_flutter_todo/const/TodoDataParams.dart'; +import 'package:wire_flutter_todo/const/TodoFilterValues.dart'; +import 'package:wire_flutter_todo/const/TodoViewSignal.dart'; +import 'package:wire_flutter_todo/localization.dart'; +import 'package:wire_flutter_todo/models.dart'; +import 'package:wire_flutter_todo/widgets/buttons/extra_actions_button.dart'; +import 'package:wire_flutter_todo/widgets/buttons/filter_button.dart'; +import 'package:wire_flutter_todo/widgets/stats_counter.dart'; +import 'package:wire_flutter_todo/widgets/list/todo_list_widget.dart'; + +class HomeScreen extends StatefulWidget { + HomeScreen() : super(key: ArchSampleKeys.homeScreen); + @override + State createState() => HomeScreenState(); +} + +class HomeScreenState extends State { + AppTab activeTab = AppTab.todos; + + void _updateTab(AppTab tab) { + setState(() { + activeTab = tab; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(VanillaLocalizations.of(context).appTitle), + actions: [ + FilterButton( + isActive: activeTab == AppTab.todos, + activeFilter: TodoFilterValue.ALL, + onSelected: (filter) => Wire.send(TodoViewSignal.FILTER, filter), + ), + WireDataBuilder( + dataKey: TodoDataParams.COUNT, + builder: (context, notCompletedCount) { + var allTodoCount = Wire.data(TodoDataParams.LIST).value.length; + var allCompleted = notCompletedCount == 0 && allTodoCount > 0; + var hasCompletedTodos = (allTodoCount - notCompletedCount) > 0; + return ExtraActionsButton( + allComplete: allCompleted, + hasCompletedTodos: hasCompletedTodos, + onSelected: (action) { + if (action == ExtraAction.toggleAllComplete) { + Wire.send(TodoViewSignal.COMPLETE_ALL, !allCompleted); + } else if (action == ExtraAction.clearCompleted) { + Wire.send(TodoViewSignal.CLEAR_COMPLETED); + } + }, + );}, + ) + ], + ), + body: activeTab == AppTab.todos + ? TodoList() + : StatsCounter(), + + floatingActionButton: FloatingActionButton( + key: ArchSampleKeys.addTodoFab, + onPressed: () => Navigator.pushNamed(context, ArchSampleRoutes.addTodo), + child: Icon(Icons.add), + tooltip: ArchSampleLocalizations.of(context).addTodo, + ), + bottomNavigationBar: BottomNavigationWidget(activeTab) + ); + } + + Widget BottomNavigationWidget(activeTab) => BottomNavigationBar( + key: ArchSampleKeys.tabs, + currentIndex: AppTab.values.indexOf(activeTab), + onTap: (index) { _updateTab(AppTab.values[index]); }, + items: AppTab.values.map((tab) { + return BottomNavigationBarItem( + icon: Icon( + tab == AppTab.todos ? Icons.list : Icons.show_chart, + key: tab == AppTab.stats + ? ArchSampleKeys.statsTab + : ArchSampleKeys.todoTab, + ), + title: Text( + tab == AppTab.stats + ? ArchSampleLocalizations.of(context).stats + : ArchSampleLocalizations.of(context).todos, + ), + ); + }).toList(), + ); +} diff --git a/wire/lib/service/IDatabaseService.dart b/wire/lib/service/IDatabaseService.dart new file mode 100644 index 00000000..ae6c1cc4 --- /dev/null +++ b/wire/lib/service/IDatabaseService.dart @@ -0,0 +1,6 @@ +abstract class IDatabaseService { + Future init([String key]); + bool exist(String key); + dynamic retrieve(String key); + void save(String key, dynamic data); +} \ No newline at end of file diff --git a/wire/lib/service/MobileDatabaseService.dart b/wire/lib/service/MobileDatabaseService.dart new file mode 100644 index 00000000..ac234e04 --- /dev/null +++ b/wire/lib/service/MobileDatabaseService.dart @@ -0,0 +1,45 @@ +import 'package:key_value_store_flutter/key_value_store_flutter.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:todos_repository_core/todos_repository_core.dart'; +import 'package:todos_repository_local_storage/todos_repository_local_storage.dart'; + +import 'IDatabaseService.dart'; + +class MobileDatabaseService extends IDatabaseService { + MobileDatabaseService(); + + TodosRepository repository; + var jsonList = []; + + @override + Future init([String key]) async { + repository = LocalStorageRepository( + localStorage: KeyValueStorage( + key, + FlutterKeyValueStore(await SharedPreferences.getInstance()), + )); + await repository.loadTodos().then((loadedTodos) { + loadedTodos.forEach((todoEntry) => { + jsonList.add({ + 'id': todoEntry.id, + 'text': todoEntry.task, + 'note': todoEntry.note, + 'completed': todoEntry.complete + }) + }); + }); + } + + @override + bool exist(String key) { return true; } + + @override + dynamic retrieve(String key) { + return jsonList; + } + + @override + void save(String key, dynamic data) { + + } +} \ No newline at end of file diff --git a/wire/lib/widgets/buttons/extra_actions_button.dart b/wire/lib/widgets/buttons/extra_actions_button.dart new file mode 100644 index 00000000..a6ee7358 --- /dev/null +++ b/wire/lib/widgets/buttons/extra_actions_button.dart @@ -0,0 +1,42 @@ +// Copyright 2018 The Flutter Architecture Sample Authors. All rights reserved. +// Use of this source code is governed by the MIT license that can be found +// in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:todos_app_core/todos_app_core.dart'; +import 'package:wire_flutter_todo/models.dart'; + +class ExtraActionsButton extends StatelessWidget { + final PopupMenuItemSelected onSelected; + final bool allComplete; + final bool hasCompletedTodos; + + ExtraActionsButton({ + this.onSelected, + this.allComplete, + this.hasCompletedTodos, + Key key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return PopupMenuButton( + key: ArchSampleKeys.extraActionsButton, + onSelected: onSelected, + itemBuilder: (BuildContext context) => >[ + PopupMenuItem( + key: ArchSampleKeys.toggleAll, + value: ExtraAction.toggleAllComplete, + child: Text(allComplete + ? ArchSampleLocalizations.of(context).markAllIncomplete + : ArchSampleLocalizations.of(context).markAllComplete), + ), + if (hasCompletedTodos) PopupMenuItem( + key: ArchSampleKeys.clearCompleted, + value: ExtraAction.clearCompleted, + child: Text(ArchSampleLocalizations.of(context).clearCompleted), + ), + ], + ); + } +} diff --git a/wire/lib/widgets/buttons/filter_button.dart b/wire/lib/widgets/buttons/filter_button.dart new file mode 100644 index 00000000..d74c5991 --- /dev/null +++ b/wire/lib/widgets/buttons/filter_button.dart @@ -0,0 +1,94 @@ +// Copyright 2018 The Flutter Architecture Sample Authors. All rights reserved. +// Use of this source code is governed by the MIT license that can be found +// in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:todos_app_core/todos_app_core.dart'; +import 'package:wire_flutter_todo/const/TodoFilterValues.dart'; + +class FilterButton extends StatelessWidget { + final PopupMenuItemSelected onSelected; + final TodoFilterValue activeFilter; + final bool isActive; + + FilterButton({this.onSelected, this.activeFilter, this.isActive, Key key}) + : super(key: key); + + @override + Widget build(BuildContext context) { + final defaultStyle = Theme.of(context).textTheme.bodyText1; + final activeStyle = Theme.of(context) + .textTheme + .bodyText1 + .copyWith(color: Theme.of(context).accentColor); + final button = _Button( + onSelected: onSelected, + activeFilter: activeFilter, + activeStyle: activeStyle, + defaultStyle: defaultStyle, + ); + + return AnimatedOpacity( + opacity: isActive ? 1.0 : 0.0, + duration: Duration(milliseconds: 150), + child: isActive ? button : IgnorePointer(child: button), + ); + } +} + +class _Button extends StatelessWidget { + const _Button({ + Key key, + @required this.onSelected, + @required this.activeFilter, + @required this.activeStyle, + @required this.defaultStyle, + }) : super(key: key); + + final PopupMenuItemSelected onSelected; + final TodoFilterValue activeFilter; + final TextStyle activeStyle; + final TextStyle defaultStyle; + + @override + Widget build(BuildContext context) { + return PopupMenuButton( + key: ArchSampleKeys.filterButton, + tooltip: ArchSampleLocalizations.of(context).filterTodos, + onSelected: onSelected, + itemBuilder: (BuildContext context) => >[ + PopupMenuItem( + key: ArchSampleKeys.allFilter, + value: TodoFilterValue.ALL, + child: Text( + ArchSampleLocalizations.of(context).showAll, + style: activeFilter == TodoFilterValue.ALL + ? activeStyle + : defaultStyle, + ), + ), + PopupMenuItem( + key: ArchSampleKeys.activeFilter, + value: TodoFilterValue.ACTIVE, + child: Text( + ArchSampleLocalizations.of(context).showActive, + style: activeFilter == TodoFilterValue.ACTIVE + ? activeStyle + : defaultStyle, + ), + ), + PopupMenuItem( + key: ArchSampleKeys.completedFilter, + value: TodoFilterValue.COMPLETED, + child: Text( + ArchSampleLocalizations.of(context).showCompleted, + style: activeFilter == TodoFilterValue.COMPLETED + ? activeStyle + : defaultStyle, + ), + ), + ], + icon: Icon(Icons.filter_list), + ); + } +} diff --git a/wire/lib/widgets/list/todo_item_widget.dart b/wire/lib/widgets/list/todo_item_widget.dart new file mode 100644 index 00000000..05e26d13 --- /dev/null +++ b/wire/lib/widgets/list/todo_item_widget.dart @@ -0,0 +1,53 @@ +// Copyright 2018 The Flutter Architecture Sample Authors. All rights reserved. +// Use of this source code is governed by the MIT license that can be found +// in the LICENSE file. + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:todos_app_core/todos_app_core.dart'; +import 'package:wire_flutter_todo/data/vo/TodoVO.dart'; +import 'package:wire_flutter/wire_flutter.dart'; + +class TodoItem extends StatelessWidget { + + final String id; + final GestureTapCallback onTap; + final ValueChanged onToggle; + + TodoItem({ + Key key, + @required this.id, + @required this.onTap, + @required this.onToggle, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return WireDataBuilder( + dataKey: id, + builder: (context, todoVO) => Visibility( + visible: todoVO.visible, + child: ListTile( + onTap: onTap, + leading: Checkbox( + key: ArchSampleKeys.todoItemCheckbox(todoVO.id), + value: todoVO.completed, + onChanged: onToggle, + ), + title: Text( + todoVO.text, + key: ArchSampleKeys.todoItemTask(todoVO.id), + style: Theme.of(context).textTheme.headline6, + ), + subtitle: Text( + todoVO.note, + key: ArchSampleKeys.todoItemNote(todoVO.id), + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.subtitle1, + ), + ), + ), + ); + } +} diff --git a/wire/lib/widgets/list/todo_list_widget.dart b/wire/lib/widgets/list/todo_list_widget.dart new file mode 100644 index 00000000..7f07480d --- /dev/null +++ b/wire/lib/widgets/list/todo_list_widget.dart @@ -0,0 +1,93 @@ +// Copyright 2018 The Flutter Architecture Sample Authors. All rights reserved. +// Use of this source code is governed by the MIT license that can be found +// in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:todos_app_core/todos_app_core.dart'; +import 'package:wire_flutter_todo/const/TodoDataParams.dart'; +import 'package:wire_flutter_todo/const/TodoViewSignal.dart'; +import 'package:wire_flutter_todo/const/TodoApplicationState.dart'; +import 'package:wire_flutter_todo/data/dto/CreateDTO.dart'; +import 'package:wire_flutter_todo/data/vo/TodoVO.dart'; +import 'package:wire_flutter_todo/screens/detail_screen.dart'; +import 'package:wire_flutter_todo/widgets/list/todo_item_widget.dart'; +import 'package:wire/wire.dart'; +import 'package:wire_flutter/wire_flutter.dart'; + +import '../../const/TodoDataParams.dart'; + +class TodoList extends StatelessWidget { + + TodoList() : super(key: ArchSampleKeys.todoList); + + @override + Widget build(BuildContext context) { + return WireDataBuilder( + dataKey: TodoDataParams.STATE, + builder: (context, state) => Container( + child: state == TodoApplicationState.LOADING + ? Center( + child: CircularProgressIndicator( + key: ArchSampleKeys.todosLoading, + )) + : WireDataBuilder>( + dataKey: TodoDataParams.LIST, + builder: (context, list) => ListView.builder( + key: ArchSampleKeys.todoList, + itemCount: list.length, + itemBuilder: (BuildContext context, int index) { + final todoId = list[index]; + return Dismissible( + key: ArchSampleKeys.todoItem(todoId), + onDismissed: (direction) { + _removeTodo(context, todoId); + }, + child: TodoItem( + id: todoId, + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) { + return DetailScreen( + id: todoId, + onDelete: () => { + _removeTodo(context, todoId) + }, + ); + }, + ), + ); + }, + onToggle: (value) => + Wire.send(TodoViewSignal.TOGGLE, todoId), + ), + ); + }, + ), + ), + ), + ); + } + + void _removeTodo(BuildContext context, String todoId) { + var todoWireData = Wire.data(todoId); + TodoVO todoVO = todoWireData.value; + Wire.send(TodoViewSignal.DELETE, todoId); + + Scaffold.of(context).showSnackBar( + SnackBar( + key: ArchSampleKeys.snackbar, + duration: Duration(seconds: 2), + content: Text( + ArchSampleLocalizations.of(context).todoDeleted(todoVO.text), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + action: SnackBarAction( + label: ArchSampleLocalizations.of(context).undo, + onPressed: () => Wire.send(TodoViewSignal.INPUT, CreateDTO(todoVO.text, todoVO.note, todoVO.completed)) + ), + ), + ); + } +} diff --git a/wire/lib/widgets/stats_counter.dart b/wire/lib/widgets/stats_counter.dart new file mode 100644 index 00000000..a2141695 --- /dev/null +++ b/wire/lib/widgets/stats_counter.dart @@ -0,0 +1,60 @@ +// Copyright 2018 The Flutter Architecture Sample Authors. All rights reserved. +// Use of this source code is governed by the MIT license that can be found +// in the LICENSE file. + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:todos_app_core/todos_app_core.dart'; +import 'package:wire/wire.dart'; +import 'package:wire_flutter/wire_flutter.dart'; +import 'package:wire_flutter_todo/const/TodoDataParams.dart'; + +class StatsCounter extends StatelessWidget { + StatsCounter() : super(key: ArchSampleKeys.statsCounter); + + @override + Widget build(BuildContext context) { + return Center( + child: WireDataBuilder( + dataKey: TodoDataParams.COUNT, + builder: (context, notCompletedCount) { + var allTodoCount = Wire.data(TodoDataParams.LIST).value.length; + var numCompleted = allTodoCount - notCompletedCount; + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: EdgeInsets.only(bottom: 8.0), + child: Text( + ArchSampleLocalizations.of(context).completedTodos, + style: Theme.of(context).textTheme.bodyText1, + ), + ), + Padding( + padding: EdgeInsets.only(bottom: 24.0), + child: Text('$numCompleted', + key: ArchSampleKeys.statsNumCompleted, + style: Theme.of(context).textTheme.subtitle1, + ), + ), + Padding( + padding: EdgeInsets.only(bottom: 8.0), + child: Text( + ArchSampleLocalizations.of(context).activeTodos, + style: Theme.of(context).textTheme.subtitle2, + ), + ), + Padding( + padding: EdgeInsets.only(bottom: 24.0), + child: Text( + '$notCompletedCount', + key: ArchSampleKeys.statsNumActive, + style: Theme.of(context).textTheme.subtitle1, + ), + ) + ], + );}, + ), + ); + } +} diff --git a/wire/pubspec.yaml b/wire/pubspec.yaml new file mode 100644 index 00000000..f6922863 --- /dev/null +++ b/wire/pubspec.yaml @@ -0,0 +1,82 @@ +name: wire_flutter_todo +description: A new Flutter project. + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +version: 1.0.0+1 + +environment: + sdk: ">=2.2.2 <3.0.0" + +dependencies: + flutter: + sdk: flutter + todos_app_core: + path: ../todos_app_core + todos_repository_local_storage: + path: ../todos_repository_local_storage + key_value_store_flutter: + key_value_store_web: + shared_preferences: + wire: ^0.9.7 + wire_flutter: ^0.9.0 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_driver: + sdk: flutter + test: + mockito: + integration_tests: + path: ../integration_tests + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/wire/test/app_state_test.dart b/wire/test/app_state_test.dart new file mode 100644 index 00000000..634b936a --- /dev/null +++ b/wire/test/app_state_test.dart @@ -0,0 +1,109 @@ +// Copyright 2018 The Flutter Architecture Sample Authors. All rights reserved. +// Use of this source code is governed by the MIT license that can be found +// in the LICENSE file. + +import 'package:test/test.dart'; + +void main() { + group('AppState', () { +// test('should check if there are completed todos', () { +// final state = AppState(todos: [ +// Todo('a'), +// Todo('b'), +// Todo('c', complete: true), +// ]); +// +// expect(state.hasCompletedTodos, true); +// }); +// +// test('should calculate the number of active todos', () { +// final state = AppState(todos: [ +// Todo('a'), +// Todo('b'), +// Todo('c', complete: true), +// ]); +// +// expect(state.numActive, 2); +// }); +// +// +// test('should return active todos if the VisibilityFilter is active', () { +// final todo1 = Todo('a'); +// final todo2 = Todo('b'); +// final todo3 = Todo('c', complete: true); +// final todos = [ +// todo1, +// todo2, +// todo3, +// ]; +// final state = AppState( +// todos: todos, +// ); +// +// expect(state.filteredTodos(VisibilityFilter.active), [ +// todo1, +// todo2, +// ]); +// }); +// +// test('should return completed todos if the VisibilityFilter is completed', +// () { +// final todo1 = Todo('a'); +// final todo2 = Todo('b'); +// final todo3 = Todo('c', complete: true); +// final todos = [ +// todo1, +// todo2, +// todo3, +// ]; +// final state = AppState( +// todos: todos, +// ); +// +// expect(state.filteredTodos(VisibilityFilter.completed), [todo3]); +// }); +// +// test('should clear the completed todos', () { +// final todo1 = Todo('a'); +// final todo2 = Todo('b'); +// final todo3 = Todo('c', complete: true); +// final todos = [ +// todo1, +// todo2, +// todo3, +// ]; +// final state = AppState( +// todos: todos, +// ); +// +// state.clearCompleted(); +// +// expect(state.todos, [ +// todo1, +// todo2, +// ]); +// }); +// +// test('toggle all as complete or incomplete', () { +// final todo1 = Todo('a'); +// final todo2 = Todo('b'); +// final todo3 = Todo('c', complete: true); +// final todos = [ +// todo1, +// todo2, +// todo3, +// ]; +// final state = AppState( +// todos: todos, +// ); +// +// // Toggle all complete +// state.toggleAll(); +// expect(state.todos.every((t) => t.complete), isTrue); +// +// // Toggle all incomplete +// state.toggleAll(); +// expect(state.todos.every((t) => !t.complete), isTrue); +// }); + }); +} diff --git a/wire/test/widget_test.dart b/wire/test/widget_test.dart new file mode 100644 index 00000000..5967b1a8 --- /dev/null +++ b/wire/test/widget_test.dart @@ -0,0 +1,27 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility that Flutter provides. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. +// await tester.pumpWidget(MyApp()); +// +// // Verify that our counter starts at 0. +// expect(find.text('0'), findsOneWidget); +// expect(find.text('1'), findsNothing); +// +// // Tap the '+' icon and trigger a frame. +// await tester.tap(find.byIcon(Icons.add)); +// await tester.pump(); +// +// // Verify that our counter has incremented. +// expect(find.text('0'), findsNothing); +// expect(find.text('1'), findsOneWidget); + }); +} diff --git a/wire/test_driver/todo_app.dart b/wire/test_driver/todo_app.dart new file mode 100644 index 00000000..dd9b3ec2 --- /dev/null +++ b/wire/test_driver/todo_app.dart @@ -0,0 +1,13 @@ +// Copyright 2018 The Flutter Architecture Sample Authors. All rights reserved. +// Use of this source code is governed by the MIT license that can be found +// in the LICENSE file. + +// This line imports the extension +import 'package:flutter_driver/driver_extension.dart'; +import 'package:wire_flutter_todo/main.dart' as app; + +void main() { + enableFlutterDriverExtension(); + + app.main(); +} diff --git a/wire/test_driver/todo_app_test.dart b/wire/test_driver/todo_app_test.dart new file mode 100644 index 00000000..ad93f5a5 --- /dev/null +++ b/wire/test_driver/todo_app_test.dart @@ -0,0 +1,9 @@ +// Copyright 2018 The Flutter Architecture Sample Authors. All rights reserved. +// Use of this source code is governed by the MIT license that can be found +// in the LICENSE file. + +import 'package:integration_tests/integration_tests.dart' as integration_tests; + +void main() { + integration_tests.main(); +} diff --git a/wire/web/favicon.png b/wire/web/favicon.png new file mode 100644 index 00000000..8aaa46ac Binary files /dev/null and b/wire/web/favicon.png differ diff --git a/wire/web/icons/Icon-192.png b/wire/web/icons/Icon-192.png new file mode 100644 index 00000000..b749bfef Binary files /dev/null and b/wire/web/icons/Icon-192.png differ diff --git a/wire/web/icons/Icon-512.png b/wire/web/icons/Icon-512.png new file mode 100644 index 00000000..88cfd48d Binary files /dev/null and b/wire/web/icons/Icon-512.png differ diff --git a/wire/web/index.html b/wire/web/index.html new file mode 100644 index 00000000..31c9d35b --- /dev/null +++ b/wire/web/index.html @@ -0,0 +1,10 @@ + + + + + wire_flutter_todo + + + + + diff --git a/wire/web/manifest.json b/wire/web/manifest.json new file mode 100644 index 00000000..20bf7247 --- /dev/null +++ b/wire/web/manifest.json @@ -0,0 +1,23 @@ +{ + "name": "wire_flutter_todo", + "short_name": "wire_flutter_todo", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "Wire Flutter application.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} diff --git a/wire/web/src/WebDatabaseService.dart b/wire/web/src/WebDatabaseService.dart new file mode 100644 index 00000000..fa60f79c --- /dev/null +++ b/wire/web/src/WebDatabaseService.dart @@ -0,0 +1,24 @@ +import 'dart:html'; +import 'dart:convert'; + +import '../../lib/service/IDatabaseService.dart'; + +class WebDatabaseService extends IDatabaseService { + @override + bool exist(String key) { + return window.localStorage.containsKey(key); + } + + @override + dynamic retrieve(String key) { + return jsonDecode(window.localStorage[key]); + } + + @override + void save(String key, dynamic data) { + window.localStorage[key] = jsonEncode(data); + } + + @override + Future init([String key]) { return null; } +} \ No newline at end of file diff --git a/wire/web/src/main_web.dart b/wire/web/src/main_web.dart new file mode 100644 index 00000000..3d617c18 --- /dev/null +++ b/wire/web/src/main_web.dart @@ -0,0 +1,32 @@ +// Copyright 2018 The Flutter Architecture Sample Authors. All rights reserved. +// Use of this source code is governed by the MIT license that can be found +// in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:wire/wire.dart'; +import 'package:wire_flutter_todo/app.dart'; +import 'package:wire_flutter_todo/const/TodoApplicationState.dart'; +import 'package:wire_flutter_todo/controller/TodoController.dart'; +import 'file:///Users/vladimir.minkin/Documents/Projects/Personal/Flutter/flutter_architecture_samples/wire/web/src/WebDatabaseService.dart'; + +import '../../lib/const/TodoDataParams.dart'; +import '../../lib/model/TodoModel.dart'; + +var todoModel; +var todoController; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + + Wire.data(TodoDataParams.STATE, TodoApplicationState.LOADING); + + var databaseService = WebDatabaseService(); + await databaseService.init(TodoModel.LOCAL_STORAGE_KEY); + + todoModel = TodoModel(databaseService); + todoController = TodoController(todoModel); + + runApp(WireApp()); + + Wire.data(TodoDataParams.STATE, TodoApplicationState.READY); +}