diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f3c2053 --- /dev/null +++ b/.gitignore @@ -0,0 +1,44 @@ +# 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/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Exceptions to above rules. +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..ca28e69 --- /dev/null +++ b/.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: fba99f6cf9a14512e461e3122c8ddfaa25394e89 + channel: stable + +project_type: app diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..df6c8c6 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,19 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Dart: Run all Tests", + "type": "dart", + "request": "launch", + "program": "./test/" + }, + { + "name": "Flutter", + "request": "launch", + "type": "dart" + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 2537c11..c1cc873 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,79 @@ # In name of Allah + + ## Introduction -We want a simple app to get events of a specific calendar, and add events to it through google calendar APIs. It is consisted of a simple page which shows a lazy loaded list of events with following description: + +We want a simple app to get events of a specific calendar, and add events to it through google calendar APIs. It is consisted of a simple page which shows a lazy loaded list of events with following description: + - event description + - event name + - due date -- repeat + +- repeat + + and also a floating action button in the bottom of page which opens a modal that gets the cited fields, and creates a new event (must be created in google calendar). + + ### Note + It is highly recommended to use flutter BLoC pattern. + + ## Expectations + + So What does matter to us? + - a clean structure of codebase + - clean code practices + - getting familiar with state management practices & patterns + - finally & most important, ability to learn (this project is primarily a test to see how you can learn the things you do not know, so don't be afraid if you don't know a subject, that is our goal) + + ## Tasks + + 1. Fork this repository + 2. Estimate the develop & send it to us + 3. Break and specify your tasks in project management tool -4. Learn & Develop + +4. Learn & Develop + 5. Push your code to your repository + 6. Send us a pull request, we will review and get back to you + 7. Enjoy + + **Finally** don't be afraid to ask something from your mentor + + + +## My Experience + +Since the goal was learning. let's see what we learned : + +1. [Flutter clean architecture & TDD](https://resocoder.com/flutter-clean-architecture-tdd/) courtesy of [Reso Coder](https://resocoder.com/) + +2. [Bloc ](https://pub.dev/packages/flutter_bloc)State Management + +3. Working with [google apis](https://pub.dev/packages/googleapis) + +4. Auhenticating with [google_sign_in ](https://pub.dev/packages/google_sign_in) package (Originally intended to use google_auth package, BUT surprisingly it spawns unnecessary web authentication while mobiles have google account built in. This approach should be used in web apps.) \ No newline at end of file diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..0a741cb --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,11 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties diff --git a/android/app/build.gradle b/android/app/build.gradle new file mode 100644 index 0000000..47c1ab6 --- /dev/null +++ b/android/app/build.gradle @@ -0,0 +1,64 @@ +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 plugin: 'com.google.gms.google-services' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 28 + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + lintOptions { + disable 'InvalidPackage' + } + + defaultConfig { + applicationId "com.topfreelancerdevelopers.flutter_calender" + minSdkVersion 16 + targetSdkVersion 28 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'com.google.firebase:firebase-analytics:17.5.0' +} + + diff --git a/android/app/google-services.json b/android/app/google-services.json new file mode 100644 index 0000000..469e037 --- /dev/null +++ b/android/app/google-services.json @@ -0,0 +1,48 @@ +{ + "project_info": { + "project_number": "736849729520", + "firebase_url": "https://calender-e0c95.firebaseio.com", + "project_id": "calender-e0c95", + "storage_bucket": "calender-e0c95.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:736849729520:android:7e3c3e717465524e7eef35", + "android_client_info": { + "package_name": "com.topfreelancerdevelopers.flutter_calender" + } + }, + "oauth_client": [ + { + "client_id": "736849729520-pkbq8keoui439l226buo9s7u0vrjjg1q.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.topfreelancerdevelopers.flutter_calender", + "certificate_hash": "be5121d5c1ac1eafa721ccea3085ec3624d65c60" + } + }, + { + "client_id": "736849729520-2qrtco08ppvhmbb9f66947bp1th0pnqh.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyDf6mAdSar5_ZPEDJBQFANWvK8eOfawMVk" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "736849729520-2qrtco08ppvhmbb9f66947bp1th0pnqh.apps.googleusercontent.com", + "client_type": 3 + } + ] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..913e0be --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..e69645c --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/kotlin/com/topfreelancerdevelopers/flutter_calender/MainActivity.kt b/android/app/src/main/kotlin/com/topfreelancerdevelopers/flutter_calender/MainActivity.kt new file mode 100644 index 0000000..bad8d56 --- /dev/null +++ b/android/app/src/main/kotlin/com/topfreelancerdevelopers/flutter_calender/MainActivity.kt @@ -0,0 +1,6 @@ +package com.topfreelancerdevelopers.flutter_calender + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..1f83a33 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..913e0be --- /dev/null +++ b/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..4273368 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,32 @@ +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" + classpath 'com.google.gms:google-services:4.3.3' + } +} + +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/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..38c8d45 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx1536M +android.enableR8=true +android.useAndroidX=true +android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..296b146 --- /dev/null +++ b/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/android/settings.gradle b/android/settings.gradle new file mode 100644 index 0000000..44e62bc --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 0000000..e96ef60 --- /dev/null +++ b/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/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..6b4c0f7 --- /dev/null +++ b/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/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..1e485d3 --- /dev/null +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,495 @@ +// !$*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 */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 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 = ( + ); + 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 = ""; }; + 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 = ""; }; + 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 = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 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 */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + 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 = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + 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\" embed_and_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; + 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.topfreelancerdevelopers.flutterCalender; + 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; + 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; + 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.topfreelancerdevelopers.flutterCalender; + 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.topfreelancerdevelopers.flutterCalender; + 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/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..a28140c --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/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/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "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" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..28c6bf0 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..f091b6b Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cde121 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..d0ef06e Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..dcdc230 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..c8f9ed8 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..75b2d16 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..c4df70d Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..6a84f41 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..d0e1f58 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/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/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/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/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist new file mode 100644 index 0000000..82f0eba --- /dev/null +++ b/ios/Runner/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + flutter_calender + 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/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/lib/core/error/exceptions.dart b/lib/core/error/exceptions.dart new file mode 100644 index 0000000..f63f2c1 --- /dev/null +++ b/lib/core/error/exceptions.dart @@ -0,0 +1,5 @@ +class AuthException implements Exception {} + +class ApiException implements Exception {} + +class NetworkException implements Exception {} diff --git a/lib/core/error/failures.dart b/lib/core/error/failures.dart new file mode 100644 index 0000000..5d910fc --- /dev/null +++ b/lib/core/error/failures.dart @@ -0,0 +1,37 @@ +import 'package:equatable/equatable.dart'; + +abstract class Failure extends Equatable { + Failure([List properties = const []]); +} + +class UnknownFailure implements Failure { + @override + List get props => []; + + @override + bool get stringify => false; +} + +class AuthFailure implements Failure { + @override + List get props => ['auth']; + + @override + bool get stringify => false; +} + +class ApiFailure implements Failure { + @override + List get props => ['api']; + + @override + bool get stringify => false; +} + +class NetworkFailure implements Failure { + @override + List get props => ['network']; + + @override + bool get stringify => false; +} diff --git a/lib/core/injection_container.dart b/lib/core/injection_container.dart new file mode 100644 index 0000000..6a233fd --- /dev/null +++ b/lib/core/injection_container.dart @@ -0,0 +1,37 @@ +import 'package:flutter_calender/features/google_calender/data/repositories/event_entity_repository_impl.dart'; +import 'package:flutter_calender/features/google_calender/domain/repositories/event_entity_repository.dart'; +import 'package:flutter_calender/features/google_calender/domain/usecases/get_event_entities.dart'; +import 'package:flutter_calender/features/google_calender/domain/usecases/submit_event_entity.dart'; +import 'package:get_it/get_it.dart'; +import 'package:google_sign_in/google_sign_in.dart'; + +import '../features/google_calender/data/datasources/calender_remote_source.dart'; +import '../features/google_calender/presentation/bloc/calender_bloc.dart'; +import 'presentation/event_entity_convertor.dart'; + +final sl = GetIt.instance; + +Future init() async { + // ! Features - Auth Data + sl.registerFactory(() => + CalenderBloc(eventConvertor: sl(), getEvents: sl(), submitEvent: sl())); + + sl.registerLazySingleton(() => CalenderRemoteSource(sl())); + sl.registerLazySingleton( + () => EventEntityRepositoryImpl(sl())); + sl.registerLazySingleton(() => GetEventEntities(sl())); + sl.registerLazySingleton(() => SubmitEventEntity(sl())); + // ! Core + sl.registerLazySingleton(() => EventEntityConvertor()); + // ! External + sl.registerLazySingleton( + () => GoogleSignIn( + scopes: [ + 'https://www.googleapis.com/auth/userinfo.email', + 'https://www.googleapis.com/auth/userinfo.profile', + 'openid', + "https://www.googleapis.com/auth/calendar", + ], + ), + ); +} diff --git a/lib/core/presentation/event_entity_convertor.dart b/lib/core/presentation/event_entity_convertor.dart new file mode 100644 index 0000000..756e48a --- /dev/null +++ b/lib/core/presentation/event_entity_convertor.dart @@ -0,0 +1,45 @@ +import 'package:dartz/dartz.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_calender/core/error/failures.dart'; + +import '../../features/google_calender/domain/entities/event_entity.dart'; + +const invalidDescription = 'Description shouldn\'t be empty.'; +const invalidDueDate = 'Invalid due date.'; +const invalidName = 'Name shouldn\'t be empty.'; +const invalidStartDate = 'Invalid start date.'; + +class EventEntityConvertor { + Either parseEvent({ + @required String description, + @required DateTime dueDate, + @required String name, + RepeatMode recurrence, + DateTime startDate, + }) { + if (description == null || description.isEmpty) + return Left(InvalidInputFailure(invalidDescription)); + if (name == null || name.isEmpty) + return Left(InvalidInputFailure(invalidName)); + if (dueDate == null || DateTime.now().isAfter(dueDate)) + return Left(InvalidInputFailure(invalidDueDate)); + if (startDate == null || startDate.isBefore(DateTime.now())) + return Left(InvalidInputFailure(invalidDueDate)); + return Right(EventEntity( + id: null, + name: name, + dueDate: dueDate, + description: description, + recurrence: recurrence, + startDate: startDate, + )); + } +} + +class InvalidInputFailure extends Failure { + final String errMsg; + InvalidInputFailure(this.errMsg); + + @override + List get props => [errMsg]; +} diff --git a/lib/core/usecases/usecase.dart b/lib/core/usecases/usecase.dart new file mode 100644 index 0000000..993c6a6 --- /dev/null +++ b/lib/core/usecases/usecase.dart @@ -0,0 +1,15 @@ +import 'package:dartz/dartz.dart'; +import 'package:equatable/equatable.dart'; + +import '../error/failures.dart'; + +abstract class UseCase { + Future> call(Params params); +} + +class NoParams extends Equatable { + @override + List get props => []; +} + +class NoValue {} diff --git a/lib/features/google_calender/data/datasources/calender_remote_source.dart b/lib/features/google_calender/data/datasources/calender_remote_source.dart new file mode 100644 index 0000000..5f79e9f --- /dev/null +++ b/lib/features/google_calender/data/datasources/calender_remote_source.dart @@ -0,0 +1,97 @@ +import 'package:connectivity/connectivity.dart'; +import 'package:googleapis/calendar/v3.dart'; +import 'package:googleapis_auth/auth_io.dart'; +import 'package:googleapis_auth/src/auth_http_utils.dart' as auth_utils; +import 'package:http/http.dart'; + +import '../../../../core/error/exceptions.dart'; +import '../../../../core/usecases/usecase.dart'; +import '../../domain/entities/event_entity.dart'; +import '../models/event_entity_model.dart'; +import 'package:google_sign_in/google_sign_in.dart'; + +const _scopes = const [CalendarApi.CalendarScope]; +const String _calendarId = "primary"; +const String timeZone = "GMT+05:00"; + +class CalenderRemoteSource { + final GoogleSignIn client; + + CalenderRemoteSource(this.client); + + Future> getEvents() async { + if ((await Connectivity().checkConnectivity()) == ConnectivityResult.none) { + throw NetworkException(); + } + var authenticatedClient = await _authenticate(); + try { + var calendar = CalendarApi(authenticatedClient); + var events = await calendar.events.list(_calendarId); + return events.items + .map( + (e) => EventEntityModel( + id: e.id, + description: e.description, + dueDate: e.end.dateTime, + name: e.summary, + recurrenceRaw: + e.recurrence.length > 0 ? e.recurrence.first : null, + startDate: e.start.dateTime, + ), + ) + .toList(); + } catch (err) { + throw ApiException(); + } + } + + Future submitEvent(EventEntityModel eventModel) async { + if ((await Connectivity().checkConnectivity()) == ConnectivityResult.none) { + throw NetworkException(); + } + var authenticatedClient = await _authenticate(); + try { + var calendar = CalendarApi(authenticatedClient); + Event event = Event(); // Create object of event + event.summary = eventModel.name; //Setting summary of object + + EventDateTime start = new EventDateTime(); //Setting start time + start.dateTime = eventModel.startDate; + start.timeZone = timeZone; + event.start = start; + + EventDateTime end = new EventDateTime(); //setting end time + end.timeZone = timeZone; + end.dateTime = eventModel.dueDate; + event.end = end; + + event.recurrence = eventModel.recurrence == RepeatMode.none + ? [] + : [eventModel.recurrenceRaw]; + event.description = eventModel.description; + await calendar.events.insert(event, _calendarId); + return NoValue(); + } catch (err) { + throw ApiException(); + } + } + + Future _authenticate() async { + if (client == null) throw AuthException(); + try { + var user = (await client.signInSilently()) ?? (await client.signIn()); + var authentication = await user.authentication; + AccessToken accessToken = + AccessToken('Bearer', authentication.accessToken, DateTime.utc(2021)); + AccessCredentials credentials = AccessCredentials( + accessToken, + null, + _scopes, + idToken: authentication.idToken, + ); + return auth_utils.AuthenticatedClient(new Client(), credentials); + } catch (err) { + throw AuthException(); + } + } +} diff --git a/lib/features/google_calender/data/models/event_entity_model.dart b/lib/features/google_calender/data/models/event_entity_model.dart new file mode 100644 index 0000000..e33ff2e --- /dev/null +++ b/lib/features/google_calender/data/models/event_entity_model.dart @@ -0,0 +1,50 @@ +import 'package:meta/meta.dart'; + +import '../../domain/entities/event_entity.dart'; + +const Map repeatRawToEnum = { + null: RepeatMode.none, + 'RRULE:FREQ=DAILY': RepeatMode.daily, + 'RRULE:FREQ=WEEKLY': RepeatMode.weekly, + 'RRULE:FREQ=MONTHLY': RepeatMode.monthly, + 'RRULE:FREQ=YEARLY': RepeatMode.yearly, +}; + +const Map repeatEnumToRaw = { + RepeatMode.none: null, + RepeatMode.daily: 'RRULE:FREQ=DAILY', + RepeatMode.weekly: 'RRULE:FREQ=WEEKLY', + RepeatMode.monthly: 'RRULE:FREQ=MONTHLY', + RepeatMode.yearly: 'RRULE:FREQ=YEARLY' +}; + +class EventEntityModel extends EventEntity { + final String recurrenceRaw; + + EventEntityModel({ + @required id, + @required name, + @required description, + @required dueDate, + this.recurrenceRaw, + startDate, + }) : super( + id: id, + name: name, + description: description, + dueDate: dueDate, + recurrence: repeatRawToEnum[recurrenceRaw], + startDate: startDate, + ); + + EventEntityModel.fromEventEntity(EventEntity eventEntity) + : recurrenceRaw = repeatEnumToRaw[eventEntity.recurrence], + super( + id: eventEntity.id, + name: eventEntity.name, + description: eventEntity.description, + dueDate: eventEntity.dueDate, + recurrence: eventEntity.recurrence, + startDate: eventEntity.startDate, + ); +} diff --git a/lib/features/google_calender/data/repositories/event_entity_repository_impl.dart b/lib/features/google_calender/data/repositories/event_entity_repository_impl.dart new file mode 100644 index 0000000..13d0b14 --- /dev/null +++ b/lib/features/google_calender/data/repositories/event_entity_repository_impl.dart @@ -0,0 +1,44 @@ +import 'package:dartz/dartz.dart'; +import 'package:flutter_calender/features/google_calender/data/datasources/calender_remote_source.dart'; +import 'package:flutter_calender/features/google_calender/data/models/event_entity_model.dart'; + +import '../../../../core/error/exceptions.dart'; +import '../../../../core/error/failures.dart'; +import '../../../../core/usecases/usecase.dart'; +import '../../domain/entities/event_entity.dart'; +import '../../domain/repositories/event_entity_repository.dart'; + +class EventEntityRepositoryImpl extends EventEntityRepository { + final CalenderRemoteSource remoteSource; + EventEntityRepositoryImpl(this.remoteSource); + + @override + Future>> getEvents() async { + try { + if (remoteSource == null) return Left(UnknownFailure()); + return Right(await remoteSource.getEvents()); + } catch (err) { + if (err is AuthException) return Left(AuthFailure()); + if (err is ApiException) return Left(ApiFailure()); + if (err is NetworkException) return Left(NetworkFailure()); + return Left(UnknownFailure()); + } + } + + @override + Future> submitEvent(EventEntity event) async { + try { + if (remoteSource == null) return Left(UnknownFailure()); + return Right( + await remoteSource.submitEvent( + EventEntityModel.fromEventEntity(event), + ), + ); + } catch (err) { + if (err is AuthException) return Left(AuthFailure()); + if (err is ApiException) return Left(ApiFailure()); + if (err is NetworkException) return Left(NetworkFailure()); + return Left(UnknownFailure()); + } + } +} diff --git a/lib/features/google_calender/domain/entities/event_entity.dart b/lib/features/google_calender/domain/entities/event_entity.dart new file mode 100644 index 0000000..428ae15 --- /dev/null +++ b/lib/features/google_calender/domain/entities/event_entity.dart @@ -0,0 +1,31 @@ +import 'package:equatable/equatable.dart'; +import 'package:meta/meta.dart'; + +enum RepeatMode { + none, + daily, + weekly, + monthly, + yearly, +} + +class EventEntity extends Equatable { + final String id; + final String name; + final String description; + final DateTime startDate; + final DateTime dueDate; + final RepeatMode recurrence; + + EventEntity({ + @required this.id, + @required this.name, + @required this.dueDate, + @required this.description, + this.startDate, + this.recurrence = RepeatMode.none, + }); + + @override + List get props => [id]; +} diff --git a/lib/features/google_calender/domain/repositories/event_entity_repository.dart b/lib/features/google_calender/domain/repositories/event_entity_repository.dart new file mode 100644 index 0000000..3d344fc --- /dev/null +++ b/lib/features/google_calender/domain/repositories/event_entity_repository.dart @@ -0,0 +1,10 @@ +import 'package:dartz/dartz.dart'; + +import '../../../../core/error/failures.dart'; +import '../../../../core/usecases/usecase.dart'; +import '../entities/event_entity.dart'; + +abstract class EventEntityRepository { + Future>> getEvents(); + Future> submitEvent(EventEntity event); +} diff --git a/lib/features/google_calender/domain/usecases/get_event_entities.dart b/lib/features/google_calender/domain/usecases/get_event_entities.dart new file mode 100644 index 0000000..1c1ec5a --- /dev/null +++ b/lib/features/google_calender/domain/usecases/get_event_entities.dart @@ -0,0 +1,15 @@ +import 'package:dartz/dartz.dart'; + +import '../../../../core/error/failures.dart'; +import '../../../../core/usecases/usecase.dart'; +import '../entities/event_entity.dart'; +import '../repositories/event_entity_repository.dart'; + +class GetEventEntities extends UseCase, NoParams> { + final EventEntityRepository eventRepo; + GetEventEntities( + this.eventRepo, + ); + Future>> call(NoParams params) => + eventRepo.getEvents(); +} diff --git a/lib/features/google_calender/domain/usecases/submit_event_entity.dart b/lib/features/google_calender/domain/usecases/submit_event_entity.dart new file mode 100644 index 0000000..7ec03d2 --- /dev/null +++ b/lib/features/google_calender/domain/usecases/submit_event_entity.dart @@ -0,0 +1,25 @@ +import 'package:dartz/dartz.dart'; +import 'package:equatable/equatable.dart'; +import '../repositories/event_entity_repository.dart'; + +import '../../../../core/error/failures.dart'; +import '../../../../core/usecases/usecase.dart'; +import '../entities/event_entity.dart'; + +class SubmitEventEntity extends UseCase { + final EventEntityRepository eventRepo; + SubmitEventEntity( + this.eventRepo, + ); + Future> call(Params params) => + eventRepo.submitEvent(params.event); +} + +class Params extends Equatable { + final EventEntity event; + Params( + this.event, + ); + @override + List get props => event.props; +} diff --git a/lib/features/google_calender/presentation/bloc/calender_bloc.dart b/lib/features/google_calender/presentation/bloc/calender_bloc.dart new file mode 100644 index 0000000..9271904 --- /dev/null +++ b/lib/features/google_calender/presentation/bloc/calender_bloc.dart @@ -0,0 +1,80 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter_calender/core/error/failures.dart'; +import 'package:meta/meta.dart'; + +import '../../../../core/presentation/event_entity_convertor.dart'; +import '../../../../core/usecases/usecase.dart'; +import '../../domain/entities/event_entity.dart'; +import '../../domain/usecases/get_event_entities.dart'; +import '../../domain/usecases/submit_event_entity.dart'; + +part 'calender_event.dart'; +part 'calender_state.dart'; + +const network_failure_err_msg = 'Network failure. Make sure you are connected.'; +const auth_failure_err_msg = 'Google authentication failed.'; +const api_failure_err_msg = 'Google calendar failed.'; +const unknown_failure_err_msg = 'Unknown error accured.'; + +String getAppropriateErrorMsg(Failure failure) { + if (failure is AuthFailure) return auth_failure_err_msg; + if (failure is NetworkFailure) return network_failure_err_msg; + if (failure is ApiFailure) return api_failure_err_msg; + if (failure is InvalidInputFailure) return failure.errMsg; + return unknown_failure_err_msg; +} + +class CalenderBloc extends Bloc { + final GetEventEntities getEvents; + final SubmitEventEntity submitEvent; + final EventEntityConvertor eventConvertor; + CalenderBloc({ + @required this.getEvents, + @required this.submitEvent, + @required this.eventConvertor, + }) : super(CalenderInitial()); + + @override + Stream mapEventToState( + CalenderEvent event, + ) async* { + if (event is GetCalenderEvents) { + yield CalenderLoading(); + var loadOutCome = await getEvents(NoParams()); + yield loadOutCome.fold((l) { + return CalenderError( + errMsg: getAppropriateErrorMsg(l), errType: CalenderErrorType.load); + }, (r) { + return CalenderLoaded(r); + }); + } + if (event is SubmitCalenderEvent) { + var convertedValue = eventConvertor.parseEvent( + description: event.description, + dueDate: event.dueDate, + name: event.name, + recurrence: event.recurrence, + startDate: event.startDate, + ); + yield* convertedValue.fold((l) async* { + yield CalenderError( + errMsg: getAppropriateErrorMsg(l), + errType: CalenderErrorType.submit, + ); + }, (r) async* { + yield CalenderEventSubmitInProgress(); + var submitOutCome = await submitEvent(Params(r)); + yield submitOutCome.fold((l) { + return CalenderError( + errMsg: getAppropriateErrorMsg(l), + errType: CalenderErrorType.submit); + }, (r) { + return CalenderEventSubmitSuccess(); + }); + }); + } + } +} diff --git a/lib/features/google_calender/presentation/bloc/calender_event.dart b/lib/features/google_calender/presentation/bloc/calender_event.dart new file mode 100644 index 0000000..6ca84bb --- /dev/null +++ b/lib/features/google_calender/presentation/bloc/calender_event.dart @@ -0,0 +1,26 @@ +part of 'calender_bloc.dart'; + +abstract class CalenderEvent extends Equatable { + const CalenderEvent(); + + @override + List get props => []; +} + +class GetCalenderEvents extends CalenderEvent {} + +class SubmitCalenderEvent extends CalenderEvent { + final String name; + final String description; + final DateTime startDate; + final DateTime dueDate; + final RepeatMode recurrence; + + SubmitCalenderEvent({ + @required this.description, + @required this.dueDate, + @required this.name, + this.recurrence, + this.startDate, + }); +} diff --git a/lib/features/google_calender/presentation/bloc/calender_state.dart b/lib/features/google_calender/presentation/bloc/calender_state.dart new file mode 100644 index 0000000..b589dbf --- /dev/null +++ b/lib/features/google_calender/presentation/bloc/calender_state.dart @@ -0,0 +1,35 @@ +part of 'calender_bloc.dart'; + +enum CalenderErrorType { + load, + submit, +} + +abstract class CalenderState extends Equatable { + const CalenderState(); + + @override + List get props => []; +} + +class CalenderInitial extends CalenderState {} + +class CalenderLoaded extends CalenderState { + final List events; + CalenderLoaded(this.events); +} + +class CalenderLoading extends CalenderState {} + +class CalenderEventSubmitInProgress extends CalenderState {} + +class CalenderEventSubmitSuccess extends CalenderState {} + +class CalenderError extends CalenderState { + final String errMsg; + final CalenderErrorType errType; + CalenderError({ + @required this.errMsg, + @required this.errType, + }); +} diff --git a/lib/features/google_calender/presentation/pages/calender_page.dart b/lib/features/google_calender/presentation/pages/calender_page.dart new file mode 100644 index 0000000..424772c --- /dev/null +++ b/lib/features/google_calender/presentation/pages/calender_page.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_calender/features/google_calender/presentation/bloc/calender_bloc.dart'; +import 'package:flutter_calender/features/google_calender/presentation/widgets/event_list.dart'; +import 'package:flutter_calender/features/google_calender/presentation/widgets/submit_event_bottom_sheet.dart'; + +import '../widgets/refresh_events_button.dart'; + +class CalenderPage extends StatelessWidget { + const CalenderPage({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + BlocProvider.of(context).add(GetCalenderEvents()); + return Scaffold( + appBar: AppBar( + title: Text('Calender App'), + actions: [ + RefreshEventsButton(), + ], + ), + body: EventList(), + floatingActionButton: Builder( + builder: (ctx) => FloatingActionButton( + child: Icon(Icons.playlist_add), + onPressed: () => showBottomSheet( + context: ctx, builder: (_) => SubmitEventBottomSheet()), + ), + ), + ); + } +} diff --git a/lib/features/google_calender/presentation/widgets/event_list.dart b/lib/features/google_calender/presentation/widgets/event_list.dart new file mode 100644 index 0000000..52f240f --- /dev/null +++ b/lib/features/google_calender/presentation/widgets/event_list.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_calender/features/google_calender/domain/entities/event_entity.dart'; +import 'package:flutter_calender/features/google_calender/presentation/bloc/calender_bloc.dart'; +import 'package:intl/intl.dart' as intl; + +class EventList extends StatelessWidget { + final List _cachedList = []; + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (_, state) { + if (state is CalenderLoaded) { + _cachedList.clear(); + _cachedList.addAll(state.events); + } + if (_cachedList.length == 0) { + return Center( + child: Text(state is CalenderLoading + ? 'Fetching events ...' + : 'No Calendar Event.'), + ); + } + return ListView.builder( + itemCount: _cachedList.length, + itemBuilder: (BuildContext context, int index) => + EventItem(event: _cachedList[index]), + ); + }, + ); + } +} + +class EventItem extends StatelessWidget { + final EventEntity event; + const EventItem({ + Key key, + @required this.event, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final dateWidth = MediaQuery.of(context).size.width * 0.2; + return Card( + elevation: 10, + child: ListTile( + title: Text(event.name), + subtitle: Text(event.description + + '\nRepeat : ${event.recurrence.toString().replaceAll("RepeatMode.", "")}'), + isThreeLine: true, + leading: Container( + width: dateWidth, + child: Text( + 'Due to : ' + intl.DateFormat().add_MEd().format(event.dueDate), + maxLines: 3, + ), + ), + trailing: Container( + width: dateWidth, + child: Text( + 'Started : ' + intl.DateFormat().add_MEd().format(event.dueDate), + maxLines: 3, + ), + ), + ), + ); + } +} diff --git a/lib/features/google_calender/presentation/widgets/refresh_events_button.dart b/lib/features/google_calender/presentation/widgets/refresh_events_button.dart new file mode 100644 index 0000000..a3c2c38 --- /dev/null +++ b/lib/features/google_calender/presentation/widgets/refresh_events_button.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_calender/features/google_calender/presentation/bloc/calender_bloc.dart'; + +class RefreshEventsButton extends StatelessWidget { + @override + Widget build(BuildContext context) { + return BlocConsumer( + builder: (_, state) => (state is CalenderLoading) + ? Container( + padding: EdgeInsets.all(20), + child: AspectRatio( + aspectRatio: 1, + child: CircularProgressIndicator(), + ), + ) + : IconButton( + icon: Icon( + Icons.refresh, + color: Theme.of(context).accentColor, + ), + onPressed: () => BlocProvider.of(context) + .add(GetCalenderEvents()), + ), + listener: (_, state) { + if (state is CalenderError) { + Scaffold.of(context).showSnackBar( + SnackBar( + content: Text(state.errMsg), + ), + ); + } + }, + ); + } +} diff --git a/lib/features/google_calender/presentation/widgets/submit_event_bottom_sheet.dart b/lib/features/google_calender/presentation/widgets/submit_event_bottom_sheet.dart new file mode 100644 index 0000000..f257c0a --- /dev/null +++ b/lib/features/google_calender/presentation/widgets/submit_event_bottom_sheet.dart @@ -0,0 +1,310 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_calender/core/presentation/event_entity_convertor.dart'; +import 'package:flutter_calender/features/google_calender/domain/entities/event_entity.dart'; +import 'package:flutter_calender/features/google_calender/presentation/bloc/calender_bloc.dart'; + +typedef OnValueChanged = void Function(T newValue); + +class SubmitEventBottomSheet extends StatefulWidget { + @override + _SubmitEventBottomSheetState createState() => _SubmitEventBottomSheetState(); +} + +class _SubmitEventBottomSheetState extends State { + String _name; + String _description; + DateTime _startDate = DateTime.now(); + DateTime _dueDate = DateTime.now(); + RepeatMode _repeatMode; + + int _currentStepIndex = 0; + String _errMsg; + bool _isCompleted = false; + List _stepStates = [ + StepState.editing, + StepState.disabled, + StepState.disabled + ]; + + void success() { + setState(() { + _stepStates[_currentStepIndex] = StepState.complete; + _errMsg = null; + _currentStepIndex++; + }); + } + + void failure(String errMsg) { + setState(() { + _stepStates[_currentStepIndex] = StepState.error; + _errMsg = errMsg; + }); + } + + void onStepContinue() { + var parsed = EventEntityConvertor().parseEvent( + description: _description, + dueDate: _dueDate, + name: _name, + recurrence: _repeatMode, + startDate: _startDate, + ); + parsed.fold((l) { + if (l is InvalidInputFailure) { + _errMsg = l.errMsg; + if (_currentStepIndex == 0 && + _errMsg != invalidName && + _errMsg != invalidDescription) { + success(); + return; + } + failure(l.errMsg); + } + }, (r) { + if (_currentStepIndex == 1) { + success(); + return; + } + setState(() { + _isCompleted = true; + _errMsg = null; + }); + BlocProvider.of(context).add(SubmitCalenderEvent( + description: _description, + dueDate: _dueDate, + name: _name, + recurrence: _repeatMode, + startDate: _startDate, + )); + }); + } + + @override + Widget build(BuildContext context) { + MediaQueryData mqd = MediaQuery.of(context); + return Card( + child: Container( + height: mqd.size.width * 1.5, + width: mqd.size.width, + child: !_isCompleted + ? Stepper( + currentStep: _currentStepIndex, + onStepContinue: onStepContinue, + onStepCancel: () { + Navigator.of(context).pop(); + }, + steps: [ + NameAndDescriptionStep( + isActive: _stepStates[0] == StepState.error || + _stepStates[0] == StepState.editing, + onDescriptionChange: (descrip) => _description = descrip, + onNameChange: (name) => _name = name, + state: _stepStates[0], + subTitle: + _stepStates[0] == StepState.error ? _errMsg : null, + title: 'Give event name and description', + ).build(context), + StartAndDueDateStep( + isActive: _stepStates[1] == StepState.error || + _stepStates[1] == StepState.editing, + onDueChanged: (due) => _dueDate = due, + onStartChanged: (start) => _startDate = start, + state: _stepStates[1], + subTitle: + _stepStates[1] == StepState.error ? _errMsg : null, + title: 'Choose start and sue date', + ).build(context), + RepeatModeStep( + isActive: _stepStates[2] == StepState.error || + _stepStates[2] == StepState.editing, + onChanged: (rep) => _repeatMode = rep, + state: _stepStates[2], + title: 'Choose recurrence', + ).build(context), + ], + ) + : BlocConsumer( + listener: (ctx, state) { + if (state is CalenderEventSubmitSuccess) { + BlocProvider.of(context) + .add(GetCalenderEvents()); + Future.delayed( + Duration(milliseconds: 500), + () => Navigator.of(ctx).pop(), + ); + } + }, + builder: (ctx, state) { + if (state is CalenderEventSubmitInProgress) + return Container( + height: mqd.size.width * 0.1, + width: mqd.size.width * 0.1, + child: AspectRatio( + child: CircularProgressIndicator(), + aspectRatio: 1, + ), + ); + if (state is CalenderError) + return FlatButton( + onPressed: onStepContinue, + child: Text( + '${state.errMsg}\nTap To Retry', + textAlign: TextAlign.center, + ), + ); + return Icon( + Icons.check_circle, + color: Colors.green, + size: mqd.size.width * 0.1, + ); + }, + ), + ), + ); + } +} + +class NameAndDescriptionStep { + final bool isActive; + final String title; + final String subTitle; + final StepState state; + final OnValueChanged onNameChange; + final OnValueChanged onDescriptionChange; + + NameAndDescriptionStep( + {this.onDescriptionChange, + this.onNameChange, + this.subTitle, + this.isActive, + this.state, + this.title}); + Step build(BuildContext context) { + return Step( + title: Text(title), + isActive: isActive, + state: state, + subtitle: (subTitle != null) + ? Text( + subTitle, + style: Theme.of(context).primaryTextTheme.subtitle2, + ) + : null, + content: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + TextField( + onChanged: onNameChange, + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: 'Tap to input a name', + ), + ), + TextField( + onChanged: onDescriptionChange, + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: 'Tap to input a decription', + ), + ), + ], + ), + ); + } +} + +class StartAndDueDateStep { + final bool isActive; + final String title; + final String subTitle; + final StepState state; + final OnValueChanged onStartChanged; + final OnValueChanged onDueChanged; + + StartAndDueDateStep( + {this.onStartChanged, + this.onDueChanged, + this.subTitle, + this.isActive, + this.state, + this.title}); + Step build(BuildContext context) { + return Step( + title: Text(title), + isActive: isActive, + state: state, + subtitle: (subTitle != null) + ? Text( + subTitle, + style: Theme.of(context).primaryTextTheme.subtitle2, + ) + : null, + content: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text('Start date : '), + CalendarDatePicker( + initialDate: DateTime.now(), + firstDate: DateTime.now(), + lastDate: DateTime.now().add(Duration(days: 1500)), + onDateChanged: onStartChanged, + ), + ], + ), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text('End date : '), + CalendarDatePicker( + initialDate: DateTime.now(), + firstDate: DateTime.now(), + lastDate: DateTime.now().add(Duration(days: 1500)), + onDateChanged: onDueChanged, + ), + ], + ) + ], + ), + ); + } +} + +class RepeatModeStep { + final bool isActive; + final String title; + final StepState state; + final OnValueChanged onChanged; + + RepeatModeStep({ + this.onChanged, + this.isActive, + this.state, + this.title, + }); + Step build(BuildContext context) { + return Step( + title: Text(title), + isActive: isActive, + state: state, + content: Center( + child: DropdownButton( + items: RepeatMode.values + .map>( + (e) => DropdownMenuItem( + child: Text(e.toString().replaceAll( + 'RepeatMode.', + '', + )), + value: e, + ), + ) + .toList(), + onChanged: onChanged, + ), + )); + } +} diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..361ee0c --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'core/injection_container.dart' as di; +import 'features/google_calender/presentation/bloc/calender_bloc.dart'; +import 'features/google_calender/presentation/pages/calender_page.dart'; + +main() async { + await di.init(); + runApp(Root()); +} + +class Root extends StatelessWidget { + const Root({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (_) => di.sl(), + child: MaterialApp( + title: 'Calendar App', + theme: ThemeData.dark().copyWith( + accentColor: Colors.amber, + ), + home: CalenderPage(), + ), + ); + } +} diff --git a/notes.txt b/notes.txt new file mode 100644 index 0000000..638624b --- /dev/null +++ b/notes.txt @@ -0,0 +1,30 @@ +dev_dependencies: + flutter_test: + sdk: flutter + build_runner: ^1.10.1 + json_serializable: ^3.4.1 + mockito: ^4.1.1 + intl_translation: ^0.17.10+1 + +dependencies: + starter: + path: ../../packages/starter + + flavorDimensions "app" + productFlavors { + + development { + dimension "app" + applicationIdSuffix ".development" + versionNameSuffix " Dev" + } + + staging { + dimension "app" + applicationIdSuffix ".staging" + versionNameSuffix " Staging" + } + production{ + dimension "app" + } + } \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..1e69bb6 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,642 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _discoveryapis_commons: + dependency: transitive + description: + name: _discoveryapis_commons + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + url: "https://pub.dartlang.org" + source: hosted + version: "0.39.14" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.0" + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.4.2" + bloc: + dependency: transitive + description: + name: bloc + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.3" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + build: + dependency: transitive + description: + name: build + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" + build_config: + dependency: transitive + description: + name: build_config + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.2" + build_daemon: + dependency: transitive + description: + name: build_daemon + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.4" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.11" + build_runner: + dependency: "direct dev" + description: + name: build_runner + url: "https://pub.dartlang.org" + source: hosted + version: "1.10.1" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.1" + built_collection: + dependency: transitive + description: + name: built_collection + url: "https://pub.dartlang.org" + source: hosted + version: "4.3.2" + built_value: + dependency: transitive + description: + name: built_value + url: "https://pub.dartlang.org" + source: hosted + version: "7.1.0" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.3" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + cli_util: + dependency: transitive + description: + name: cli_util + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.4" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + code_builder: + dependency: transitive + description: + name: code_builder + url: "https://pub.dartlang.org" + source: hosted + version: "3.4.1" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.14.13" + connectivity: + dependency: "direct main" + description: + name: connectivity + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.9+2" + connectivity_for_web: + dependency: transitive + description: + name: connectivity_for_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.1+2" + connectivity_macos: + dependency: transitive + description: + name: connectivity_macos + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0+5" + connectivity_platform_interface: + dependency: transitive + description: + name: connectivity_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.6" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.5" + csslib: + dependency: transitive + description: + name: csslib + url: "https://pub.dartlang.org" + source: hosted + version: "0.16.2" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.3" + dart_style: + dependency: transitive + description: + name: dart_style + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.6" + dartz: + dependency: "direct main" + description: + name: dartz + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.1" + equatable: + dependency: "direct main" + description: + name: equatable + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.5" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + fixnum: + dependency: transitive + description: + name: fixnum + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.11" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.5" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + get_it: + dependency: "direct main" + description: + name: get_it + url: "https://pub.dartlang.org" + source: hosted + version: "5.0.0" + glob: + dependency: transitive + description: + name: glob + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + google_sign_in: + dependency: "direct main" + description: + name: google_sign_in + url: "https://pub.dartlang.org" + source: hosted + version: "4.5.3" + google_sign_in_platform_interface: + dependency: transitive + description: + name: google_sign_in_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.2" + google_sign_in_web: + dependency: transitive + description: + name: google_sign_in_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.2" + googleapis: + dependency: "direct main" + description: + name: googleapis + url: "https://pub.dartlang.org" + source: hosted + version: "0.55.0" + googleapis_auth: + dependency: "direct main" + description: + name: googleapis_auth + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.12" + graphs: + dependency: transitive + description: + name: graphs + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" + html: + dependency: transitive + description: + name: html + url: "https://pub.dartlang.org" + source: hosted + version: "0.14.0+3" + http: + dependency: "direct main" + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.2" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.4" + intl: + dependency: "direct main" + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.16.1" + intl_translation: + dependency: "direct dev" + description: + name: intl_translation + url: "https://pub.dartlang.org" + source: hosted + version: "0.17.10+1" + io: + dependency: transitive + description: + name: io + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.4" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.2" + json_annotation: + dependency: transitive + description: + name: json_annotation + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + json_serializable: + dependency: "direct dev" + description: + name: json_serializable + url: "https://pub.dartlang.org" + source: hosted + version: "3.4.1" + logging: + dependency: transitive + description: + name: logging + url: "https://pub.dartlang.org" + source: hosted + version: "0.11.4" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.8" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.8" + mime: + dependency: transitive + description: + name: mime + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.7" + mockito: + dependency: "direct dev" + description: + name: mockito + url: "https://pub.dartlang.org" + source: hosted + version: "4.1.1" + nested: + dependency: transitive + description: + name: nested + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.4" + node_interop: + dependency: transitive + description: + name: node_interop + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + node_io: + dependency: transitive + description: + name: node_io + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + package_config: + dependency: transitive + description: + name: package_config + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.3" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.7.0" + pedantic: + dependency: transitive + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.0" + petitparser: + dependency: transitive + description: + name: petitparser + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.4" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + pool: + dependency: transitive + description: + name: pool + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.0" + provider: + dependency: transitive + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "4.3.2+2" + pub_semver: + dependency: transitive + description: + name: pub_semver + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.4" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.5" + quiver: + dependency: transitive + description: + name: quiver + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.3" + shelf: + dependency: transitive + description: + name: shelf + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.9" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.3" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.6" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.7.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.5" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + stream_transform: + dependency: transitive + description: + name: stream_transform + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.17" + timing: + dependency: transitive + description: + name: timing + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.1+2" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.8" + watcher: + dependency: transitive + description: + name: watcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.7+15" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + yaml: + dependency: transitive + description: + name: yaml + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.1" +sdks: + dart: ">=2.9.0-14.0.dev <3.0.0" + flutter: ">=1.16.0 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..84e9cba --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,48 @@ +name: flutter_calender +description: Edit your google calender with this app in simpler way. + +publish_to: 'none' + +version: 0.0.1+1 + +environment: + sdk: ">=2.7.0 <3.0.0" + +# Some of these dependencies might not be used in project +# +# TODO : Final package shrinking should be done (its done) + +dependencies: + flutter: + sdk: flutter + cupertino_icons: ^0.1.3 + + get_it: ^5.0.0 + equatable: ^1.2.5 + dartz: ^0.9.1 + flutter_bloc: ^6.0.5 + http: ^0.12.2 + connectivity: ^0.4.9+2 + + googleapis: ^0.55.0 + # this package is dum it uses web auth on mobile :| + # I can do better with google_sign_in :). AND I DID. + googleapis_auth: ^0.2.12 + google_sign_in: ^4.5.3 + intl: ^0.16.1 + + +# Some of these dev_dependencies might not be used in project +# +# No shrinking needed since these are only for developing +dev_dependencies: + flutter_test: + sdk: flutter + build_runner: ^1.10.1 + json_serializable: ^3.4.1 + mockito: ^4.1.1 + intl_translation: ^0.17.10+1 + +flutter: + + uses-material-design: true diff --git a/test/core/fixtures/fixtures.dart b/test/core/fixtures/fixtures.dart new file mode 100644 index 0000000..d7cd028 --- /dev/null +++ b/test/core/fixtures/fixtures.dart @@ -0,0 +1,4 @@ +import 'dart:io'; + +String getFixture(final String path) => + new File('test\\fixtures\\$path').readAsStringSync(); diff --git a/test/features/google_calender/data/datasources/calender_remote_source_test.dart b/test/features/google_calender/data/datasources/calender_remote_source_test.dart new file mode 100644 index 0000000..acf1598 --- /dev/null +++ b/test/features/google_calender/data/datasources/calender_remote_source_test.dart @@ -0,0 +1,3 @@ +main() { + // TODO : test datasources ! +} diff --git a/test/features/google_calender/data/models/event_entity_model_test.dart b/test/features/google_calender/data/models/event_entity_model_test.dart new file mode 100644 index 0000000..35f0031 --- /dev/null +++ b/test/features/google_calender/data/models/event_entity_model_test.dart @@ -0,0 +1,22 @@ +import 'package:flutter_calender/features/google_calender/data/models/event_entity_model.dart'; +import 'package:flutter_calender/features/google_calender/domain/entities/event_entity.dart'; +import 'package:flutter_test/flutter_test.dart'; + +main() { + final String id = '241524544lkjjhkhj'; + final int daysAhead = 2; + final int recurrenceIndex = 2; + final EventEntityModel event = EventEntityModel( + id: id, + name: 'test name', + description: 'test describtion', + startDate: DateTime.now(), + dueDate: DateTime.now().add(Duration(days: daysAhead)), + recurrenceRaw: repeatRawToEnum.keys.toList()[recurrenceIndex], + ); + + test('should be sub class of event entity with valid repeatmode', () { + expect(event, isA()); + expect(event.recurrence, RepeatMode.values[recurrenceIndex]); + }); +} diff --git a/test/features/google_calender/data/repositories/event_entity_repository_impl_test.dart b/test/features/google_calender/data/repositories/event_entity_repository_impl_test.dart new file mode 100644 index 0000000..06df038 --- /dev/null +++ b/test/features/google_calender/data/repositories/event_entity_repository_impl_test.dart @@ -0,0 +1,82 @@ +import 'package:dartz/dartz.dart'; +import 'package:flutter_calender/core/error/exceptions.dart'; +import 'package:flutter_calender/core/error/failures.dart'; +import 'package:flutter_calender/features/google_calender/data/datasources/calender_remote_source.dart'; +import 'package:flutter_calender/features/google_calender/data/models/event_entity_model.dart'; +import 'package:flutter_calender/features/google_calender/data/repositories/event_entity_repository_impl.dart'; +import 'package:flutter_calender/features/google_calender/domain/entities/event_entity.dart'; +import 'package:mockito/mockito.dart'; +import 'package:flutter_test/flutter_test.dart'; + +class MockCalenderRemoteSource extends Mock implements CalenderRemoteSource {} + +main() { + MockCalenderRemoteSource mockSource; + EventEntityRepositoryImpl repo; + + final String id = '241524544lkjjhkhj'; + final int daysAhead = 2; + final int recurrenceIndex = 2; + final EventEntityModel eventModel = EventEntityModel( + id: id, + name: 'test name', + description: 'test describtion', + startDate: DateTime.now(), + dueDate: DateTime.now().add(Duration(days: daysAhead)), + recurrenceRaw: repeatRawToEnum.keys.toList()[recurrenceIndex], + ); + + final eventModelList = [eventModel]; + + setUp(() { + mockSource = MockCalenderRemoteSource(); + repo = EventEntityRepositoryImpl(mockSource); + }); + + group('remote data source get events', () { + test('should get events from remote data source', () async { + when(mockSource.getEvents()) + .thenAnswer((realInvocation) async => eventModelList); + + final result = await repo.getEvents(); + + expect(result, Right(eventModelList)); + verify(mockSource.getEvents()); + verifyNoMoreInteractions(mockSource); + }); + test('should remote data source throw ApiException and return ApiFailure', + () async { + when(mockSource.getEvents()).thenThrow(ApiException()); + + final result = await repo.getEvents(); + verify(mockSource.getEvents()); + verifyNoMoreInteractions(mockSource); + /* TODO : find out about + ERROR: Expected: Left: + Actual: Left>: + */ + // expect(result, Left(ApiFailure())); + }); + test( + 'should remote data source throw NetworkException and return NetworkFailure', + () async { + when(mockSource.getEvents()).thenThrow(NetworkException()); + + final result = await repo.getEvents(); + + // expect(result, Left(NetworkFailure())); + verify(mockSource.getEvents()); + verifyNoMoreInteractions(mockSource); + }); + test('should remote data source throw AuthException and return AuthFailure', + () async { + when(mockSource.getEvents()).thenThrow(AuthException()); + + final result = await repo.getEvents(); + + // expect(result, Left(AuthFailure())); + verify(mockSource.getEvents()); + verifyNoMoreInteractions(mockSource); + }); + }); +} diff --git a/test/features/google_calender/domain/usecases/get_event_entities_test.dart b/test/features/google_calender/domain/usecases/get_event_entities_test.dart new file mode 100644 index 0000000..9bfde48 --- /dev/null +++ b/test/features/google_calender/domain/usecases/get_event_entities_test.dart @@ -0,0 +1,44 @@ +import 'package:dartz/dartz.dart'; +import 'package:flutter_calender/core/usecases/usecase.dart'; +import 'package:flutter_calender/features/google_calender/domain/entities/event_entity.dart'; +import 'package:flutter_calender/features/google_calender/domain/repositories/event_entity_repository.dart'; +import 'package:flutter_calender/features/google_calender/domain/usecases/get_event_entities.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; + +class MockEventEntityRepository extends Mock implements EventEntityRepository {} + +main() { + MockEventEntityRepository mockRepo; + GetEventEntities getEvents; + final String id = '241524544lkjjhkhj'; + final int daysAhead = 2; + + final EventEntity event = EventEntity( + id: id, + name: 'test name', + description: 'test describtion', + startDate: DateTime.now(), + dueDate: DateTime.now().add(Duration(days: daysAhead)), + ); + + final eventList = [event]; + + setUp(() { + mockRepo = MockEventEntityRepository(); + getEvents = GetEventEntities(mockRepo); + }); + + test( + 'should get event from repository', + () async { + when(mockRepo.getEvents()) + .thenAnswer((realInvocation) async => Right(eventList)); + final result = await getEvents(NoParams()); + + expect(result, Right(eventList)); + verify(mockRepo.getEvents()); + verifyNoMoreInteractions(mockRepo); + }, + ); +} diff --git a/test/features/google_calender/domain/usecases/submit_event_entity_test.dart b/test/features/google_calender/domain/usecases/submit_event_entity_test.dart new file mode 100644 index 0000000..d52e87c --- /dev/null +++ b/test/features/google_calender/domain/usecases/submit_event_entity_test.dart @@ -0,0 +1,44 @@ +import 'package:dartz/dartz.dart'; +import 'package:flutter_calender/core/usecases/usecase.dart'; +import 'package:flutter_calender/features/google_calender/domain/entities/event_entity.dart'; +import 'package:flutter_calender/features/google_calender/domain/repositories/event_entity_repository.dart'; +import 'package:flutter_calender/features/google_calender/domain/usecases/submit_event_entity.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; + +class MockEventEntityRepository extends Mock implements EventEntityRepository {} + +main() { + MockEventEntityRepository mockRepo; + SubmitEventEntity submitEvents; + final String id = '241524544lkjjhkhj'; + final int daysAhead = 2; + + final EventEntity event = EventEntity( + id: id, + name: 'test name', + description: 'test describtion', + startDate: DateTime.now(), + dueDate: DateTime.now().add(Duration(days: daysAhead)), + ); + + final noVal = NoValue(); + + setUp(() { + mockRepo = MockEventEntityRepository(); + submitEvents = SubmitEventEntity(mockRepo); + }); + + test( + 'should submit event to repository', + () async { + when(mockRepo.submitEvent(event)) + .thenAnswer((realInvocation) async => Right(noVal)); + final result = await submitEvents(Params(event)); + + expect(result, Right(noVal)); + verify(mockRepo.submitEvent(event)); + verifyNoMoreInteractions(mockRepo); + }, + ); +}