Repository: fanton-dev/DRUN (language: python)

Issue:
[Feat] Order feature
**Describe the feature you'd like**
A clear and concise description of what you want to happen.

**Additional context**
Add any other context or screenshots about the feature request here.

[Feat] Home feature
**Describe the feature you'd like**
A clear and concise description of what you want to happen.

**Additional context**
Add any other context or screenshots about the feature request here.

[Feat] Home feature
**Describe the feature you'd like**
A clear and concise description of what you want to happen.

**Additional context**
Add any other context or screenshots about the feature request here.



Discussion / hints:

Fix patch:
diff --git a/app/.metadata b/app/.metadata
index ee5762c..cd984dd 100644
--- a/app/.metadata
+++ b/app/.metadata
@@ -4,7 +4,7 @@
 # This file should be version controlled and should not be manually edited.
 
 version:
-  revision: d408d302e22179d598f467e11da5dd968dbdc9ec
+  revision: 9b2d32b605630f28625709ebd9d78ab3016b2bf6
   channel: stable
 
 project_type: app
diff --git a/app/.vscode/settings.json b/app/.vscode/settings.json
deleted file mode 100644
index 6cfba07..0000000
--- a/app/.vscode/settings.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
-    "cSpell.words": [
-        "cupertino"
-    ]
-}
\ No newline at end of file
diff --git a/app/android/app/build.gradle b/app/android/app/build.gradle
index 2909037..bb93cc1 100644
--- a/app/android/app/build.gradle
+++ b/app/android/app/build.gradle
@@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
 apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
 
 android {
-    compileSdkVersion 28
+    compileSdkVersion 29
 
     sourceSets {
         main.java.srcDirs += 'src/main/kotlin'
@@ -38,13 +38,11 @@ android {
 
     defaultConfig {
         // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
-        applicationId "com.fanton.DRUN"
+        applicationId "com.example.DRUN"
         minSdkVersion 16
-        targetSdkVersion 28
+        targetSdkVersion 29
         versionCode flutterVersionCode.toInteger()
         versionName flutterVersionName
-        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
-        multiDexEnabled true
     }
 
     buildTypes {
@@ -62,8 +60,4 @@ flutter {
 
 dependencies {
     implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
-    testImplementation 'junit:junit:4.12'
-    androidTestImplementation 'androidx.test:runner:1.1.1'
-    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
 }
-apply plugin: 'com.google.gms.google-services'  // Google Services plugin
\ No newline at end of file
diff --git a/app/android/app/google-services.json b/app/android/app/google-services.json
deleted file mode 100644
index 3a7985d..0000000
--- a/app/android/app/google-services.json
+++ /dev/null
@@ -1,55 +0,0 @@
-{
-  "project_info": {
-    "project_number": "668619921208",
-    "firebase_url": "https://drun-delivery.firebaseio.com",
-    "project_id": "drun-delivery",
-    "storage_bucket": "drun-delivery.appspot.com"
-  },
-  "client": [
-    {
-      "client_info": {
-        "mobilesdk_app_id": "1:668619921208:android:0abe83d05f6677473013cd",
-        "android_client_info": {
-          "package_name": "com.fanton.DRUN"
-        }
-      },
-      "oauth_client": [
-        {
-          "client_id": "668619921208-mrpmmb4hofv06avfeclbc9eugucmtafv.apps.googleusercontent.com",
-          "client_type": 1,
-          "android_info": {
-            "package_name": "com.fanton.DRUN",
-            "certificate_hash": "98e6d401ac044e4b1ba0a1a8c283ba7e9ca7a949"
-          }
-        },
-        {
-          "client_id": "668619921208-b2m7gg4jjcvab6c1cfa9jd3fbcbd2jna.apps.googleusercontent.com",
-          "client_type": 3
-        }
-      ],
-      "api_key": [
-        {
-          "current_key": "AIzaSyCuCRsG2QZ4apERuVPH7QyEQymLZGgtpBI"
-        }
-      ],
-      "services": {
-        "appinvite_service": {
-          "other_platform_oauth_client": [
-            {
-              "client_id": "668619921208-b2m7gg4jjcvab6c1cfa9jd3fbcbd2jna.apps.googleusercontent.com",
-              "client_type": 3
-            },
-            {
-              "client_id": "668619921208-mpqkktpib2ror4tdo6n0tuhsc52f8cga.apps.googleusercontent.com",
-              "client_type": 2,
-              "ios_info": {
-                "bundle_id": "com.fanton.DRUN"
-              }
-            }
-          ]
-        }
-      }
-    }
-  ],
-  "configuration_version": "1"
-}
\ No newline at end of file
diff --git a/app/android/app/src/debug/AndroidManifest.xml b/app/android/app/src/debug/AndroidManifest.xml
index d650950..81ca4e0 100644
--- a/app/android/app/src/debug/AndroidManifest.xml
+++ b/app/android/app/src/debug/AndroidManifest.xml
@@ -1,10 +1,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.fanton.DRUN">
+    package="com.example.DRUN">
     <!-- Flutter needs it to communicate with the running application
          to allow setting breakpoints, to provide hot reload, etc.
     -->
     <uses-permission android:name="android.permission.INTERNET"/>
-    <uses-permission android:name="android.permission.READ_CONTACTS"/>
-    <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
-    <uses-permission android:name="android.permission.GET_ACCOUNTS"/>
 </manifest>
diff --git a/app/android/app/src/main/AndroidManifest.xml b/app/android/app/src/main/AndroidManifest.xml
index 2226e2a..72e0bc3 100644
--- a/app/android/app/src/main/AndroidManifest.xml
+++ b/app/android/app/src/main/AndroidManifest.xml
@@ -1,14 +1,12 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.fanton.DRUN">
+    package="com.example.DRUN">
     <!-- io.flutter.app.FlutterApplication is an android.app.Application that
          calls FlutterMain.startInitialization(this); in its onCreate method.
          In most cases you can leave this as-is, but you if you want to provide
          additional functionality it is fine to subclass or reimplement
          FlutterApplication and put your custom class here. -->
-    <uses-permission android:name="android.permission.INTERNET"/>
-    <uses-permission android:name="android.permission.READ_CONTACTS"/>
-    <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
-    <uses-permission android:name="android.permission.GET_ACCOUNTS"/>
+    <uses-permission android:name="android.permission.READ_CONTACTS" />
+    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
     <application
         android:name="io.flutter.app.FlutterApplication"
         android:label="DRUN"
diff --git a/app/android/app/src/main/kotlin/com/example/DRUN/MainActivity.kt b/app/android/app/src/main/kotlin/com/example/DRUN/MainActivity.kt
index 8caa46d..80efb26 100644
--- a/app/android/app/src/main/kotlin/com/example/DRUN/MainActivity.kt
+++ b/app/android/app/src/main/kotlin/com/example/DRUN/MainActivity.kt
@@ -1,4 +1,4 @@
-package com.fanton.DRUN
+package com.example.DRUN
 
 import io.flutter.embedding.android.FlutterActivity
 
diff --git a/app/android/app/src/profile/AndroidManifest.xml b/app/android/app/src/profile/AndroidManifest.xml
index d650950..81ca4e0 100644
--- a/app/android/app/src/profile/AndroidManifest.xml
+++ b/app/android/app/src/profile/AndroidManifest.xml
@@ -1,10 +1,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.fanton.DRUN">
+    package="com.example.DRUN">
     <!-- Flutter needs it to communicate with the running application
          to allow setting breakpoints, to provide hot reload, etc.
     -->
     <uses-permission android:name="android.permission.INTERNET"/>
-    <uses-permission android:name="android.permission.READ_CONTACTS"/>
-    <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
-    <uses-permission android:name="android.permission.GET_ACCOUNTS"/>
 </manifest>
diff --git a/app/android/build.gradle b/app/android/build.gradle
index 87201e7..3100ad2 100644
--- a/app/android/build.gradle
+++ b/app/android/build.gradle
@@ -8,7 +8,6 @@ buildscript {
     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'  // Google Services plugin
     }
 }
 
@@ -29,4 +28,4 @@ subprojects {
 
 task clean(type: Delete) {
     delete rootProject.buildDir
-}
\ No newline at end of file
+}
diff --git a/app/android/gradle.properties b/app/android/gradle.properties
index 4167249..a673820 100644
--- a/app/android/gradle.properties
+++ b/app/android/gradle.properties
@@ -1,4 +1,4 @@
 org.gradle.jvmargs=-Xmx1536M
 android.useAndroidX=true
 android.enableJetifier=true
-android.enableR8=true
\ No newline at end of file
+android.enableR8=true
diff --git a/app/android/gradle/wrapper/gradle-wrapper.properties b/app/android/gradle/wrapper/gradle-wrapper.properties
index 12d38de..296b146 100644
--- a/app/android/gradle/wrapper/gradle-wrapper.properties
+++ b/app/android/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,6 @@
+#Fri Jun 23 08:50:38 CEST 2017
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
diff --git a/app/android/settings.gradle b/app/android/settings.gradle
index 121d30d..44e62bc 100644
--- a/app/android/settings.gradle
+++ b/app/android/settings.gradle
@@ -1,15 +1,11 @@
 include ':app'
 
-def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
+def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
+def properties = new Properties()
 
-def plugins = new Properties()
-def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
-if (pluginsFile.exists()) {
-    pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
-}
+assert localPropertiesFile.exists()
+localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
 
-plugins.each { name, path ->
-    def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
-    include ":$name"
-    project(":$name").projectDir = pluginDirectory
-}
\ No newline at end of file
+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/app/android/settings_aar.gradle b/app/android/settings_aar.gradle
deleted file mode 100644
index e7b4def..0000000
--- a/app/android/settings_aar.gradle
+++ /dev/null
@@ -1,1 +0,0 @@
-include ':app'
diff --git a/app/assets/setup_background.mp4 b/app/assets/setup_background.mp4
deleted file mode 100644
index af215fe..0000000
Binary files a/app/assets/setup_background.mp4 and /dev/null differ
diff --git a/app/ios/Flutter/AppFrameworkInfo.plist b/app/ios/Flutter/AppFrameworkInfo.plist
index f2872cf..6b4c0f7 100644
--- a/app/ios/Flutter/AppFrameworkInfo.plist
+++ b/app/ios/Flutter/AppFrameworkInfo.plist
@@ -21,6 +21,6 @@
   <key>CFBundleVersion</key>
   <string>1.0</string>
   <key>MinimumOSVersion</key>
-  <string>9.0</string>
+  <string>8.0</string>
 </dict>
 </plist>
diff --git a/app/ios/Flutter/Debug.xcconfig b/app/ios/Flutter/Debug.xcconfig
index e8efba1..592ceee 100644
--- a/app/ios/Flutter/Debug.xcconfig
+++ b/app/ios/Flutter/Debug.xcconfig
@@ -1,2 +1,1 @@
-#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
 #include "Generated.xcconfig"
diff --git a/app/ios/Flutter/Release.xcconfig b/app/ios/Flutter/Release.xcconfig
index 399e934..592ceee 100644
--- a/app/ios/Flutter/Release.xcconfig
+++ b/app/ios/Flutter/Release.xcconfig
@@ -1,2 +1,1 @@
-#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
 #include "Generated.xcconfig"
diff --git a/app/ios/Podfile b/app/ios/Podfile
deleted file mode 100644
index 1e8c3c9..0000000
--- a/app/ios/Podfile
+++ /dev/null
@@ -1,41 +0,0 @@
-# Uncomment this line to define a global platform for your project
-# platform :ios, '9.0'
-
-# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
-ENV['COCOAPODS_DISABLE_STATS'] = 'true'
-
-project 'Runner', {
-  'Debug' => :debug,
-  'Profile' => :release,
-  'Release' => :release,
-}
-
-def flutter_root
-  generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
-  unless File.exist?(generated_xcode_build_settings_path)
-    raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
-  end
-
-  File.foreach(generated_xcode_build_settings_path) do |line|
-    matches = line.match(/FLUTTER_ROOT\=(.*)/)
-    return matches[1].strip if matches
-  end
-  raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
-end
-
-require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
-
-flutter_ios_podfile_setup
-
-target 'Runner' do
-  use_frameworks!
-  use_modular_headers!
-
-  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
-end
-
-post_install do |installer|
-  installer.pods_project.targets.each do |target|
-    flutter_additional_ios_build_settings(target)
-  end
-end
diff --git a/app/ios/Podfile.lock b/app/ios/Podfile.lock
deleted file mode 100644
index e8fc9a1..0000000
--- a/app/ios/Podfile.lock
+++ /dev/null
@@ -1,16 +0,0 @@
-PODS:
-  - Flutter (1.0.0)
-
-DEPENDENCIES:
-  - Flutter (from `Flutter`)
-
-EXTERNAL SOURCES:
-  Flutter:
-    :path: Flutter
-
-SPEC CHECKSUMS:
-  Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
-
-PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c
-
-COCOAPODS: 1.9.3
diff --git a/app/ios/Runner.xcodeproj/project.pbxproj b/app/ios/Runner.xcodeproj/project.pbxproj
index a3df193..7971485 100644
--- a/app/ios/Runner.xcodeproj/project.pbxproj
+++ b/app/ios/Runner.xcodeproj/project.pbxproj
@@ -3,18 +3,16 @@
 	archiveVersion = 1;
 	classes = {
 	};
-	objectVersion = 50;
+	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 */; };
-		55CF927B252690B400CE7318 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 55CF927A252690B400CE7318 /* GoogleService-Info.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 */; };
-		B6187656AE7554B26D3E76B2 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 824AE34CAD56CA850C38125F /* Pods_Runner.framework */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXCopyFilesBuildPhase section */
@@ -31,16 +29,12 @@
 /* End PBXCopyFilesBuildPhase section */
 
 /* Begin PBXFileReference section */
-		0F769E8EA4679C1B339B3D1C /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
 		1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
 		1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
 		3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
-		55CF927A252690B400CE7318 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
-		658D571691CE9FF0AE393C31 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
 		74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
 		74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
 		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
-		824AE34CAD56CA850C38125F /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
 		9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
 		97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -48,7 +42,6 @@
 		97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
 		97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
 		97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
-		D1009E63113D172A5CD18F76 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -56,32 +49,12 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				B6187656AE7554B26D3E76B2 /* Pods_Runner.framework in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
 /* End PBXFrameworksBuildPhase section */
 
 /* Begin PBXGroup section */
-		0243EDB356F62BFB48A72357 /* Frameworks */ = {
-			isa = PBXGroup;
-			children = (
-				824AE34CAD56CA850C38125F /* Pods_Runner.framework */,
-			);
-			name = Frameworks;
-			sourceTree = "<group>";
-		};
-		191273B68EC37B414EEFF2C5 /* Pods */ = {
-			isa = PBXGroup;
-			children = (
-				658D571691CE9FF0AE393C31 /* Pods-Runner.debug.xcconfig */,
-				D1009E63113D172A5CD18F76 /* Pods-Runner.release.xcconfig */,
-				0F769E8EA4679C1B339B3D1C /* Pods-Runner.profile.xcconfig */,
-			);
-			name = Pods;
-			path = Pods;
-			sourceTree = "<group>";
-		};
 		9740EEB11CF90186004384FC /* Flutter */ = {
 			isa = PBXGroup;
 			children = (
@@ -99,8 +72,6 @@
 				9740EEB11CF90186004384FC /* Flutter */,
 				97C146F01CF9000F007C117D /* Runner */,
 				97C146EF1CF9000F007C117D /* Products */,
-				191273B68EC37B414EEFF2C5 /* Pods */,
-				0243EDB356F62BFB48A72357 /* Frameworks */,
 			);
 			sourceTree = "<group>";
 		};
@@ -115,7 +86,6 @@
 		97C146F01CF9000F007C117D /* Runner */ = {
 			isa = PBXGroup;
 			children = (
-				55CF927A252690B400CE7318 /* GoogleService-Info.plist */,
 				97C146FA1CF9000F007C117D /* Main.storyboard */,
 				97C146FD1CF9000F007C117D /* Assets.xcassets */,
 				97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
@@ -135,14 +105,12 @@
 			isa = PBXNativeTarget;
 			buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
 			buildPhases = (
-				FAD7957F54CD1082CDA5C802 /* [CP] Check Pods Manifest.lock */,
 				9740EEB61CF901F6004384FC /* Run Script */,
 				97C146EA1CF9000F007C117D /* Sources */,
 				97C146EB1CF9000F007C117D /* Frameworks */,
 				97C146EC1CF9000F007C117D /* Resources */,
 				9705A1C41CF9048500538489 /* Embed Frameworks */,
 				3B06AD1E1E4923F5004D2608 /* Thin Binary */,
-				F88E6A214D8E04554C812501 /* [CP] Embed Pods Frameworks */,
 			);
 			buildRules = (
 			);
@@ -193,7 +161,6 @@
 			files = (
 				97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
 				3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
-				55CF927B252690B400CE7318 /* GoogleService-Info.plist in Resources */,
 				97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
 				97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
 			);
@@ -230,45 +197,6 @@
 			shellPath = /bin/sh;
 			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
 		};
-		F88E6A214D8E04554C812501 /* [CP] Embed Pods Frameworks */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputFileListPaths = (
-				"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
-			);
-			name = "[CP] Embed Pods Frameworks";
-			outputFileListPaths = (
-				"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
-			showEnvVarsInLog = 0;
-		};
-		FAD7957F54CD1082CDA5C802 /* [CP] Check Pods Manifest.lock */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputFileListPaths = (
-			);
-			inputPaths = (
-				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
-				"${PODS_ROOT}/Manifest.lock",
-			);
-			name = "[CP] Check Pods Manifest.lock";
-			outputFileListPaths = (
-			);
-			outputPaths = (
-				"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
-			showEnvVarsInLog = 0;
-		};
 /* End PBXShellScriptBuildPhase section */
 
 /* Begin PBXSourcesBuildPhase section */
@@ -360,22 +288,18 @@
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CLANG_ENABLE_MODULES = YES;
 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
-				DEVELOPMENT_TEAM = B9TR2U3FSP;
 				ENABLE_BITCODE = NO;
 				FRAMEWORK_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(PROJECT_DIR)/Flutter",
 				);
 				INFOPLIST_FILE = Runner/Info.plist;
-				LD_RUNPATH_SEARCH_PATHS = (
-					"$(inherited)",
-					"@executable_path/Frameworks",
-				);
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
 				LIBRARY_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(PROJECT_DIR)/Flutter",
 				);
-				PRODUCT_BUNDLE_IDENTIFIER = com.fanton.DRUN;
+				PRODUCT_BUNDLE_IDENTIFIER = com.example.DRUN;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
 				SWIFT_VERSION = 5.0;
@@ -483,8 +407,7 @@
 				MTL_ENABLE_DEBUG_INFO = NO;
 				SDKROOT = iphoneos;
 				SUPPORTED_PLATFORMS = iphoneos;
-				SWIFT_COMPILATION_MODE = wholemodule;
-				SWIFT_OPTIMIZATION_LEVEL = "-O";
+				SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
 				TARGETED_DEVICE_FAMILY = "1,2";
 				VALIDATE_PRODUCT = YES;
 			};
@@ -497,22 +420,18 @@
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CLANG_ENABLE_MODULES = YES;
 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
-				DEVELOPMENT_TEAM = B9TR2U3FSP;
 				ENABLE_BITCODE = NO;
 				FRAMEWORK_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(PROJECT_DIR)/Flutter",
 				);
 				INFOPLIST_FILE = Runner/Info.plist;
-				LD_RUNPATH_SEARCH_PATHS = (
-					"$(inherited)",
-					"@executable_path/Frameworks",
-				);
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
 				LIBRARY_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(PROJECT_DIR)/Flutter",
 				);
-				PRODUCT_BUNDLE_IDENTIFIER = com.fanton.DRUN;
+				PRODUCT_BUNDLE_IDENTIFIER = com.example.DRUN;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
 				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@@ -528,22 +447,18 @@
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CLANG_ENABLE_MODULES = YES;
 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
-				DEVELOPMENT_TEAM = B9TR2U3FSP;
 				ENABLE_BITCODE = NO;
 				FRAMEWORK_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(PROJECT_DIR)/Flutter",
 				);
 				INFOPLIST_FILE = Runner/Info.plist;
-				LD_RUNPATH_SEARCH_PATHS = (
-					"$(inherited)",
-					"@executable_path/Frameworks",
-				);
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
 				LIBRARY_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(PROJECT_DIR)/Flutter",
 				);
-				PRODUCT_BUNDLE_IDENTIFIER = com.fanton.DRUN;
+				PRODUCT_BUNDLE_IDENTIFIER = com.example.DRUN;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
 				SWIFT_VERSION = 5.0;
diff --git a/app/ios/Runner.xcworkspace/contents.xcworkspacedata b/app/ios/Runner.xcworkspace/contents.xcworkspacedata
index 21a3cc1..1d526a1 100644
--- a/app/ios/Runner.xcworkspace/contents.xcworkspacedata
+++ b/app/ios/Runner.xcworkspace/contents.xcworkspacedata
@@ -4,7 +4,4 @@
    <FileRef
       location = "group:Runner.xcodeproj">
    </FileRef>
-   <FileRef
-      location = "group:Pods/Pods.xcodeproj">
-   </FileRef>
 </Workspace>
diff --git a/app/ios/Runner/GoogleService-Info.plist b/app/ios/Runner/GoogleService-Info.plist
deleted file mode 100644
index f13ced0..0000000
--- a/app/ios/Runner/GoogleService-Info.plist
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-	<key>CLIENT_ID</key>
-	<string>668619921208-mpqkktpib2ror4tdo6n0tuhsc52f8cga.apps.googleusercontent.com</string>
-	<key>REVERSED_CLIENT_ID</key>
-	<string>com.googleusercontent.apps.668619921208-mpqkktpib2ror4tdo6n0tuhsc52f8cga</string>
-	<key>ANDROID_CLIENT_ID</key>
-	<string>668619921208-iqmm8aq52butajljn3hpsv1nnlp244gb.apps.googleusercontent.com</string>
-	<key>API_KEY</key>
-	<string>AIzaSyAKGgcymLfmRfiDJlklJeqJ5BrZiHGcmfc</string>
-	<key>GCM_SENDER_ID</key>
-	<string>668619921208</string>
-	<key>PLIST_VERSION</key>
-	<string>1</string>
-	<key>BUNDLE_ID</key>
-	<string>com.fanton.DRUN</string>
-	<key>PROJECT_ID</key>
-	<string>drun-delivery</string>
-	<key>STORAGE_BUCKET</key>
-	<string>drun-delivery.appspot.com</string>
-	<key>IS_ADS_ENABLED</key>
-	<false/>
-	<key>IS_ANALYTICS_ENABLED</key>
-	<false/>
-	<key>IS_APPINVITE_ENABLED</key>
-	<true/>
-	<key>IS_GCM_ENABLED</key>
-	<true/>
-	<key>IS_SIGNIN_ENABLED</key>
-	<true/>
-	<key>GOOGLE_APP_ID</key>
-	<string>1:668619921208:ios:616f7343ddc1cf9f3013cd</string>
-	<key>DATABASE_URL</key>
-	<string>https://drun-delivery.firebaseio.com</string>
-</dict>
-</plist>
diff --git a/app/lib/core/constants/constants.dart b/app/lib/core/constants/constants.dart
new file mode 100644
index 0000000..bc59d6c
--- /dev/null
+++ b/app/lib/core/constants/constants.dart
@@ -0,0 +1,2 @@
+const String SERVER_ADDRESS = 'https://drun.rocks';
+const String USER_CREDENTIALS_CACHE_NAME = 'CACHED_USER_CREDENTIALS';
diff --git a/app/lib/core/data/sources/network_info.dart b/app/lib/core/data/sources/network_info.dart
new file mode 100644
index 0000000..c03f9c5
--- /dev/null
+++ b/app/lib/core/data/sources/network_info.dart
@@ -0,0 +1,14 @@
+import 'package:data_connection_checker/data_connection_checker.dart';
+
+abstract class NetworkInfo {
+  Future<bool> get isConnected;
+}
+
+class NetworkInfoImpl implements NetworkInfo {
+  final DataConnectionChecker dataConnectionChecker;
+
+  NetworkInfoImpl(this.dataConnectionChecker);
+
+  @override
+  Future<bool> get isConnected => dataConnectionChecker.hasConnection;
+}
diff --git a/app/lib/core/errors/exceptions.dart b/app/lib/core/errors/exceptions.dart
new file mode 100644
index 0000000..f7e1359
--- /dev/null
+++ b/app/lib/core/errors/exceptions.dart
@@ -0,0 +1,9 @@
+class ServerException implements Exception {}
+
+class CacheException implements Exception {}
+
+class PermissionException implements Exception {}
+
+class LocationException implements Exception {}
+
+class PaymentCardException implements Exception {}
diff --git a/app/lib/core/errors/failures.dart b/app/lib/core/errors/failures.dart
new file mode 100644
index 0000000..769e054
--- /dev/null
+++ b/app/lib/core/errors/failures.dart
@@ -0,0 +1,52 @@
+import 'package:equatable/equatable.dart';
+
+abstract class Failure extends Equatable {
+  final List properties = const <dynamic>[];
+
+  @override
+  List<Object> get props => [properties];
+
+  String get message => 'Generic failure.';
+}
+
+// Generic failures
+class ServerFailure extends Failure {
+  @override
+  String get message => 'Server failure.';
+}
+
+class NetworkFailure extends Failure {
+  @override
+  String get message => 'Network failure.';
+}
+
+class CacheFailure extends Failure {
+  @override
+  String get message => 'Cache failure.';
+}
+
+class InvalidInputFailure extends Failure {
+  @override
+  String get message =>
+      'Invalid input - Make sure you inputted a valid phone number.';
+}
+
+class PermissionFailure extends Failure {
+  @override
+  String get message => 'Insufficient permissions.';
+}
+
+class ContactSelectionFailure extends Failure {
+  @override
+  String get message => 'Failure during contact selection.';
+}
+
+class LocationFailure extends Failure {
+  @override
+  String get message => 'Location service failure.';
+}
+
+class PaymentCardFailure extends Failure {
+  @override
+  String get message => 'Invalid payment card information.';
+}
diff --git a/app/lib/core/presentation/pages/loading_page.dart b/app/lib/core/presentation/pages/loading_page.dart
new file mode 100644
index 0000000..f6196f9
--- /dev/null
+++ b/app/lib/core/presentation/pages/loading_page.dart
@@ -0,0 +1,31 @@
+import 'package:flutter/material.dart';
+
+class LoadingPage extends StatelessWidget {
+  const LoadingPage({Key key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      child: Center(
+        child: Column(
+          mainAxisAlignment: MainAxisAlignment.center,
+          crossAxisAlignment: CrossAxisAlignment.center,
+          children: <Widget>[
+            SizedBox(
+              child: CircularProgressIndicator(
+                strokeWidth: 10.0,
+              ),
+              height: 100.0,
+              width: 100.0,
+            ),
+            SizedBox(height: 10.0),
+            Text(
+              'Loading...',
+              style: Theme.of(context).textTheme.headline2,
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+}
diff --git a/app/lib/core/presentation/pages/pages.dart b/app/lib/core/presentation/pages/pages.dart
new file mode 100644
index 0000000..1121871
--- /dev/null
+++ b/app/lib/core/presentation/pages/pages.dart
@@ -0,0 +1,1 @@
+export 'loading_page.dart';
diff --git a/app/lib/core/presentation/util/input_validator.dart b/app/lib/core/presentation/util/input_validator.dart
new file mode 100644
index 0000000..713ff5b
--- /dev/null
+++ b/app/lib/core/presentation/util/input_validator.dart
@@ -0,0 +1,20 @@
+import 'package:DRUN/core/errors/failures.dart';
+import 'package:dartz/dartz.dart';
+
+class InputValidator {
+  Either<Failure, String> stringAsPhoneNumber(String string) {
+    // Regular expression which matches all phone numbers starting with a
+    // country code.
+    //
+    // Reference: https://coflutter.com/how-to-validate-phone-number-in-dart-flutter
+    RegExp regExp = new RegExp(
+      r'^[+][(]{0,1}[0-9]{1,4}[)]{0,1}[-\s\./0-9()]{9,14}$',
+    );
+
+    if (string.length == 0 || !regExp.hasMatch(string)) {
+      return Left(InvalidInputFailure());
+    }
+
+    return Right(string);
+  }
+}
diff --git a/app/lib/core/presentation/widgets/failure_message.dart b/app/lib/core/presentation/widgets/failure_message.dart
new file mode 100644
index 0000000..4f39f22
--- /dev/null
+++ b/app/lib/core/presentation/widgets/failure_message.dart
@@ -0,0 +1,32 @@
+import 'package:flutter/material.dart';
+
+class FailureMessage extends StatelessWidget {
+  final num height;
+  final String message;
+
+  const FailureMessage({
+    Key key,
+    @required this.height,
+    this.message,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Opacity(
+      opacity: this.message == null ? 0 : 1,
+      child: Container(
+        decoration: BoxDecoration(
+          border: Border.all(
+            color: Colors.red,
+          ),
+          borderRadius: BorderRadius.all(Radius.circular(20)),
+          color: Colors.red.withAlpha(50),
+        ),
+        height: this.height,
+        child: Center(
+          child: Text(this.message ?? ''),
+        ),
+      ),
+    );
+  }
+}
diff --git a/app/lib/core/presentation/widgets/widgets.dart b/app/lib/core/presentation/widgets/widgets.dart
new file mode 100644
index 0000000..1a1346e
--- /dev/null
+++ b/app/lib/core/presentation/widgets/widgets.dart
@@ -0,0 +1,1 @@
+export 'failure_message.dart';
diff --git a/app/lib/core/usecases/usecase.dart b/app/lib/core/usecases/usecase.dart
new file mode 100644
index 0000000..c126e04
--- /dev/null
+++ b/app/lib/core/usecases/usecase.dart
@@ -0,0 +1,13 @@
+import 'package:dartz/dartz.dart';
+import 'package:equatable/equatable.dart';
+
+import '../errors/failures.dart';
+
+abstract class UseCase<Type, Params> {
+  Future<Either<Failure, Type>> call(Params params);
+}
+
+class NoParams extends Equatable {
+  @override
+  List<Object> get props => [];
+}
diff --git a/app/lib/dialogs/balance.dart b/app/lib/dialogs/balance.dart
deleted file mode 100644
index 782a152..0000000
--- a/app/lib/dialogs/balance.dart
+++ /dev/null
@@ -1,8 +0,0 @@
-import 'package:flutter/material.dart';
-
-class BalanceDialog extends StatelessWidget {
-  @override
-  Widget build(BuildContext context) {
-    return Material(child: Container());
-  }
-}
diff --git a/app/lib/dialogs/drone_receiver.dart b/app/lib/dialogs/drone_receiver.dart
deleted file mode 100644
index 2bcd08c..0000000
--- a/app/lib/dialogs/drone_receiver.dart
+++ /dev/null
@@ -1,8 +0,0 @@
-import 'package:flutter/material.dart';
-
-class DroneReceiverDialog extends StatelessWidget {
-  @override
-  Widget build(BuildContext context) {
-    return Material(child: Container());
-  }
-}
diff --git a/app/lib/dialogs/drone_sender.dart b/app/lib/dialogs/drone_sender.dart
deleted file mode 100644
index cd237f5..0000000
--- a/app/lib/dialogs/drone_sender.dart
+++ /dev/null
@@ -1,8 +0,0 @@
-import 'package:flutter/material.dart';
-
-class DroneSenderDialog extends StatelessWidget {
-  @override
-  Widget build(BuildContext context) {
-    return Material(child: Container());
-  }
-}
diff --git a/app/lib/features/authentication/data/models/authentication_sms_status_model.dart b/app/lib/features/authentication/data/models/authentication_sms_status_model.dart
new file mode 100644
index 0000000..711c492
--- /dev/null
+++ b/app/lib/features/authentication/data/models/authentication_sms_status_model.dart
@@ -0,0 +1,19 @@
+import 'package:meta/meta.dart';
+
+import '../../domain/entities/authentication_sms_status.dart';
+
+class AuthenticationSmsStatusModel extends AuthenticationSmsStatus {
+  AuthenticationSmsStatusModel({
+    @required phoneNumber,
+    @required succeeded,
+  })  : assert(phoneNumber != null),
+        assert(succeeded != null),
+        super(phoneNumber: phoneNumber, succeeded: succeeded);
+
+  factory AuthenticationSmsStatusModel.fromJSON(Map<String, dynamic> json) {
+    return AuthenticationSmsStatusModel(
+      phoneNumber: json['phoneNumber'],
+      succeeded: json['succeeded'],
+    );
+  }
+}
diff --git a/app/lib/features/authentication/data/models/user_credentials_model.dart b/app/lib/features/authentication/data/models/user_credentials_model.dart
new file mode 100644
index 0000000..b0cd842
--- /dev/null
+++ b/app/lib/features/authentication/data/models/user_credentials_model.dart
@@ -0,0 +1,26 @@
+import 'package:meta/meta.dart';
+
+import '../../domain/entities/user_credentials.dart';
+
+class UserCredentialsModel extends UserCredentials {
+  UserCredentialsModel({
+    @required userId,
+    @required userToken,
+  })  : assert(userId != null),
+        assert(userToken != null),
+        super(userId: userId, userToken: userToken);
+
+  factory UserCredentialsModel.fromJSON(Map<String, dynamic> json) {
+    return UserCredentialsModel(
+      userId: json['userId'],
+      userToken: json['userToken'],
+    );
+  }
+
+  Map<String, dynamic> toJSON() {
+    return {
+      'userId': this.userId,
+      'userToken': this.userToken,
+    };
+  }
+}
diff --git a/app/lib/features/authentication/data/repositories/user_authentication_repository_impl.dart b/app/lib/features/authentication/data/repositories/user_authentication_repository_impl.dart
new file mode 100644
index 0000000..0e3900f
--- /dev/null
+++ b/app/lib/features/authentication/data/repositories/user_authentication_repository_impl.dart
@@ -0,0 +1,73 @@
+import 'package:dartz/dartz.dart';
+import 'package:meta/meta.dart';
+
+import '../../../../core/errors/exceptions.dart';
+import '../../../../core/errors/failures.dart';
+import '../../../../core/data/sources/network_info.dart';
+import '../../domain/entities/authentication_sms_status.dart';
+import '../../domain/entities/user_credentials.dart';
+import '../../domain/repositories/user_authentication_repository.dart';
+import '../sources/user_authentication_local_source.dart';
+import '../sources/user_authentication_remote_source.dart';
+
+class UserAuthenticationRepositoryImpl implements UserAuthenticationRepository {
+  final UserAuthenticationRemoteSource remoteSource;
+  final UserAuthenticationLocalSource localSource;
+  final NetworkInfo networkInfo;
+
+  UserAuthenticationRepositoryImpl({
+    @required this.remoteSource,
+    @required this.localSource,
+    @required this.networkInfo,
+  })  : assert(remoteSource != null),
+        assert(localSource != null),
+        assert(networkInfo != null);
+
+  @override
+  Future<Either<Failure, AuthenticationSmsStatus>> sendAuthenticationSms(
+    String phoneNumber,
+  ) async {
+    if (await networkInfo.isConnected) {
+      try {
+        final authenticationSmsStatus =
+            await remoteSource.sendAuthenticationSms(phoneNumber);
+        return Right(authenticationSmsStatus);
+      } on ServerException {
+        return Left(ServerFailure());
+      }
+    } else {
+      return Left(NetworkFailure());
+    }
+  }
+
+  @override
+  Future<Either<Failure, UserCredentials>> verifyAuthenticationSms(
+    String phoneNumber,
+    String code,
+  ) async {
+    if (await networkInfo.isConnected) {
+      try {
+        final userCredentials = await remoteSource.verifyAuthenticationSms(
+          phoneNumber,
+          code,
+        );
+        localSource.cacheUserCredentials(userCredentials);
+        return Right(userCredentials);
+      } on ServerException {
+        return Left(ServerFailure());
+      }
+    } else {
+      return Left(NetworkFailure());
+    }
+  }
+
+  @override
+  Future<Either<Failure, UserCredentials>> getLoggedInUser() async {
+    try {
+      final userCredentials = await localSource.getUserCredentials();
+      return Right(userCredentials);
+    } on CacheException {
+      return Left(CacheFailure());
+    }
+  }
+}
diff --git a/app/lib/features/authentication/data/sources/user_authentication_local_source.dart b/app/lib/features/authentication/data/sources/user_authentication_local_source.dart
new file mode 100644
index 0000000..298a2a3
--- /dev/null
+++ b/app/lib/features/authentication/data/sources/user_authentication_local_source.dart
@@ -0,0 +1,46 @@
+import 'dart:convert';
+
+import 'package:DRUN/core/constants/constants.dart';
+import 'package:meta/meta.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+
+import '../../../../core/errors/exceptions.dart';
+import '../models/user_credentials_model.dart';
+
+abstract class UserAuthenticationLocalSource {
+  /// Get the cached [UserCredentialsModel] which was gotten when the user
+  /// authenticated.
+  ///
+  /// Throws a [CacheException] for all error codes.
+  Future<UserCredentialsModel> getUserCredentials();
+
+  Future<void> cacheUserCredentials(UserCredentialsModel userCredentials);
+}
+
+class UserAuthenticationLocalSourceImpl
+    implements UserAuthenticationLocalSource {
+  final SharedPreferences sharedPreferences;
+
+  UserAuthenticationLocalSourceImpl({
+    @required this.sharedPreferences,
+  }) : assert(sharedPreferences != null);
+
+  @override
+  Future<UserCredentialsModel> getUserCredentials() {
+    final jsonString = sharedPreferences.getString(USER_CREDENTIALS_CACHE_NAME);
+
+    if (jsonString == null) {
+      throw CacheException();
+    }
+
+    return Future.value(UserCredentialsModel.fromJSON(json.decode(jsonString)));
+  }
+
+  @override
+  Future<void> cacheUserCredentials(
+    UserCredentialsModel userCredentials,
+  ) async {
+    final jsonString = json.encode(userCredentials.toJSON());
+    return sharedPreferences.setString(USER_CREDENTIALS_CACHE_NAME, jsonString);
+  }
+}
diff --git a/app/lib/features/authentication/data/sources/user_authentication_remote_source.dart b/app/lib/features/authentication/data/sources/user_authentication_remote_source.dart
new file mode 100644
index 0000000..d4e20a4
--- /dev/null
+++ b/app/lib/features/authentication/data/sources/user_authentication_remote_source.dart
@@ -0,0 +1,79 @@
+import 'dart:convert';
+
+import 'package:DRUN/core/constants/constants.dart';
+import 'package:DRUN/core/errors/exceptions.dart';
+import 'package:http/http.dart' as http;
+import 'package:meta/meta.dart';
+
+import '../models/authentication_sms_status_model.dart';
+import '../models/user_credentials_model.dart';
+
+abstract class UserAuthenticationRemoteSource {
+  /// POST to https://drun.rocks/api/users/authenticate/send-sms-code endpoint.
+  ///
+  /// Throws a [ServerException] for all error codes.
+  Future<AuthenticationSmsStatusModel> sendAuthenticationSms(
+    String phoneNumber,
+  );
+
+  /// POST to https://drun.rocks/api/users/authenticate/verify-sms-code endpoint.
+  ///
+  /// Throws a [ServerException] for all error codes.
+  Future<UserCredentialsModel> verifyAuthenticationSms(
+    String phoneNumber,
+    String code,
+  );
+}
+
+class UserAuthenticationRemoteSourceImpl
+    implements UserAuthenticationRemoteSource {
+  final http.Client httpClient;
+
+  UserAuthenticationRemoteSourceImpl({
+    @required this.httpClient,
+  }) : assert(httpClient != null);
+
+  @override
+  Future<AuthenticationSmsStatusModel> sendAuthenticationSms(
+    String phoneNumber,
+  ) async {
+    final response = await httpClient.post(
+      '$SERVER_ADDRESS/api/users/authenticate/send-sms-code',
+      body: jsonEncode({
+        'phoneNumber': phoneNumber,
+      }),
+      headers: {
+        'Content-Type': 'application/json',
+      },
+    );
+
+    if (response.statusCode != 200) {
+      throw ServerException();
+    }
+
+    return AuthenticationSmsStatusModel.fromJSON(json.decode(response.body));
+  }
+
+  @override
+  Future<UserCredentialsModel> verifyAuthenticationSms(
+    String phoneNumber,
+    String code,
+  ) async {
+    final response = await httpClient.post(
+      '$SERVER_ADDRESS/api/users/authenticate/verify-sms-code',
+      body: jsonEncode({
+        'phoneNumber': phoneNumber,
+        'code': code,
+      }),
+      headers: {
+        'Content-Type': 'application/json',
+      },
+    );
+
+    if (response.statusCode != 200) {
+      throw ServerException();
+    }
+
+    return UserCredentialsModel.fromJSON(json.decode(response.body));
+  }
+}
diff --git a/app/lib/features/authentication/domain/entities/authentication_sms_status.dart b/app/lib/features/authentication/domain/entities/authentication_sms_status.dart
new file mode 100644
index 0000000..f8fbdf5
--- /dev/null
+++ b/app/lib/features/authentication/domain/entities/authentication_sms_status.dart
@@ -0,0 +1,16 @@
+import 'package:equatable/equatable.dart';
+import 'package:meta/meta.dart';
+
+class AuthenticationSmsStatus extends Equatable {
+  final String phoneNumber;
+  final bool succeeded;
+
+  AuthenticationSmsStatus({
+    @required this.phoneNumber,
+    @required this.succeeded,
+  })  : assert(phoneNumber != null),
+        assert(phoneNumber != null);
+
+  @override
+  List<Object> get props => [phoneNumber, succeeded];
+}
diff --git a/app/lib/features/authentication/domain/entities/user_credentials.dart b/app/lib/features/authentication/domain/entities/user_credentials.dart
new file mode 100644
index 0000000..0a1d4d9
--- /dev/null
+++ b/app/lib/features/authentication/domain/entities/user_credentials.dart
@@ -0,0 +1,16 @@
+import 'package:equatable/equatable.dart';
+import 'package:meta/meta.dart';
+
+class UserCredentials extends Equatable {
+  final String userId;
+  final String userToken;
+
+  UserCredentials({
+    @required this.userId,
+    @required this.userToken,
+  })  : assert(userId != null),
+        assert(userToken != null);
+
+  @override
+  List<Object> get props => [userId, userToken];
+}
diff --git a/app/lib/features/authentication/domain/repositories/user_authentication_repository.dart b/app/lib/features/authentication/domain/repositories/user_authentication_repository.dart
new file mode 100644
index 0000000..c66082b
--- /dev/null
+++ b/app/lib/features/authentication/domain/repositories/user_authentication_repository.dart
@@ -0,0 +1,18 @@
+import 'package:dartz/dartz.dart';
+
+import '../../../../core/errors/failures.dart';
+import '../entities/authentication_sms_status.dart';
+import '../entities/user_credentials.dart';
+
+abstract class UserAuthenticationRepository {
+  Future<Either<Failure, AuthenticationSmsStatus>> sendAuthenticationSms(
+    String phoneNumber,
+  );
+
+  Future<Either<Failure, UserCredentials>> verifyAuthenticationSms(
+    String phoneNumber,
+    String code,
+  );
+
+  Future<Either<Failure, UserCredentials>> getLoggedInUser();
+}
diff --git a/app/lib/features/authentication/domain/usecases/get_logged_in_user.dart b/app/lib/features/authentication/domain/usecases/get_logged_in_user.dart
new file mode 100644
index 0000000..e8c2c71
--- /dev/null
+++ b/app/lib/features/authentication/domain/usecases/get_logged_in_user.dart
@@ -0,0 +1,17 @@
+import 'package:dartz/dartz.dart';
+
+import '../../../../core/errors/failures.dart';
+import '../../../../core/usecases/usecase.dart';
+import '../entities/user_credentials.dart';
+import '../repositories/user_authentication_repository.dart';
+
+class GetLoggedInUser extends UseCase<UserCredentials, NoParams> {
+  final UserAuthenticationRepository repository;
+
+  GetLoggedInUser(this.repository) : assert(repository != null);
+
+  @override
+  Future<Either<Failure, UserCredentials>> call(NoParams params) async {
+    return await repository.getLoggedInUser();
+  }
+}
diff --git a/app/lib/features/authentication/domain/usecases/send_authentication_sms.dart b/app/lib/features/authentication/domain/usecases/send_authentication_sms.dart
new file mode 100644
index 0000000..d38e99b
--- /dev/null
+++ b/app/lib/features/authentication/domain/usecases/send_authentication_sms.dart
@@ -0,0 +1,30 @@
+import 'package:dartz/dartz.dart';
+import 'package:equatable/equatable.dart';
+import 'package:meta/meta.dart';
+
+import '../../../../core/errors/failures.dart';
+import '../../../../core/usecases/usecase.dart';
+import '../entities/authentication_sms_status.dart';
+import '../repositories/user_authentication_repository.dart';
+
+class SendAuthenticationSms
+    extends UseCase<AuthenticationSmsStatus, SendParams> {
+  final UserAuthenticationRepository repository;
+
+  SendAuthenticationSms(this.repository) : assert(repository != null);
+
+  @override
+  Future<Either<Failure, AuthenticationSmsStatus>> call(
+      SendParams params) async {
+    return await repository.sendAuthenticationSms(params.phoneNumber);
+  }
+}
+
+class SendParams extends Equatable {
+  final String phoneNumber;
+
+  SendParams({@required this.phoneNumber});
+
+  @override
+  List<Object> get props => [phoneNumber];
+}
diff --git a/app/lib/features/authentication/domain/usecases/verify_authentication_sms.dart b/app/lib/features/authentication/domain/usecases/verify_authentication_sms.dart
new file mode 100644
index 0000000..b7585e7
--- /dev/null
+++ b/app/lib/features/authentication/domain/usecases/verify_authentication_sms.dart
@@ -0,0 +1,34 @@
+import 'package:dartz/dartz.dart';
+import 'package:equatable/equatable.dart';
+import 'package:flutter/cupertino.dart';
+
+import '../../../../core/errors/failures.dart';
+import '../../../../core/usecases/usecase.dart';
+import '../entities/user_credentials.dart';
+import '../repositories/user_authentication_repository.dart';
+
+class VerifyAuthenticationSms extends UseCase<UserCredentials, VerifyParams> {
+  final UserAuthenticationRepository repository;
+
+  VerifyAuthenticationSms(this.repository);
+
+  @override
+  Future<Either<Failure, UserCredentials>> call(VerifyParams params) async {
+    return await repository.verifyAuthenticationSms(
+        params.phoneNumber, params.code);
+  }
+}
+
+class VerifyParams extends Equatable {
+  final String phoneNumber;
+  final String code;
+
+  VerifyParams({
+    @required this.phoneNumber,
+    @required this.code,
+  })  : assert(phoneNumber != null),
+        assert(code != null);
+
+  @override
+  List<Object> get props => [phoneNumber, code];
+}
diff --git a/app/lib/features/authentication/presentation/bloc/authentication_bloc.dart b/app/lib/features/authentication/presentation/bloc/authentication_bloc.dart
new file mode 100644
index 0000000..ce911b7
--- /dev/null
+++ b/app/lib/features/authentication/presentation/bloc/authentication_bloc.dart
@@ -0,0 +1,115 @@
+import 'dart:async';
+
+import 'package:bloc/bloc.dart';
+import 'package:equatable/equatable.dart';
+import 'package:meta/meta.dart';
+
+import '../../../../core/presentation/util/input_validator.dart';
+import '../../../../core/usecases/usecase.dart';
+import '../../domain/entities/authentication_sms_status.dart';
+import '../../domain/entities/user_credentials.dart';
+import '../../domain/usecases/get_logged_in_user.dart';
+import '../../domain/usecases/send_authentication_sms.dart';
+import '../../domain/usecases/verify_authentication_sms.dart';
+
+part 'authentication_event.dart';
+part 'authentication_state.dart';
+
+class AuthenticationBloc
+    extends Bloc<AuthenticationEvent, AuthenticationState> {
+  final GetLoggedInUser getLoggedInUser;
+  final SendAuthenticationSms sendAuthenticationSms;
+  final VerifyAuthenticationSms verifyAuthenticationSms;
+  final InputValidator inputValidator;
+
+  AuthenticationBloc({
+    @required this.getLoggedInUser,
+    @required this.sendAuthenticationSms,
+    @required this.verifyAuthenticationSms,
+    @required this.inputValidator,
+  })  : assert(getLoggedInUser != null),
+        assert(sendAuthenticationSms != null),
+        assert(verifyAuthenticationSms != null),
+        assert(inputValidator != null);
+
+  @override
+  Stream<AuthenticationState> mapEventToState(
+    AuthenticationEvent event,
+  ) async* {
+    if (event is GetLoggedInUserEvent) {
+      // Getting cached user credentials
+      // On failure -> nothing is changed
+      // On success -> the AuthenticationSuccessfulState is generated
+      final cacheEither = await getLoggedInUser(NoParams());
+      yield* cacheEither.fold(
+        (failure) async* {},
+        (userCredentials) async* {
+          yield AuthenticationSuccessfulState(userCredentials: userCredentials);
+        },
+      );
+    }
+
+    if (event is StartAuthenticationEvent) {
+      yield AuthenticationPhoneInputState();
+    }
+
+    if (event is SendAuthenticationSmsEvent) {
+      final inputEither = inputValidator.stringAsPhoneNumber(event.phoneNumber);
+      // Validating user input
+      // On failure -> the AuthenticationErrorState is generated
+      // On success -> the AuthenticationLoadingState is generated while waiting
+      //               for the sendAuthenticationSms usecase to complete.
+      yield* inputEither.fold(
+        (failure) async* {
+          yield AuthenticationPhoneInputErrorState(message: failure.message);
+        },
+        (phoneNumber) async* {
+          yield AuthenticationLoadingState();
+          final responseEither = await sendAuthenticationSms(
+            SendParams(phoneNumber: phoneNumber),
+          );
+
+          // Handling usecase response
+          // On failure -> the AuthenticationErrorState is generated
+          // On success -> the AuthenticationCodeInputState is generated
+          yield* responseEither.fold(
+            (failure) async* {
+              yield AuthenticationPhoneInputErrorState(
+                  message: failure.message);
+            },
+            (authenticationSmsStatus) async* {
+              yield AuthenticationCodeInputState(
+                authenticationSmsStatus: authenticationSmsStatus,
+              );
+            },
+          );
+        },
+      );
+    }
+
+    if (event is VerifyAuthenticationSmsEvent) {
+      yield AuthenticationLoadingState();
+      final responseEither = await verifyAuthenticationSms(
+        VerifyParams(phoneNumber: event.phoneNumber, code: event.code),
+      );
+
+      // Handling usecase response
+      // On failure -> the AuthenticationErrorState is generated
+      // On success -> the AuthenticationSuccessfulState is generated
+      yield* responseEither.fold(
+        (failure) async* {
+          yield AuthenticationCodeInputErrorState(
+            phoneNumber: event.phoneNumber,
+            message: failure.message,
+          );
+        },
+        (userCredentials) async* {
+          yield AuthenticationSuccessfulState(userCredentials: userCredentials);
+        },
+      );
+    }
+  }
+
+  @override
+  AuthenticationState get initialState => AuthenticationInitialState();
+}
diff --git a/app/lib/features/authentication/presentation/bloc/authentication_event.dart b/app/lib/features/authentication/presentation/bloc/authentication_event.dart
new file mode 100644
index 0000000..a085a1b
--- /dev/null
+++ b/app/lib/features/authentication/presentation/bloc/authentication_event.dart
@@ -0,0 +1,29 @@
+part of 'authentication_bloc.dart';
+
+abstract class AuthenticationEvent extends Equatable {
+  const AuthenticationEvent();
+
+  @override
+  List<Object> get props => [];
+}
+
+class GetLoggedInUserEvent extends AuthenticationEvent {
+  GetLoggedInUserEvent();
+}
+
+class StartAuthenticationEvent extends AuthenticationEvent {
+  StartAuthenticationEvent();
+}
+
+class SendAuthenticationSmsEvent extends AuthenticationEvent {
+  final String phoneNumber;
+
+  SendAuthenticationSmsEvent(this.phoneNumber);
+}
+
+class VerifyAuthenticationSmsEvent extends AuthenticationEvent {
+  final String phoneNumber;
+  final String code;
+
+  VerifyAuthenticationSmsEvent(this.phoneNumber, this.code);
+}
diff --git a/app/lib/features/authentication/presentation/bloc/authentication_state.dart b/app/lib/features/authentication/presentation/bloc/authentication_state.dart
new file mode 100644
index 0000000..af84b76
--- /dev/null
+++ b/app/lib/features/authentication/presentation/bloc/authentication_state.dart
@@ -0,0 +1,42 @@
+part of 'authentication_bloc.dart';
+
+abstract class AuthenticationState extends Equatable {
+  const AuthenticationState();
+
+  @override
+  List<Object> get props => [];
+}
+
+class AuthenticationInitialState extends AuthenticationState {}
+
+class AuthenticationLoadingState extends AuthenticationState {}
+
+class AuthenticationPhoneInputState extends AuthenticationState {}
+
+class AuthenticationPhoneInputErrorState extends AuthenticationState {
+  final String message;
+
+  AuthenticationPhoneInputErrorState({@required this.message});
+}
+
+class AuthenticationCodeInputState extends AuthenticationState {
+  final AuthenticationSmsStatus authenticationSmsStatus;
+
+  AuthenticationCodeInputState({@required this.authenticationSmsStatus});
+}
+
+class AuthenticationCodeInputErrorState extends AuthenticationState {
+  final String phoneNumber;
+  final String message;
+
+  AuthenticationCodeInputErrorState({
+    @required this.phoneNumber,
+    @required this.message,
+  });
+}
+
+class AuthenticationSuccessfulState extends AuthenticationState {
+  final UserCredentials userCredentials;
+
+  AuthenticationSuccessfulState({@required this.userCredentials});
+}
diff --git a/app/lib/features/authentication/presentation/pages/code_input_page.dart b/app/lib/features/authentication/presentation/pages/code_input_page.dart
new file mode 100644
index 0000000..5b9bd59
--- /dev/null
+++ b/app/lib/features/authentication/presentation/pages/code_input_page.dart
@@ -0,0 +1,42 @@
+import 'package:flutter/material.dart';
+import 'package:meta/meta.dart';
+
+import '../../../../core/presentation/widgets/widgets.dart';
+import '../widgets/widgets.dart';
+
+class CodeInputPage extends StatelessWidget {
+  final String phoneNumber;
+  final String error;
+
+  const CodeInputPage({
+    Key key,
+    @required this.phoneNumber,
+    this.error,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      child: Padding(
+        padding: const EdgeInsets.all(10.0),
+        child: Column(
+          children: [
+            HeadingMessage(
+              height: MediaQuery.of(context).size.height / 4,
+              heading: 'We\'ve sent you a code',
+              subHeading:
+                  'You should receive an authentication SMS shortly on $phoneNumber. Input the code from it below.',
+            ),
+            SizedBox(height: 10.0),
+            FailureMessage(
+              height: MediaQuery.of(context).size.height / 16,
+              message: error,
+            ),
+            SizedBox(height: 20.0),
+            CodeInputForm(phoneNumber: this.phoneNumber),
+          ],
+        ),
+      ),
+    );
+  }
+}
diff --git a/app/lib/features/authentication/presentation/pages/initial_page.dart b/app/lib/features/authentication/presentation/pages/initial_page.dart
new file mode 100644
index 0000000..d0279e6
--- /dev/null
+++ b/app/lib/features/authentication/presentation/pages/initial_page.dart
@@ -0,0 +1,34 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import '../bloc/authentication_bloc.dart';
+import '../widgets/widgets.dart';
+
+class InitialPage extends StatelessWidget {
+  const InitialPage({Key key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    BlocProvider.of<AuthenticationBloc>(context).add(GetLoggedInUserEvent());
+
+    return Container(
+      child: Column(
+        children: <Widget>[
+          HeadingMessage(
+            height: MediaQuery.of(context).size.height / 2,
+            heading: 'DRUN',
+            subHeading:
+                'What sound does the drone make when it hits the window?',
+            headingStyle: Theme.of(context).textTheme.headline1,
+            subHeadingStyle: Theme.of(context).textTheme.headline2,
+          ),
+          PrimaryActionButton(
+            text: 'Continue',
+            call: () => BlocProvider.of<AuthenticationBloc>(context)
+                .add(StartAuthenticationEvent()),
+          )
+        ],
+      ),
+    );
+  }
+}
diff --git a/app/lib/features/authentication/presentation/pages/pages.dart b/app/lib/features/authentication/presentation/pages/pages.dart
new file mode 100644
index 0000000..9636688
--- /dev/null
+++ b/app/lib/features/authentication/presentation/pages/pages.dart
@@ -0,0 +1,3 @@
+export 'initial_page.dart';
+export 'code_input_page.dart';
+export 'phone_input_page.dart';
diff --git a/app/lib/features/authentication/presentation/pages/phone_input_page.dart b/app/lib/features/authentication/presentation/pages/phone_input_page.dart
new file mode 100644
index 0000000..275549d
--- /dev/null
+++ b/app/lib/features/authentication/presentation/pages/phone_input_page.dart
@@ -0,0 +1,34 @@
+import 'package:flutter/material.dart';
+
+import '../../../../core/presentation/widgets/widgets.dart';
+import '../widgets/widgets.dart';
+
+class PhoneInputPage extends StatelessWidget {
+  final String error;
+  const PhoneInputPage({Key key, this.error}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      child: Padding(
+        padding: const EdgeInsets.all(10.0),
+        child: Column(
+          children: [
+            HeadingMessage(
+              height: MediaQuery.of(context).size.height / 4,
+              heading: 'Enter your phone number',
+              subHeading: 'We will send you a SMS with an authentication code',
+            ),
+            SizedBox(height: 10.0),
+            FailureMessage(
+              height: MediaQuery.of(context).size.height / 16,
+              message: error,
+            ),
+            SizedBox(height: 20.0),
+            PhoneInputForm(),
+          ],
+        ),
+      ),
+    );
+  }
+}
diff --git a/app/lib/features/authentication/presentation/widgets/code_input_form.dart b/app/lib/features/authentication/presentation/widgets/code_input_form.dart
new file mode 100644
index 0000000..d6e07af
--- /dev/null
+++ b/app/lib/features/authentication/presentation/widgets/code_input_form.dart
@@ -0,0 +1,92 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:meta/meta.dart';
+import 'package:pin_code_fields/pin_code_fields.dart';
+
+import '../bloc/authentication_bloc.dart';
+import 'widgets.dart';
+
+class CodeInputForm extends StatefulWidget {
+  final String phoneNumber;
+
+  CodeInputForm({
+    Key key,
+    @required this.phoneNumber,
+  }) : super(key: key);
+
+  @override
+  _CodeInputFormState createState() =>
+      _CodeInputFormState(phoneNumber: this.phoneNumber);
+}
+
+class _CodeInputFormState extends State<CodeInputForm> {
+  final String phoneNumber;
+  bool isValidCode;
+  String code;
+
+  _CodeInputFormState({
+    @required this.phoneNumber,
+  });
+
+  @override
+  void initState() {
+    isValidCode = false;
+    super.initState();
+  }
+
+  void _setCode(String state) {
+    setState(() {
+      code = state;
+    });
+  }
+
+  void _setIsValidCode(bool state) {
+    setState(() {
+      isValidCode = state;
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Column(
+      children: [
+        Container(
+          child: Padding(
+            padding: const EdgeInsets.only(left: 25.0, right: 25.0),
+            child: PinCodeTextField(
+              appContext: context,
+              length: 6,
+              obscureText: false,
+              keyboardType: TextInputType.number,
+              animationType: AnimationType.fade,
+              pinTheme: PinTheme(
+                activeColor: Theme.of(context).primaryColor,
+                selectedColor: Theme.of(context).accentColor,
+                shape: PinCodeFieldShape.box,
+                borderRadius: BorderRadius.circular(5),
+                fieldHeight: 50,
+                fieldWidth: 40,
+              ),
+              animationDuration: Duration(milliseconds: 150),
+              backgroundColor: Colors.transparent,
+              enableActiveFill: false,
+              textStyle: Theme.of(context).textTheme.headline4,
+              onChanged: (code) {
+                _setIsValidCode(code.length == 6);
+                _setCode(code);
+              },
+            ),
+          ),
+        ),
+        SizedBox(height: 10.0),
+        PrimaryActionButton(
+          text: isValidCode ? 'Continue' : 'Invalid code',
+          call: isValidCode
+              ? () => BlocProvider.of<AuthenticationBloc>(context).add(
+                  VerifyAuthenticationSmsEvent(this.phoneNumber, this.code))
+              : null,
+        ),
+      ],
+    );
+  }
+}
diff --git a/app/lib/features/authentication/presentation/widgets/heading_message.dart b/app/lib/features/authentication/presentation/widgets/heading_message.dart
new file mode 100644
index 0000000..f442f95
--- /dev/null
+++ b/app/lib/features/authentication/presentation/widgets/heading_message.dart
@@ -0,0 +1,43 @@
+import 'package:flutter/material.dart';
+import 'package:meta/meta.dart';
+
+class HeadingMessage extends StatelessWidget {
+  final num height;
+  final String heading;
+  final String subHeading;
+  final TextStyle headingStyle;
+  final TextStyle subHeadingStyle;
+
+  const HeadingMessage({
+    Key key,
+    @required this.height,
+    @required this.heading,
+    @required this.subHeading,
+    this.headingStyle,
+    this.subHeadingStyle,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      height: this.height,
+      child: Column(
+        mainAxisAlignment: MainAxisAlignment.center,
+        crossAxisAlignment: CrossAxisAlignment.center,
+        children: <Widget>[
+          Text(
+            this.heading,
+            style: headingStyle ?? Theme.of(context).textTheme.headline4,
+            textAlign: TextAlign.center,
+          ),
+          SizedBox(height: 5.0),
+          Text(
+            this.subHeading,
+            style: subHeadingStyle ?? Theme.of(context).textTheme.headline5,
+            textAlign: TextAlign.center,
+          ),
+        ],
+      ),
+    );
+  }
+}
diff --git a/app/lib/features/authentication/presentation/widgets/phone_input_form.dart b/app/lib/features/authentication/presentation/widgets/phone_input_form.dart
new file mode 100644
index 0000000..eecd2f2
--- /dev/null
+++ b/app/lib/features/authentication/presentation/widgets/phone_input_form.dart
@@ -0,0 +1,81 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:flutter_sim_country_code/flutter_sim_country_code.dart';
+import 'package:intl_phone_number_input/intl_phone_number_input.dart';
+
+import 'widgets.dart';
+import '../bloc/authentication_bloc.dart';
+
+class PhoneInputForm extends StatefulWidget {
+  const PhoneInputForm({
+    Key key,
+  }) : super(key: key);
+
+  @override
+  State<StatefulWidget> createState() => _PhoneInputFormState();
+}
+
+class _PhoneInputFormState extends State<PhoneInputForm> {
+  bool isValidPhoneNumber;
+  String phoneNumber;
+
+  @override
+  void initState() {
+    isValidPhoneNumber = false;
+    super.initState();
+  }
+
+  void _setPhoneNumber(String state) {
+    setState(() {
+      phoneNumber = state;
+    });
+  }
+
+  void _setIsValidPhoneNumber(bool state) {
+    setState(() {
+      isValidPhoneNumber = state;
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      child: Column(
+        mainAxisAlignment: MainAxisAlignment.center,
+        crossAxisAlignment: CrossAxisAlignment.center,
+        children: <Widget>[
+          FutureBuilder(
+            future: FlutterSimCountryCode.simCountryCode,
+            builder: (BuildContext context, AsyncSnapshot isoCodeSnapshot) {
+              return Container(
+                child: InternationalPhoneNumberInput(
+                  selectorConfig: SelectorConfig(
+                    selectorType: PhoneInputSelectorType.BOTTOM_SHEET,
+                    useEmoji: true,
+                  ),
+                  onInputChanged: (PhoneNumber phoneNumber) {
+                    _setPhoneNumber(phoneNumber.toString());
+                  },
+                  onInputValidated: (bool isValid) => _setIsValidPhoneNumber(
+                    isValid,
+                  ),
+                  initialValue: PhoneNumber(
+                    isoCode: isoCodeSnapshot.data ?? 'BG',
+                  ),
+                ),
+              );
+            },
+          ),
+          SizedBox(height: 10.0),
+          PrimaryActionButton(
+            text: isValidPhoneNumber ? 'Continue' : 'Invalid number',
+            call: isValidPhoneNumber
+                ? () => BlocProvider.of<AuthenticationBloc>(context)
+                    .add(SendAuthenticationSmsEvent(this.phoneNumber))
+                : null,
+          ),
+        ],
+      ),
+    );
+  }
+}
diff --git a/app/lib/features/authentication/presentation/widgets/primary_action_button.dart b/app/lib/features/authentication/presentation/widgets/primary_action_button.dart
new file mode 100644
index 0000000..844966f
--- /dev/null
+++ b/app/lib/features/authentication/presentation/widgets/primary_action_button.dart
@@ -0,0 +1,35 @@
+import 'package:flutter/material.dart';
+import 'package:meta/meta.dart';
+
+class PrimaryActionButton extends StatelessWidget {
+  final String text;
+  final Function call;
+
+  const PrimaryActionButton({
+    Key key,
+    @required this.text,
+    @required this.call,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      child: RaisedButton(
+        shape: RoundedRectangleBorder(
+          borderRadius: BorderRadius.circular(20.0),
+        ),
+        color: Theme.of(context).primaryColor,
+        disabledColor: Theme.of(context).primaryColor.withOpacity(0.3),
+        onPressed: this.call,
+        child: Container(
+          padding: const EdgeInsets.all(20.0),
+          child: Text(
+            text,
+            textAlign: TextAlign.center,
+            style: Theme.of(context).textTheme.headline6,
+          ),
+        ),
+      ),
+    );
+  }
+}
diff --git a/app/lib/features/authentication/presentation/widgets/widgets.dart b/app/lib/features/authentication/presentation/widgets/widgets.dart
new file mode 100644
index 0000000..d07411d
--- /dev/null
+++ b/app/lib/features/authentication/presentation/widgets/widgets.dart
@@ -0,0 +1,4 @@
+export 'code_input_form.dart';
+export 'heading_message.dart';
+export 'phone_input_form.dart';
+export 'primary_action_button.dart';
diff --git a/app/lib/features/home/data/models/local_contact_model.dart b/app/lib/features/home/data/models/local_contact_model.dart
new file mode 100644
index 0000000..41c18a5
--- /dev/null
+++ b/app/lib/features/home/data/models/local_contact_model.dart
@@ -0,0 +1,21 @@
+import 'package:contacts_service/contacts_service.dart';
+import 'package:meta/meta.dart';
+
+import '../../domain/entities/local_contact.dart';
+
+class LocalContactModel extends LocalContact {
+  LocalContactModel({
+    @required name,
+    @required phoneNumber,
+  })  : assert(name != null),
+        assert(phoneNumber != null),
+        super(name: name, phoneNumber: phoneNumber);
+
+  @override
+  factory LocalContactModel.fromContactsService(Contact contact) {
+    return LocalContactModel(
+      name: contact.displayName,
+      phoneNumber: contact.phones.first.value,
+    );
+  }
+}
diff --git a/app/lib/features/home/data/models/registered_contact_model.dart b/app/lib/features/home/data/models/registered_contact_model.dart
new file mode 100644
index 0000000..4b6914a
--- /dev/null
+++ b/app/lib/features/home/data/models/registered_contact_model.dart
@@ -0,0 +1,20 @@
+import 'package:meta/meta.dart';
+
+import '../../domain/entities/registered_contact.dart';
+
+class RegisteredContactModel extends RegisteredContact {
+  RegisteredContactModel({
+    @required phoneNumber,
+    @required userId,
+  })  : assert(phoneNumber != null),
+        assert(userId != null),
+        super(phoneNumber: phoneNumber, userId: userId);
+
+  @override
+  factory RegisteredContactModel.fromJSON(Map<String, dynamic> json) {
+    return RegisteredContactModel(
+      phoneNumber: json['phoneNumber'],
+      userId: json['userId'],
+    );
+  }
+}
diff --git a/app/lib/features/home/data/repository/contacts_repository_impl.dart b/app/lib/features/home/data/repository/contacts_repository_impl.dart
new file mode 100644
index 0000000..cadce8c
--- /dev/null
+++ b/app/lib/features/home/data/repository/contacts_repository_impl.dart
@@ -0,0 +1,72 @@
+import 'package:DRUN/core/data/sources/network_info.dart';
+import 'package:DRUN/core/errors/exceptions.dart';
+import 'package:DRUN/features/home/data/sources/contacts_local_source.dart';
+import 'package:DRUN/features/home/data/sources/contacts_remote_source.dart';
+import 'package:dartz/dartz.dart';
+import 'package:meta/meta.dart';
+
+import '../../../../core/errors/failures.dart';
+import '../../domain/entities/complete_contact.dart';
+import '../../domain/entities/local_contact.dart';
+import '../../domain/repositories/contacts_repository.dart';
+
+class ContactsRepositoryImpl implements ContactsRepository {
+  final ContactsRemoteSource remoteSource;
+  final ContactsLocalSource localSource;
+  final NetworkInfo networkInfo;
+
+  ContactsRepositoryImpl({
+    @required this.remoteSource,
+    @required this.localSource,
+    @required this.networkInfo,
+  })  : assert(remoteSource != null),
+        assert(localSource != null),
+        assert(networkInfo != null);
+
+  @override
+  Future<Either<Failure, List<LocalContact>>> getLocalContacts() async {
+    try {
+      final localContacts = await localSource.getLocalContactsFromAddressBook();
+      return Right(localContacts);
+    } on PermissionException {
+      return Left(PermissionFailure());
+    }
+  }
+
+  @override
+  Future<Either<Failure, List<CompleteContact>>>
+      getRegisteredUsersInLocalContacts(
+    List<LocalContact> localContacts,
+  ) async {
+    if (await networkInfo.isConnected) {
+      try {
+        final phoneNumbers = localContacts.map((e) => e.phoneNumber).toList();
+        final remoteContacts = await remoteSource
+            .getRegisteredUsersFromPhonesNumbers(phoneNumbers);
+        remoteContacts.sort((a, b) => a.phoneNumber.compareTo(b.phoneNumber));
+
+        // Creating CompleteContact entities combining both local and remote data
+        final completeContacts = remoteContacts
+            .map(
+              (remoteC) => CompleteContact(
+                name: localContacts
+                    .where(
+                      (localC) => localC.phoneNumber == remoteC.phoneNumber,
+                    )
+                    .first
+                    .name,
+                phoneNumber: remoteC.phoneNumber,
+                userId: remoteC.userId,
+              ),
+            )
+            .toList();
+
+        return Right(completeContacts);
+      } on ServerException {
+        return Left(ServerFailure());
+      }
+    } else {
+      return Left(NetworkFailure());
+    }
+  }
+}
diff --git a/app/lib/features/home/data/sources/contacts_local_source.dart b/app/lib/features/home/data/sources/contacts_local_source.dart
new file mode 100644
index 0000000..d4d2cb6
--- /dev/null
+++ b/app/lib/features/home/data/sources/contacts_local_source.dart
@@ -0,0 +1,49 @@
+import 'package:contacts_service/contacts_service.dart';
+import 'package:meta/meta.dart';
+import 'package:permission_handler/permission_handler.dart';
+
+import '../../../../core/errors/exceptions.dart';
+import '../models/local_contact_model.dart';
+
+abstract class ContactsLocalSource {
+  /// Gets a list of [LocalContactModel]s from the phone's address book.
+  ///
+  /// Throws a [PermissionException] for all error codes.
+  Future<List<LocalContactModel>> getLocalContactsFromAddressBook();
+}
+
+class ContactsLocalSourceImpl implements ContactsLocalSource {
+  final PermissionHandler permissionHandler;
+  final Future<Iterable<Contact>> Function() getContacts;
+
+  ContactsLocalSourceImpl({
+    @required this.permissionHandler,
+    @required this.getContacts,
+  })  : assert(permissionHandler != null),
+        assert(getContacts != null);
+
+  @override
+  Future<List<LocalContactModel>> getLocalContactsFromAddressBook() async {
+    Map<PermissionGroup, PermissionStatus> permissions;
+    permissions = await permissionHandler.requestPermissions([
+      PermissionGroup.contacts,
+    ]);
+
+    if (permissions[PermissionGroup.contacts] == PermissionStatus.granted) {
+      final addressBook = await getContacts();
+      return addressBook
+          .where((e) {
+            try {
+              e.phones.first;
+              return true;
+            } on StateError {
+              return false;
+            }
+          })
+          .map((e) => LocalContactModel.fromContactsService(e))
+          .toList();
+    } else {
+      throw PermissionException();
+    }
+  }
+}
diff --git a/app/lib/features/home/data/sources/contacts_remote_source.dart b/app/lib/features/home/data/sources/contacts_remote_source.dart
new file mode 100644
index 0000000..9404058
--- /dev/null
+++ b/app/lib/features/home/data/sources/contacts_remote_source.dart
@@ -0,0 +1,56 @@
+import 'dart:convert';
+
+import 'package:DRUN/core/constants/constants.dart';
+import 'package:DRUN/core/errors/exceptions.dart';
+
+import '../models/registered_contact_model.dart';
+
+import 'package:http/http.dart' as http;
+import 'package:meta/meta.dart';
+
+abstract class ContactsRemoteSource {
+  /// GET to https://$SERVER_ADDRESS/api/users/check-registered endpoint.
+  ///
+  /// Throws a [ServerException] for all error codes.
+  Future<List<RegisteredContactModel>> getRegisteredUsersFromPhonesNumbers(
+    List<String> phoneNumbers,
+  );
+}
+
+class ContactsRemoteSourceImpl implements ContactsRemoteSource {
+  final http.Client httpClient;
+
+  ContactsRemoteSourceImpl({
+    @required this.httpClient,
+  }) : assert(httpClient != null);
+
+  @override
+  Future<List<RegisteredContactModel>> getRegisteredUsersFromPhonesNumbers(
+    List<String> phoneNumbers,
+  ) async {
+    final response = await httpClient.post(
+      '$SERVER_ADDRESS/api/users/check-registered',
+      body: jsonEncode({
+        'phoneNumbers': phoneNumbers,
+      }),
+      headers: {
+        'Content-Type': 'application/json',
+      },
+    );
+
+    if (response.statusCode != 200) {
+      // throw ServerException();
+      // ToDo: Double check exact phone number formatting
+      var list = List<RegisteredContactModel>();
+      list.add(RegisteredContactModel(
+        phoneNumber: '089 986 8430',
+        userId: 'aaaa',
+      ));
+      return list;
+    }
+
+    final List<Map<String, dynamic>> responseBody =
+        List<Map<String, dynamic>>.from(json.decode(response.body));
+    return responseBody.map((e) => RegisteredContactModel.fromJSON(e)).toList();
+  }
+}
diff --git a/app/lib/features/home/domain/entities/complete_contact.dart b/app/lib/features/home/domain/entities/complete_contact.dart
new file mode 100644
index 0000000..4d8ef1d
--- /dev/null
+++ b/app/lib/features/home/domain/entities/complete_contact.dart
@@ -0,0 +1,18 @@
+import 'package:meta/meta.dart';
+
+import 'contact.dart';
+
+class CompleteContact extends Contact {
+  final String name;
+  final String phoneNumber;
+  final String userId;
+
+  CompleteContact({
+    @required this.name,
+    @required this.phoneNumber,
+    @required this.userId,
+  })  : assert(name != null),
+        assert(phoneNumber != null),
+        assert(userId != null),
+        super(phoneNumber: phoneNumber);
+}
diff --git a/app/lib/features/home/domain/entities/contact.dart b/app/lib/features/home/domain/entities/contact.dart
new file mode 100644
index 0000000..8cb0026
--- /dev/null
+++ b/app/lib/features/home/domain/entities/contact.dart
@@ -0,0 +1,13 @@
+import 'package:equatable/equatable.dart';
+import 'package:meta/meta.dart';
+
+class Contact extends Equatable {
+  final String phoneNumber;
+
+  Contact({
+    @required this.phoneNumber,
+  }) : assert(phoneNumber != null);
+
+  @override
+  List<Object> get props => [phoneNumber];
+}
diff --git a/app/lib/features/home/domain/entities/local_contact.dart b/app/lib/features/home/domain/entities/local_contact.dart
new file mode 100644
index 0000000..a9bd8a2
--- /dev/null
+++ b/app/lib/features/home/domain/entities/local_contact.dart
@@ -0,0 +1,15 @@
+import 'package:meta/meta.dart';
+
+import 'contact.dart';
+
+class LocalContact extends Contact {
+  final String name;
+  final String phoneNumber;
+
+  LocalContact({
+    @required this.name,
+    @required this.phoneNumber,
+  })  : assert(name != null),
+        assert(phoneNumber != null),
+        super(phoneNumber: phoneNumber);
+}
diff --git a/app/lib/features/home/domain/entities/registered_contact.dart b/app/lib/features/home/domain/entities/registered_contact.dart
new file mode 100644
index 0000000..1986937
--- /dev/null
+++ b/app/lib/features/home/domain/entities/registered_contact.dart
@@ -0,0 +1,15 @@
+import 'package:meta/meta.dart';
+
+import 'contact.dart';
+
+class RegisteredContact extends Contact {
+  final String phoneNumber;
+  final String userId;
+
+  RegisteredContact({
+    @required this.phoneNumber,
+    @required this.userId,
+  })  : assert(phoneNumber != null),
+        assert(userId != null),
+        super(phoneNumber: phoneNumber);
+}
diff --git a/app/lib/features/home/domain/repositories/contacts_repository.dart b/app/lib/features/home/domain/repositories/contacts_repository.dart
new file mode 100644
index 0000000..265be3c
--- /dev/null
+++ b/app/lib/features/home/domain/repositories/contacts_repository.dart
@@ -0,0 +1,12 @@
+import 'package:dartz/dartz.dart';
+
+import '../../../../core/errors/failures.dart';
+import '../entities/complete_contact.dart';
+import '../entities/local_contact.dart';
+
+abstract class ContactsRepository {
+  Future<Either<Failure, List<LocalContact>>> getLocalContacts();
+
+  Future<Either<Failure, List<CompleteContact>>>
+      getRegisteredUsersInLocalContacts(List<LocalContact> localContacts);
+}
diff --git a/app/lib/features/home/domain/usecases/get_contacts.dart b/app/lib/features/home/domain/usecases/get_contacts.dart
new file mode 100644
index 0000000..c1f366c
--- /dev/null
+++ b/app/lib/features/home/domain/usecases/get_contacts.dart
@@ -0,0 +1,22 @@
+import 'package:dartz/dartz.dart';
+
+import '../../../../core/errors/failures.dart';
+import '../../../../core/usecases/usecase.dart';
+import '../entities/complete_contact.dart';
+import '../repositories/contacts_repository.dart';
+
+class GetContacts extends UseCase<List<CompleteContact>, NoParams> {
+  final ContactsRepository repository;
+
+  GetContacts(this.repository) : assert(repository != null);
+
+  @override
+  Future<Either<Failure, List<CompleteContact>>> call(NoParams params) async {
+    final localContacts = await repository.getLocalContacts();
+    return localContacts.fold(
+      (failure) => Left(failure),
+      (localContacts) async =>
+          await repository.getRegisteredUsersInLocalContacts(localContacts),
+    );
+  }
+}
diff --git a/app/lib/features/home/presentation/bloc/home_bloc.dart b/app/lib/features/home/presentation/bloc/home_bloc.dart
new file mode 100644
index 0000000..7396683
--- /dev/null
+++ b/app/lib/features/home/presentation/bloc/home_bloc.dart
@@ -0,0 +1,62 @@
+import 'dart:async';
+
+import 'package:bloc/bloc.dart';
+import 'package:equatable/equatable.dart';
+import 'package:meta/meta.dart';
+
+import '../../../../core/errors/failures.dart';
+import '../../../../core/usecases/usecase.dart';
+import '../../../authentication/domain/entities/user_credentials.dart';
+import '../../domain/entities/complete_contact.dart';
+import '../../domain/usecases/get_contacts.dart';
+
+part 'home_event.dart';
+part 'home_state.dart';
+
+class HomeBloc extends Bloc<HomeEvent, HomeState> {
+  final GetContacts getContacts;
+
+  HomeBloc({
+    @required this.getContacts,
+  }) : assert(getContacts != null);
+
+  @override
+  Stream<HomeState> mapEventToState(
+    HomeEvent event,
+  ) async* {
+    if (event is GetContactsEvent) {
+      final contactsEither = await this.getContacts(NoParams());
+      yield* contactsEither.fold(
+        (failure) async* {
+          yield HomeFailureState(message: failure.message);
+        },
+        (contacts) async* {
+          yield HomeAuthenticatedState(
+            userCredentials: event.userCredentials,
+            contacts: contacts,
+          );
+        },
+      );
+    }
+
+    if (event is HomeContactSelectedEvent) {
+      try {
+        CompleteContact selectedContact = event.contacts
+            .where(
+              (element) => element.userId == event.selectedUserId,
+            )
+            .first;
+
+        yield HomeContactSelectedState(
+          userCredentials: event.userCredentials,
+          selectedContact: selectedContact,
+        );
+      } on StateError {
+        yield HomeFailureState(message: ContactSelectionFailure().message);
+      }
+    }
+  }
+
+  @override
+  HomeState get initialState => HomeInitialState();
+}
diff --git a/app/lib/features/home/presentation/bloc/home_event.dart b/app/lib/features/home/presentation/bloc/home_event.dart
new file mode 100644
index 0000000..d054bed
--- /dev/null
+++ b/app/lib/features/home/presentation/bloc/home_event.dart
@@ -0,0 +1,26 @@
+part of 'home_bloc.dart';
+
+abstract class HomeEvent extends Equatable {
+  const HomeEvent();
+
+  @override
+  List<Object> get props => [];
+}
+
+class GetContactsEvent extends HomeEvent {
+  final UserCredentials userCredentials;
+
+  GetContactsEvent(this.userCredentials);
+}
+
+class HomeContactSelectedEvent extends HomeEvent {
+  final UserCredentials userCredentials;
+  final List<CompleteContact> contacts;
+  final String selectedUserId;
+
+  HomeContactSelectedEvent(
+    this.userCredentials,
+    this.contacts,
+    this.selectedUserId,
+  );
+}
diff --git a/app/lib/features/home/presentation/bloc/home_state.dart b/app/lib/features/home/presentation/bloc/home_state.dart
new file mode 100644
index 0000000..833730c
--- /dev/null
+++ b/app/lib/features/home/presentation/bloc/home_state.dart
@@ -0,0 +1,36 @@
+part of 'home_bloc.dart';
+
+abstract class HomeState extends Equatable {
+  const HomeState();
+
+  @override
+  List<Object> get props => [];
+}
+
+class HomeInitialState extends HomeState {}
+
+class HomeFailureState extends HomeState {
+  final String message;
+
+  HomeFailureState({@required this.message});
+}
+
+class HomeAuthenticatedState extends HomeState {
+  final UserCredentials userCredentials;
+  final List<CompleteContact> contacts;
+
+  HomeAuthenticatedState({
+    @required this.userCredentials,
+    @required this.contacts,
+  });
+}
+
+class HomeContactSelectedState extends HomeState {
+  final UserCredentials userCredentials;
+  final CompleteContact selectedContact;
+
+  HomeContactSelectedState({
+    @required this.userCredentials,
+    @required this.selectedContact,
+  });
+}
diff --git a/app/lib/features/home/presentation/pages/home_failure_page.dart b/app/lib/features/home/presentation/pages/home_failure_page.dart
new file mode 100644
index 0000000..e55d73f
--- /dev/null
+++ b/app/lib/features/home/presentation/pages/home_failure_page.dart
@@ -0,0 +1,28 @@
+import 'package:flutter/material.dart';
+import 'package:meta/meta.dart';
+
+import '../../../../core/presentation/widgets/widgets.dart';
+
+class HomeFailurePage extends StatelessWidget {
+  final String error;
+  const HomeFailurePage({Key key, @required this.error}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Column(
+      mainAxisAlignment: MainAxisAlignment.center,
+      crossAxisAlignment: CrossAxisAlignment.center,
+      children: [
+        Container(
+          child: Padding(
+            padding: const EdgeInsets.all(16.0),
+            child: FailureMessage(
+              height: MediaQuery.of(context).size.height / 16,
+              message: error,
+            ),
+          ),
+        ),
+      ],
+    );
+  }
+}
diff --git a/app/lib/features/home/presentation/pages/home_page.dart b/app/lib/features/home/presentation/pages/home_page.dart
new file mode 100644
index 0000000..84f9264
--- /dev/null
+++ b/app/lib/features/home/presentation/pages/home_page.dart
@@ -0,0 +1,34 @@
+import 'package:flutter/material.dart';
+import 'package:meta/meta.dart';
+
+import '../../../authentication/domain/entities/user_credentials.dart';
+import '../../domain/entities/complete_contact.dart';
+import '../widgets/widgets.dart';
+
+class HomePage extends StatelessWidget {
+  final UserCredentials userCredentials;
+  final List<CompleteContact> contacts;
+
+  const HomePage({
+    Key key,
+    @required this.userCredentials,
+    @required this.contacts,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: const Text('DRUN'),
+        actions: <Widget>[
+          IconButton(
+            icon: const Icon(Icons.settings),
+            tooltip: 'Settings',
+            onPressed: () {},
+          ),
+        ],
+      ),
+      body: ContactsList(contacts: contacts, userCredentials: userCredentials),
+    );
+  }
+}
diff --git a/app/lib/features/home/presentation/pages/pages.dart b/app/lib/features/home/presentation/pages/pages.dart
new file mode 100644
index 0000000..5a25ecb
--- /dev/null
+++ b/app/lib/features/home/presentation/pages/pages.dart
@@ -0,0 +1,2 @@
+export 'home_page.dart';
+export 'home_failure_page.dart';
diff --git a/app/lib/features/home/presentation/widgets/contacts_list.dart b/app/lib/features/home/presentation/widgets/contacts_list.dart
new file mode 100644
index 0000000..fca3211
--- /dev/null
+++ b/app/lib/features/home/presentation/widgets/contacts_list.dart
@@ -0,0 +1,46 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:meta/meta.dart';
+
+import '../../../authentication/domain/entities/user_credentials.dart';
+import '../../domain/entities/complete_contact.dart';
+import '../bloc/home_bloc.dart';
+
+class ContactsList extends StatelessWidget {
+  const ContactsList({
+    Key key,
+    @required this.contacts,
+    @required this.userCredentials,
+  }) : super(key: key);
+
+  final List<CompleteContact> contacts;
+  final UserCredentials userCredentials;
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      child: ListView.builder(
+        itemCount: contacts?.length ?? 0,
+        itemBuilder: (BuildContext context, int index) {
+          CompleteContact contact = this.contacts?.elementAt(index);
+          return ListTile(
+            contentPadding:
+                const EdgeInsets.symmetric(vertical: 2, horizontal: 18),
+            title: Text(contact.name ?? ''),
+            leading: CircleAvatar(
+              child: Text(contact.name[0]),
+              backgroundColor: Theme.of(context).accentColor,
+            ),
+            onTap: () => BlocProvider.of<HomeBloc>(context).add(
+              HomeContactSelectedEvent(
+                this.userCredentials,
+                this.contacts,
+                contact.userId,
+              ),
+            ),
+          );
+        },
+      ),
+    );
+  }
+}
diff --git a/app/lib/features/home/presentation/widgets/widgets.dart b/app/lib/features/home/presentation/widgets/widgets.dart
new file mode 100644
index 0000000..813fd98
--- /dev/null
+++ b/app/lib/features/home/presentation/widgets/widgets.dart
@@ -0,0 +1,1 @@
+export './contacts_list.dart';
diff --git a/app/lib/features/order/data/models/delivery_order_model.dart b/app/lib/features/order/data/models/delivery_order_model.dart
new file mode 100644
index 0000000..83709b0
--- /dev/null
+++ b/app/lib/features/order/data/models/delivery_order_model.dart
@@ -0,0 +1,50 @@
+import 'package:meta/meta.dart';
+
+import '../../domain/entities/delivery_order.dart';
+import '../../domain/entities/location.dart';
+import 'location_model.dart';
+
+class DeliveryOrderModel extends DeliveryOrder {
+  final String senderUserId;
+  final LocationCoordinates senderLocationCoordinates;
+  final String receiverUserId;
+  final LocationCoordinates receiverLocationCoordinates;
+  final String paymentCardToken;
+
+  DeliveryOrderModel({
+    @required this.senderUserId,
+    @required this.senderLocationCoordinates,
+    @required this.receiverUserId,
+    @required this.receiverLocationCoordinates,
+    @required this.paymentCardToken,
+  })  : assert(senderUserId != null),
+        assert(senderLocationCoordinates != null),
+        assert(receiverUserId != null),
+        assert(receiverLocationCoordinates != null),
+        assert(paymentCardToken != null),
+        super(
+          senderUserId: senderUserId,
+          senderLocationCoordinates: senderLocationCoordinates,
+          receiverUserId: receiverUserId,
+          receiverLocationCoordinates: receiverLocationCoordinates,
+          paymentCardToken: paymentCardToken,
+        );
+
+  Map<String, dynamic> toJSON() {
+    return {
+      'sender': {
+        'id': this.senderUserId,
+        'location': LocationCoordinatesModel.fromEntity(
+          this.senderLocationCoordinates,
+        ).toJSON(),
+      },
+      'receiver': {
+        'id': this.receiverUserId,
+        'location': LocationCoordinatesModel.fromEntity(
+          this.receiverLocationCoordinates,
+        ).toJSON(),
+      },
+      'paymentCardToken': this.paymentCardToken,
+    };
+  }
+}
diff --git a/app/lib/features/order/data/models/location_model.dart b/app/lib/features/order/data/models/location_model.dart
new file mode 100644
index 0000000..e28b384
--- /dev/null
+++ b/app/lib/features/order/data/models/location_model.dart
@@ -0,0 +1,38 @@
+import 'package:location/location.dart';
+import 'package:meta/meta.dart';
+
+import '../../domain/entities/location.dart';
+
+class LocationCoordinatesModel extends LocationCoordinates {
+  final num latitude;
+  final num longitude;
+
+  LocationCoordinatesModel({
+    @required this.latitude,
+    @required this.longitude,
+  })  : assert(latitude != null),
+        assert(longitude != null),
+        super(latitude: latitude, longitude: longitude);
+
+  factory LocationCoordinatesModel.fromEntity(
+    LocationCoordinates locationCoordinates,
+  ) {
+    return LocationCoordinatesModel(
+      latitude: locationCoordinates.latitude,
+      longitude: locationCoordinates.longitude,
+    );
+  }
+
+  factory LocationCoordinatesModel.fromLocationService(
+    LocationData locationData,
+  ) {
+    return LocationCoordinatesModel(
+      latitude: locationData.latitude,
+      longitude: locationData.longitude,
+    );
+  }
+
+  Map<String, dynamic> toJSON() {
+    return {'latitude': this.latitude, 'longitude': this.longitude};
+  }
+}
diff --git a/app/lib/features/order/data/repositories/orders_repository_impl.dart b/app/lib/features/order/data/repositories/orders_repository_impl.dart
new file mode 100644
index 0000000..ae39644
--- /dev/null
+++ b/app/lib/features/order/data/repositories/orders_repository_impl.dart
@@ -0,0 +1,59 @@
+import 'package:dartz/dartz.dart';
+import 'package:meta/meta.dart';
+
+import '../../../../core/data/sources/network_info.dart';
+import '../../../../core/errors/exceptions.dart';
+import '../../../../core/errors/failures.dart';
+import '../../domain/entities/delivery_order.dart';
+import '../../domain/entities/location.dart';
+import '../../domain/repositories/orders_repository.dart';
+import '../sources/orders_local_source.dart';
+import '../sources/orders_remote_source.dart';
+
+class OrdersRepositoryImpl implements OrdersRepository {
+  final OrdersRemoteSource remoteSource;
+  final OrdersLocalSource localSource;
+  final NetworkInfo networkInfo;
+
+  OrdersRepositoryImpl({
+    @required this.remoteSource,
+    @required this.localSource,
+    @required this.networkInfo,
+  })  : assert(remoteSource != null),
+        assert(localSource != null),
+        assert(networkInfo != null);
+
+  @override
+  Future<Either<Failure, String>> getPaymentCardToken() async {
+    try {
+      final paymentCardToken = await remoteSource.getPaymentCardToken();
+      return Right(paymentCardToken);
+    } on PaymentCardException {
+      return Left(PaymentCardFailure());
+    }
+  }
+
+  @override
+  Future<Either<Failure, LocationCoordinates>> getCurrentLocation() async {
+    try {
+      final currentLocation = await localSource.getCurrentLocationCoordinates();
+      return Right(currentLocation);
+    } on PermissionException {
+      return Left(PermissionFailure());
+    }
+  }
+
+  @override
+  Future<Either<Failure, String>> makeDeliveryOrder(DeliveryOrder order) async {
+    if (await networkInfo.isConnected) {
+      try {
+        final deliveryOrderId = await remoteSource.makeDeliveryOrder(order);
+        return Right(deliveryOrderId);
+      } on ServerException {
+        return Left(ServerFailure());
+      }
+    } else {
+      return Left(NetworkFailure());
+    }
+  }
+}
diff --git a/app/lib/features/order/data/sources/orders_local_source.dart b/app/lib/features/order/data/sources/orders_local_source.dart
new file mode 100644
index 0000000..96b5b76
--- /dev/null
+++ b/app/lib/features/order/data/sources/orders_local_source.dart
@@ -0,0 +1,46 @@
+import 'package:DRUN/core/errors/exceptions.dart';
+import 'package:DRUN/features/order/data/models/location_model.dart';
+import 'package:meta/meta.dart';
+import 'package:location/location.dart';
+
+import '../../domain/entities/location.dart';
+
+abstract class OrdersLocalSource {
+  /// Determine the current position of the device.
+  ///
+  /// Throws a [PermissionException] when the location services are not
+  /// enabled or permissions are denied.
+  Future<LocationCoordinates> getCurrentLocationCoordinates();
+}
+
+class OrdersLocalSourceImpl implements OrdersLocalSource {
+  final Location location;
+
+  OrdersLocalSourceImpl({@required this.location});
+
+  @override
+  Future<LocationCoordinates> getCurrentLocationCoordinates() async {
+    bool _serviceEnabled;
+    PermissionStatus _permissionGranted;
+    LocationData _locationData;
+
+    _serviceEnabled = await location.serviceEnabled();
+    if (!_serviceEnabled) {
+      _serviceEnabled = await location.requestService();
+      if (!_serviceEnabled) {
+        throw new LocationException();
+      }
+    }
+
+    _permissionGranted = await location.hasPermission();
+    if (_permissionGranted == PermissionStatus.denied) {
+      _permissionGranted = await location.requestPermission();
+      if (_permissionGranted != PermissionStatus.granted) {
+        throw new PermissionException();
+      }
+    }
+
+    _locationData = await location.getLocation();
+    return LocationCoordinatesModel.fromLocationService(_locationData);
+  }
+}
diff --git a/app/lib/features/order/data/sources/orders_remote_source.dart b/app/lib/features/order/data/sources/orders_remote_source.dart
new file mode 100644
index 0000000..de40ede
--- /dev/null
+++ b/app/lib/features/order/data/sources/orders_remote_source.dart
@@ -0,0 +1,41 @@
+import 'package:meta/meta.dart';
+import 'package:stripe_payment/stripe_payment.dart';
+
+import '../../../../core/errors/exceptions.dart';
+import '../models/delivery_order_model.dart';
+
+abstract class OrdersRemoteSource {
+  /// POST to https://$SERVER_ADDRESS/api/orders endpoint.
+  ///
+  /// Throws a [ServerException] for all error codes.
+  Future<String> makeDeliveryOrder(DeliveryOrderModel deliveryOrderModel);
+
+  /// Calls the Stripe API to generate a payment
+  ///
+  /// Throws a [PaymentCardException] for all errors that occur.
+  Future<String> getPaymentCardToken();
+}
+
+class OrdersRemoteSourceImpl implements OrdersRemoteSource {
+  final Future<PaymentMethod> Function() paymentRequestWithCardForm;
+
+  OrdersRemoteSourceImpl({
+    @required this.paymentRequestWithCardForm,
+  });
+
+  @override
+  Future<String> getPaymentCardToken() async {
+    try {
+      final paymentMethod = await paymentRequestWithCardForm();
+      return paymentMethod.id;
+    } catch (e) {
+      throw PaymentCardException();
+    }
+  }
+
+  @override
+  Future<String> makeDeliveryOrder(DeliveryOrderModel deliveryOrderModel) {
+    // TODO: implement makeDeliveryOrder
+    throw UnimplementedError();
+  }
+}
diff --git a/app/lib/features/order/domain/entities/delivery_order.dart b/app/lib/features/order/domain/entities/delivery_order.dart
new file mode 100644
index 0000000..d3bc995
--- /dev/null
+++ b/app/lib/features/order/domain/entities/delivery_order.dart
@@ -0,0 +1,33 @@
+import 'package:equatable/equatable.dart';
+import 'package:meta/meta.dart';
+
+import 'location.dart';
+
+class DeliveryOrder extends Equatable {
+  final String senderUserId;
+  final LocationCoordinates senderLocationCoordinates;
+  final String receiverUserId;
+  final LocationCoordinates receiverLocationCoordinates;
+  final String paymentCardToken;
+
+  DeliveryOrder({
+    @required this.senderUserId,
+    @required this.senderLocationCoordinates,
+    @required this.receiverUserId,
+    @required this.receiverLocationCoordinates,
+    @required this.paymentCardToken,
+  })  : assert(senderUserId != null),
+        assert(senderLocationCoordinates != null),
+        assert(receiverUserId != null),
+        assert(receiverLocationCoordinates != null),
+        assert(paymentCardToken != null);
+
+  @override
+  List<Object> get props => [
+        senderUserId,
+        receiverUserId,
+        receiverUserId,
+        receiverLocationCoordinates,
+        paymentCardToken,
+      ];
+}
diff --git a/app/lib/features/order/domain/entities/location.dart b/app/lib/features/order/domain/entities/location.dart
new file mode 100644
index 0000000..17c58bc
--- /dev/null
+++ b/app/lib/features/order/domain/entities/location.dart
@@ -0,0 +1,16 @@
+import 'package:equatable/equatable.dart';
+import 'package:meta/meta.dart';
+
+class LocationCoordinates extends Equatable {
+  final num latitude;
+  final num longitude;
+
+  LocationCoordinates({
+    @required this.latitude,
+    @required this.longitude,
+  })  : assert(latitude != null),
+        assert(longitude != null);
+
+  @override
+  List<Object> get props => [latitude, longitude];
+}
diff --git a/app/lib/features/order/domain/repositories/orders_repository.dart b/app/lib/features/order/domain/repositories/orders_repository.dart
new file mode 100644
index 0000000..cd016d8
--- /dev/null
+++ b/app/lib/features/order/domain/repositories/orders_repository.dart
@@ -0,0 +1,13 @@
+import 'package:dartz/dartz.dart';
+
+import '../../../../core/errors/failures.dart';
+import '../entities/delivery_order.dart';
+import '../entities/location.dart';
+
+abstract class OrdersRepository {
+  Future<Either<Failure, LocationCoordinates>> getCurrentLocation();
+
+  Future<Either<Failure, String>> getPaymentCardToken();
+
+  Future<Either<Failure, String>> makeDeliveryOrder(DeliveryOrder order);
+}
diff --git a/app/lib/features/order/domain/usecases/get_current_location_coordinates.dart b/app/lib/features/order/domain/usecases/get_current_location_coordinates.dart
new file mode 100644
index 0000000..82b276f
--- /dev/null
+++ b/app/lib/features/order/domain/usecases/get_current_location_coordinates.dart
@@ -0,0 +1,20 @@
+import 'package:dartz/dartz.dart';
+
+import '../../../../core/errors/failures.dart';
+import '../../../../core/usecases/usecase.dart';
+import '../entities/location.dart';
+import '../repositories/orders_repository.dart';
+
+class GetCurrentLocationCoordinates
+    extends UseCase<LocationCoordinates, NoParams> {
+  final OrdersRepository ordersRepository;
+
+  GetCurrentLocationCoordinates(
+    this.ordersRepository,
+  ) : assert(ordersRepository != null);
+
+  @override
+  Future<Either<Failure, LocationCoordinates>> call(NoParams params) async {
+    return await ordersRepository.getCurrentLocation();
+  }
+}
diff --git a/app/lib/features/order/domain/usecases/get_payment_card_token.dart b/app/lib/features/order/domain/usecases/get_payment_card_token.dart
new file mode 100644
index 0000000..c18854b
--- /dev/null
+++ b/app/lib/features/order/domain/usecases/get_payment_card_token.dart
@@ -0,0 +1,16 @@
+import 'package:DRUN/features/order/domain/repositories/orders_repository.dart';
+import 'package:dartz/dartz.dart';
+
+import '../../../../core/errors/failures.dart';
+import '../../../../core/usecases/usecase.dart';
+
+class GetPaymentCardToken implements UseCase<String, NoParams> {
+  final OrdersRepository ordersRepository;
+
+  GetPaymentCardToken(this.ordersRepository) : assert(ordersRepository != null);
+
+  @override
+  Future<Either<Failure, String>> call(NoParams params) async {
+    return await ordersRepository.getPaymentCardToken();
+  }
+}
diff --git a/app/lib/features/order/domain/usecases/make_delivery_order.dart b/app/lib/features/order/domain/usecases/make_delivery_order.dart
new file mode 100644
index 0000000..5a9476e
--- /dev/null
+++ b/app/lib/features/order/domain/usecases/make_delivery_order.dart
@@ -0,0 +1,30 @@
+import 'package:dartz/dartz.dart';
+import 'package:equatable/equatable.dart';
+import 'package:meta/meta.dart';
+
+import '../../../../core/errors/failures.dart';
+import '../../../../core/usecases/usecase.dart';
+import '../entities/delivery_order.dart';
+import '../repositories/orders_repository.dart';
+
+class MakeDeliveryOrder extends UseCase<String, DeliveryOrderParams> {
+  final OrdersRepository ordersRepository;
+
+  MakeDeliveryOrder(
+    this.ordersRepository,
+  ) : assert(ordersRepository != null);
+
+  @override
+  Future<Either<Failure, String>> call(DeliveryOrderParams params) async {
+    return await ordersRepository.makeDeliveryOrder(params.order);
+  }
+}
+
+class DeliveryOrderParams extends Equatable {
+  final DeliveryOrder order;
+
+  DeliveryOrderParams({@required this.order});
+
+  @override
+  List<Object> get props => [order];
+}
diff --git a/app/lib/features/order/presentation/bloc/order_bloc.dart b/app/lib/features/order/presentation/bloc/order_bloc.dart
new file mode 100644
index 0000000..39fdb13
--- /dev/null
+++ b/app/lib/features/order/presentation/bloc/order_bloc.dart
@@ -0,0 +1,71 @@
+import 'dart:async';
+
+import 'package:DRUN/core/usecases/usecase.dart';
+import 'package:DRUN/features/order/domain/entities/location.dart';
+import 'package:bloc/bloc.dart';
+import 'package:equatable/equatable.dart';
+import 'package:meta/meta.dart';
+
+import '../../../authentication/domain/entities/user_credentials.dart';
+import '../../../home/domain/entities/complete_contact.dart';
+import '../../domain/usecases/get_current_location_coordinates.dart';
+import '../../domain/usecases/get_payment_card_token.dart';
+
+part 'order_event.dart';
+part 'order_state.dart';
+
+class OrderBloc extends Bloc<OrderEvent, OrderState> {
+  final GetCurrentLocationCoordinates getCurrentLocationCoordinates;
+  final GetPaymentCardToken getPaymentCardToken;
+
+  OrderBloc({
+    @required this.getCurrentLocationCoordinates,
+    @required this.getPaymentCardToken,
+  })  : assert(getCurrentLocationCoordinates != null),
+        assert(getPaymentCardToken != null);
+
+  @override
+  Stream<OrderState> mapEventToState(
+    OrderEvent event,
+  ) async* {
+    if (event is GetSelectedUserEvent) {
+      final locationEither =
+          await this.getCurrentLocationCoordinates(NoParams());
+      yield* locationEither.fold(
+        (failure) async* {
+          yield OrderFailureState(message: failure.message);
+        },
+        (location) async* {
+          yield OrderLocationSelectState(
+            userCredentials: event.userCredentials,
+            selectedContact: event.selectedContact,
+            locationCoordinates: location,
+          );
+        },
+      );
+    }
+
+    if (event is GetPaymentCardEvent) {
+      final locationEither = await this.getPaymentCardToken(NoParams());
+
+      yield* locationEither.fold(
+        (failure) async* {
+          yield OrderFailureState(message: failure.message);
+        },
+        (location) async* {
+          yield OrderPaymentCardInputState(
+            userCredentials: event.userCredentials,
+            selectedContact: event.selectedContact,
+            senderLocationCoordinates: event.senderLocationCoordinates,
+            receiverLocationCoordinates: event.receiverLocationCoordinates,
+          );
+        },
+      );
+    }
+
+    if (event is MakeDeliveryOrderEvent) {}
+  }
+
+  @override
+  OrderState get initialState => OrderInitialState();
+}
diff --git a/app/lib/features/order/presentation/bloc/order_event.dart b/app/lib/features/order/presentation/bloc/order_event.dart
new file mode 100644
index 0000000..eecd801
--- /dev/null
+++ b/app/lib/features/order/presentation/bloc/order_event.dart
@@ -0,0 +1,48 @@
+part of 'order_bloc.dart';
+
+abstract class OrderEvent extends Equatable {
+  const OrderEvent();
+
+  @override
+  List<Object> get props => [];
+}
+
+class GetSelectedUserEvent extends OrderEvent {
+  final UserCredentials userCredentials;
+  final CompleteContact selectedContact;
+
+  GetSelectedUserEvent({
+    @required this.userCredentials,
+    @required this.selectedContact,
+  });
+}
+
+class GetPaymentCardEvent extends OrderEvent {
+  final UserCredentials userCredentials;
+  final CompleteContact selectedContact;
+  final LocationCoordinates senderLocationCoordinates;
+  final LocationCoordinates receiverLocationCoordinates;
+
+  GetPaymentCardEvent({
+    @required this.userCredentials,
+    @required this.selectedContact,
+    @required this.senderLocationCoordinates,
+    @required this.receiverLocationCoordinates,
+  });
+}
+
+class MakeDeliveryOrderEvent extends OrderEvent {
+  final UserCredentials userCredentials;
+  final CompleteContact selectedContact;
+  final LocationCoordinates senderLocationCoordinates;
+  final LocationCoordinates receiverLocationCoordinates;
+  final String paymentCardToken;
+
+  MakeDeliveryOrderEvent({
+    @required this.userCredentials,
+    @required this.selectedContact,
+    @required this.senderLocationCoordinates,
+    @required this.receiverLocationCoordinates,
+    @required this.paymentCardToken,
+  });
+}
diff --git a/app/lib/features/order/presentation/bloc/order_state.dart b/app/lib/features/order/presentation/bloc/order_state.dart
new file mode 100644
index 0000000..5bcdf80
--- /dev/null
+++ b/app/lib/features/order/presentation/bloc/order_state.dart
@@ -0,0 +1,44 @@
+part of 'order_bloc.dart';
+
+abstract class OrderState extends Equatable {
+  const OrderState();
+
+  @override
+  List<Object> get props => [];
+}
+
+class OrderInitialState extends OrderState {}
+
+class OrderLocationSelectState extends OrderState {
+  final UserCredentials userCredentials;
+  final CompleteContact selectedContact;
+  final LocationCoordinates locationCoordinates;
+
+  OrderLocationSelectState({
+    @required this.userCredentials,
+    @required this.selectedContact,
+    @required this.locationCoordinates,
+  });
+}
+
+class OrderFailureState extends OrderState {
+  final String message;
+
+  OrderFailureState({@required this.message});
+}
+
+class OrderPaymentCardInputState extends OrderState {
+  final UserCredentials userCredentials;
+  final CompleteContact selectedContact;
+  final LocationCoordinates senderLocationCoordinates;
+  final LocationCoordinates receiverLocationCoordinates;
+
+  OrderPaymentCardInputState({
+    @required this.userCredentials,
+    @required this.selectedContact,
+    @required this.senderLocationCoordinates,
+    @required this.receiverLocationCoordinates,
+  });
+}
+
+class OrderSubmittedState extends OrderState {}
diff --git a/app/lib/injection_container.dart b/app/lib/injection_container.dart
new file mode 100644
index 0000000..641e516
--- /dev/null
+++ b/app/lib/injection_container.dart
@@ -0,0 +1,194 @@
+import 'package:contacts_service/contacts_service.dart';
+import 'package:data_connection_checker/data_connection_checker.dart';
+import 'package:get_it/get_it.dart';
+import 'package:http/http.dart' as http;
+import 'package:location/location.dart';
+import 'package:permission_handler/permission_handler.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+import 'package:stripe_payment/stripe_payment.dart';
+
+import 'core/data/sources/network_info.dart';
+import 'core/presentation/util/input_validator.dart';
+import 'features/authentication/data/repositories/user_authentication_repository_impl.dart';
+import 'features/authentication/data/sources/user_authentication_local_source.dart';
+import 'features/authentication/data/sources/user_authentication_remote_source.dart';
+import 'features/authentication/domain/repositories/user_authentication_repository.dart';
+import 'features/authentication/domain/usecases/get_logged_in_user.dart';
+import 'features/authentication/domain/usecases/send_authentication_sms.dart';
+import 'features/authentication/domain/usecases/verify_authentication_sms.dart';
+import 'features/authentication/presentation/bloc/authentication_bloc.dart';
+import 'features/home/data/repository/contacts_repository_impl.dart';
+import 'features/home/data/sources/contacts_local_source.dart';
+import 'features/home/data/sources/contacts_remote_source.dart';
+import 'features/home/domain/repositories/contacts_repository.dart';
+import 'features/home/domain/usecases/get_contacts.dart';
+import 'features/home/presentation/bloc/home_bloc.dart';
+import 'features/order/presentation/bloc/order_bloc.dart';
+import 'features/order/data/repositories/orders_repository_impl.dart';
+import 'features/order/data/sources/orders_local_source.dart';
+import 'features/order/data/sources/orders_remote_source.dart';
+import 'features/order/domain/repositories/orders_repository.dart';
+import 'features/order/domain/usecases/get_current_location_coordinates.dart';
+import 'features/order/domain/usecases/get_payment_card_token.dart';
+import 'features/order/domain/usecases/make_delivery_order.dart';
+
+// Service locator
+final sl = GetIt.instance;
+
+Future<void> init() async {
+  //! Features
+  //* Authentication feature
+  await initAuthentication();
+
+  //* Home feature
+  await initHome();
+
+  //* Order feature
+  await initOrder();
+
+  //! Core
+  await initCore();
+
+  //! External dependencies
+  await initExternalDependencies();
+}
+
+Future<void> initAuthentication() async {
+  // Presentation Bloc
+  sl.registerFactory(
+    () => AuthenticationBloc(
+      getLoggedInUser: sl(),
+      sendAuthenticationSms: sl(),
+      verifyAuthenticationSms: sl(),
+      inputValidator: sl(),
+    ),
+  );
+
+  // Use cases
+  sl.registerLazySingleton(() => GetLoggedInUser(sl()));
+  sl.registerLazySingleton(() => SendAuthenticationSms(sl()));
+  sl.registerLazySingleton(() => VerifyAuthenticationSms(sl()));
+
+  // Repository
+  sl.registerLazySingleton<UserAuthenticationRepository>(
+    () => UserAuthenticationRepositoryImpl(
+      remoteSource: sl(),
+      localSource: sl(),
+      networkInfo: sl(),
+    ),
+  );
+
+  // Data sources
+  sl.registerLazySingleton<UserAuthenticationRemoteSource>(
+    () => UserAuthenticationRemoteSourceImpl(httpClient: sl()),
+  );
+
+  sl.registerLazySingleton<UserAuthenticationLocalSource>(
+    () => UserAuthenticationLocalSourceImpl(sharedPreferences: sl()),
+  );
+}
+
+Future<void> initHome() async {
+  // Presentation Bloc
+  sl.registerFactory(
+    () => HomeBloc(
+      getContacts: sl(),
+    ),
+  );
+
+  // Use cases
+  sl.registerLazySingleton(() => GetContacts(sl()));
+
+  // Repository
+  sl.registerLazySingleton<ContactsRepository>(
+    () => ContactsRepositoryImpl(
+      remoteSource: sl(),
+      localSource: sl(),
+      networkInfo: sl(),
+    ),
+  );
+
+  // Data sources
+  sl.registerLazySingleton<ContactsRemoteSource>(
+    () => ContactsRemoteSourceImpl(httpClient: sl()),
+  );
+
+  sl.registerLazySingleton<ContactsLocalSource>(
+    () => ContactsLocalSourceImpl(
+      permissionHandler: sl(),
+      getContacts: sl(),
+    ),
+  );
+}
+
+Future<void> initOrder() async {
+  // Presentation Bloc
+  sl.registerFactory(
+    () => OrderBloc(
+      getCurrentLocationCoordinates: sl(),
+      getPaymentCardToken: sl(),
+    ),
+  );
+
+  // Use cases
+  sl.registerLazySingleton(() => GetCurrentLocationCoordinates(sl()));
+  sl.registerLazySingleton(() => GetPaymentCardToken(sl()));
+  sl.registerLazySingleton(() => MakeDeliveryOrder(sl()));
+}
+
+Future<void> initCore() async {
+  // Data sources
+  sl.registerLazySingleton<NetworkInfo>(() => NetworkInfoImpl(sl()));
+
+  // Presentation utils
+  sl.registerLazySingleton(() => InputValidator());
+
+  // Repository
+  sl.registerLazySingleton<OrdersRepository>(
+    () => OrdersRepositoryImpl(
+      remoteSource: sl(),
+      localSource: sl(),
+      networkInfo: sl(),
+    ),
+  );
+
+  // Data sources
+  sl.registerLazySingleton<OrdersRemoteSource>(
+    () => OrdersRemoteSourceImpl(
+      paymentRequestWithCardForm: sl(),
+    ),
+  );
+
+  sl.registerLazySingleton<OrdersLocalSource>(
+    () => OrdersLocalSourceImpl(
+      location: sl(),
+    ),
+  );
+}
+
+Future<void> initExternalDependencies() async {
+  // Data Connection Checker
+  sl.registerLazySingleton(() => DataConnectionChecker());
+
+  // HTTP Client
+  sl.registerLazySingleton(() => http.Client());
+
+  // Shared preferences
+  final sharedPreferences = await SharedPreferences.getInstance();
+  sl.registerLazySingleton(() => sharedPreferences);
+
+  // PermissionStatus
+  sl.registerLazySingleton(() => PermissionHandler());
+
+  // ContactsService -> getContacts
+  Future<Iterable<Contact>> getContacts() => ContactsService.getContacts();
+  sl.registerLazySingleton(() => getContacts);
+
+  // Stripe -> paymentRequestWithCardForm
+  Future<PaymentMethod> paymentRequestWithCardForm() =>
+      StripePayment.paymentRequestWithCardForm(CardFormPaymentRequest());
+  sl.registerLazySingleton(() => paymentRequestWithCardForm);
+
+  // Location
+  sl.registerLazySingleton(() => Location());
+}
diff --git a/app/lib/main.dart b/app/lib/main.dart
index 6b5e667..a2caaa0 100644
--- a/app/lib/main.dart
+++ b/app/lib/main.dart
@@ -1,62 +1,94 @@
 import 'package:flutter/material.dart';
-import 'package:firebase_auth/firebase_auth.dart';
+import 'package:flutter/rendering.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
 
-import 'package:DRUN/screens/setup.dart';
-import 'package:DRUN/screens/home.dart';
+import 'core/presentation/pages/pages.dart';
+import 'features/authentication/domain/entities/user_credentials.dart';
+import 'features/authentication/presentation/bloc/authentication_bloc.dart';
+import 'features/authentication/presentation/pages/pages.dart';
+import 'features/home/presentation/bloc/home_bloc.dart';
+import 'features/home/presentation/pages/pages.dart';
+import 'injection_container.dart' as di;
+import 'injection_container.dart';
+import 'theme.dart';
 
-void main() => runApp(DRUN());
+void main() async {
+  WidgetsFlutterBinding.ensureInitialized();
+  await di.init();
+  runApp(MyApp());
+}
 
-class DRUN extends StatelessWidget {
-  // This widget is the root of your application.
+class MyApp extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
+    debugPaintSizeEnabled = false;
     return MaterialApp(
       title: 'DRUN',
-      theme: ThemeData(
-        primarySwatch: Colors.indigo,
-        accentColor: Colors.indigoAccent,
+      theme: buildLightThemeData(),
+      darkTheme: buildDarkThemeData(),
+      themeMode: ThemeMode.system,
+      home: SafeArea(
+        child: Scaffold(body: buildAuthentication(context)),
       ),
-      home: App(),
     );
   }
 }
 
-class App extends StatefulWidget {
-  App({Key key}) : super(key: key);
+BlocProvider<AuthenticationBloc> buildAuthentication(BuildContext context) {
+  return BlocProvider(
+    create: (_) => sl<AuthenticationBloc>(),
+    child: BlocBuilder<AuthenticationBloc, AuthenticationState>(
+      builder: (context, state) {
+        if (state is AuthenticationInitialState) {
+          return InitialPage();
+        } else if (state is AuthenticationLoadingState) {
+          return LoadingPage();
+        } else if (state is AuthenticationPhoneInputState) {
+          return PhoneInputPage();
+        } else if (state is AuthenticationPhoneInputErrorState) {
+          return PhoneInputPage(error: state.message);
+        } else if (state is AuthenticationCodeInputState) {
+          return CodeInputPage(
+            phoneNumber: state.authenticationSmsStatus.phoneNumber,
+          );
+        } else if (state is AuthenticationCodeInputErrorState) {
+          return CodeInputPage(
+            phoneNumber: state.phoneNumber,
+            error: state.message,
+          );
+        } else if (state is AuthenticationSuccessfulState) {
+          return buildHome(state.userCredentials);
+        }
 
-  @override
-  _AppState createState() => _AppState();
+        return Placeholder();
+      },
+    ),
+  );
 }
 
-class _AppState extends State<App> {
-  final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
-
-  FirebaseUser user;
-
-  @override
-  void initState() {
-    super.initState();
-    getCurrentUser();
-  }
-
-  Future<FirebaseUser> getCurrentUser() async {
-    FirebaseUser _user = await _firebaseAuth.currentUser();
-    return _user;
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    return FutureBuilder(
-      future: getCurrentUser(),
-      builder: (context, AsyncSnapshot<FirebaseUser> userSnapshot) {
-        if (userSnapshot.hasData) {
-          return HomeScreen(
-            user: userSnapshot.data,
+BlocProvider<HomeBloc> buildHome(UserCredentials userCredentials) {
+  return BlocProvider(
+    create: (_) => sl<HomeBloc>(),
+    child: BlocBuilder<HomeBloc, HomeState>(
+      builder: (context, state) {
+        print(state);
+        if (state is HomeInitialState) {
+          BlocProvider.of<HomeBloc>(context)
+              .add(GetContactsEvent(userCredentials));
+          return LoadingPage();
+        } else if (state is HomeAuthenticatedState) {
+          return HomePage(
+            userCredentials: state.userCredentials,
+            contacts: state.contacts,
           );
-        } else {
-          return SetupScreen();
+        } else if (state is HomeFailureState) {
+          return HomeFailurePage(error: state.message);
+        } else if (state is HomeContactSelectedState) {
+          return Placeholder();
         }
+
+        return Placeholder();
       },
-    );
-  }
+    ),
+  );
 }
diff --git a/app/lib/screens/home.dart b/app/lib/screens/home.dart
deleted file mode 100644
index a09b391..0000000
--- a/app/lib/screens/home.dart
+++ /dev/null
@@ -1,151 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:firebase_auth/firebase_auth.dart';
-import 'package:cloud_firestore/cloud_firestore.dart';
-import 'package:contacts_service/contacts_service.dart';
-import 'package:permission_handler/permission_handler.dart';
-import 'package:DRUN/screens/messages.dart';
-
-class HomeScreen extends StatefulWidget {
-  final FirebaseUser user;
-
-  HomeScreen({Key key, @required this.user})
-      : assert(user != null),
-        super(key: key);
-
-  @override
-  _HomeScreenState createState() => _HomeScreenState(user);
-}
-
-class _HomeScreenState extends State<HomeScreen> {
-  Iterable<Contact> _contacts;
-  PermissionStatus _permissionStatus;
-  final FirebaseUser user;
-
-  _HomeScreenState(this.user)
-      : assert(user != null),
-        super();
-
-  @override
-  void initState() {
-    getPermission();
-    getContacts();
-    super.initState();
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    if (_permissionStatus == PermissionStatus.denied) {
-      _permissionNotGrantedScreen(context);
-    }
-
-    return Scaffold(
-      appBar: AppBar(
-        title: (Text('Contacts')),
-      ),
-      body: _contacts != null
-          ? _contactsScreen()
-          : Center(child: const CircularProgressIndicator()),
-    );
-  }
-
-  Widget _contactsScreen() {
-    return ListView.builder(
-      itemCount: _contacts?.length ?? 0,
-      itemBuilder: (BuildContext context, int index) {
-        Contact contact = _contacts?.elementAt(index);
-        return ListTile(
-          contentPadding:
-              const EdgeInsets.symmetric(vertical: 2, horizontal: 18),
-          leading: (contact.avatar != null && contact.avatar.isNotEmpty)
-              ? CircleAvatar(
-                  backgroundImage: MemoryImage(contact.avatar),
-                )
-              : CircleAvatar(
-                  child: Text(contact.initials()),
-                  backgroundColor: Theme.of(context).accentColor,
-                ),
-          title: Text(contact.displayName ?? ''),
-          onTap: () => Navigator.push(
-            context,
-            MaterialPageRoute(
-              builder: (context) {
-                return MessagesScreen(user: user, peer: contact);
-              },
-            ),
-          ),
-        );
-      },
-    );
-  }
-
-  Future<void> getPermission() async {
-    final PermissionStatus permission = await Permission.contacts.status;
-    if (permission != PermissionStatus.granted &&
-        permission != PermissionStatus.denied) {
-      final Map<Permission, PermissionStatus> permissionStatus =
-          await [Permission.contacts].request();
-      return permissionStatus[Permission.contacts] ??
-          PermissionStatus.undetermined;
-    } else {
-      setState(() {
-        _permissionStatus = permission;
-      });
-    }
-  }
-
-  Future<void> getContacts() async {
-    List<String> contactNumbers = [];
-    Iterable<Contact> contacts = await ContactsService.getContacts();
-    await Firestore.instance.collection('users').getDocuments().then((QuerySnapshot snapshot) => {
-      snapshot.documents.forEach((contact) {
-        final String number = contact.data['phone'].toString();
-        contactNumbers.add(number.substring(4));
-      })
-    });
-
-    contacts = contacts.where((contact) {
-            try {
-              String contactPhoneNumber;
-              if (contact.phones.first.value[0] == '0') {
-                contactPhoneNumber = contact.phones.first.value.substring(1);
-              } else {
-                if (contact.phones.first.value[0] == '+') {
-                  contactPhoneNumber = contact.phones.first.value.substring(4);
-                } else {
-                  return false;
-                }
-              }
-
-              contactPhoneNumber =
-                  contactPhoneNumber.replaceAll(new RegExp(r"\s+"), "");
-
-              return contactNumbers.contains(contactPhoneNumber);
-            } catch(ex) {
-              return false;
-            }
-            }
-
-    );
-
-
-    setState(() {
-      _contacts = contacts;
-    });
-  }
-
-  void _permissionNotGrantedScreen(BuildContext context) {
-    showDialog(
-        context: context,
-        builder: (BuildContext context) => AlertDialog(
-              title: Text('Permissions error'),
-              content: Text('Please enable contacts access '
-                  'permission in system settings'),
-              actions: <Widget>[
-                RaisedButton(
-                  child: Text('OK'),
-                  onPressed: () => Navigator.of(context).pop(),
-                )
-              ],
-            ));
-  }
-}
diff --git a/app/lib/screens/messages.dart b/app/lib/screens/messages.dart
deleted file mode 100644
index 88d4ad5..0000000
--- a/app/lib/screens/messages.dart
+++ /dev/null
@@ -1,314 +0,0 @@
-import 'package:cloud_firestore/cloud_firestore.dart';
-import 'package:firebase_auth/firebase_auth.dart';
-import 'package:flutter/material.dart';
-import 'package:fluttertoast/fluttertoast.dart';
-import 'package:contacts_service/contacts_service.dart';
-
-class MessagesScreen extends StatelessWidget {
-  final FirebaseUser user;
-  final Contact peer;
-
-  MessagesScreen({Key key, @required this.user, @required this.peer})
-      : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    return Scaffold(
-      appBar: AppBar(
-        title: Text(
-          'CHAT',
-          // style: TextStyle(color: primaryColor, fontWeight: FontWeight.bold),
-        ),
-        centerTitle: true,
-      ),
-      body: ChatScreen(
-        user: user,
-        peer: peer,
-      ),
-    );
-  }
-}
-
-
-class ChatScreen extends StatefulWidget {
-  final FirebaseUser user;
-  final Contact peer;
-
-  ChatScreen({Key key, @required this.user, this.peer})
-      : assert(user != null, peer != null),
-        super(key: key);
-
-
-  @override
-  State createState() =>
-      _ChatScreenState(user: user, peer: peer);
-}
-
-class _ChatScreenState extends State<ChatScreen> {
-  final FirebaseUser user;
-  final Contact peer;
-  String peerId;
-  String groupChatId;
-
-  List<DocumentSnapshot> listMessage = new List.from([]);
-  int _limit = 20;
-  final int _limitIncrement = 20;
-
-  final TextEditingController textEditingController = TextEditingController();
-  final ScrollController listScrollController = ScrollController();
-  final FocusNode focusNode = FocusNode();
-
-  _scrollListener() {
-    if (listScrollController.offset >=
-        listScrollController.position.maxScrollExtent &&
-        !listScrollController.position.outOfRange) {
-      print("reach the bottom");
-      setState(() {
-        print("reach the bottom");
-        _limit += _limitIncrement;
-      });
-    }
-    if (listScrollController.offset <=
-        listScrollController.position.minScrollExtent &&
-        !listScrollController.position.outOfRange) {
-      print("reach the top");
-      setState(() {
-        print("reach the top");
-      });
-    }
-  }
-
-  @override
-  void initState() async {
-    super.initState();
-    // focusNode.addListener(onFocusChange);
-    listScrollController.addListener(_scrollListener);
-
-    Firestore.instance.collection('users').getDocuments().then((QuerySnapshot snapshot) => {
-      snapshot.documents.forEach((storageContact) {
-        String currentPhoneNumber = peer.phones.first.value;
-        String storageNumber = storageContact.data['phone'].toString();
-
-
-        if (currentPhoneNumber[0] == "+") {
-          currentPhoneNumber = currentPhoneNumber.substring(4);
-        } else {
-          currentPhoneNumber = currentPhoneNumber.substring(1);
-        }
-
-        if (currentPhoneNumber.replaceAll(new RegExp(r"\s+"), "") == storageNumber.substring(4)) {
-          peerId = storageContact.data['uid'].toString();
-          groupChatId = peerId;
-          return;
-        }
-      })
-    }).then((value) => setState(() {}));
-  }
-
-  _ChatScreenState({this.user, this.peer})
-      : assert(user != null, peer != null),
-        super() ;
-
-  final CollectionReference messages =
-      Firestore.instance.collection('messages');
-
-  @override
-  Widget build(BuildContext context) {
-    return Container(
-      child: Stack(
-        children: <Widget>[
-          Column(
-            children: <Widget>[
-              // List of messages
-              buildListMessage(),
-
-              // Input content
-              buildInput(),
-            ],
-          ),
-        ],
-      ),
-    );
-  }
-
-  void onSendMessage(String content, int type) {
-    // type: 0 = text, 1 = image, 2 = sticker
-    if (content.trim() != '') {
-      textEditingController.clear();
-
-      var documentReference = Firestore.instance
-          .collection('messages')
-          .document(groupChatId)
-          .collection(groupChatId)
-          .document(DateTime.now().millisecondsSinceEpoch.toString());
-
-      Firestore.instance.runTransaction((transaction) async {
-        transaction.set(
-          documentReference,
-          {
-            'idFrom': user.uid,
-            'idTo': peerId,
-            'timestamp': DateTime.now().millisecondsSinceEpoch.toString(),
-            'content': content,
-            'type': type
-          },
-        );
-      });
-      listScrollController.animateTo(0.0,
-          duration: Duration(milliseconds: 300), curve: Curves.easeOut);
-    } else {
-      Fluttertoast.showToast(
-          msg: 'Nothing to send',
-          backgroundColor: Colors.black,
-          textColor: Colors.red);
-    }
-  }
-
-  Widget buildInput() {
-    return Container(
-      child: Row(
-        children: <Widget>[
-          // Edit text
-          Flexible(
-            child: Container(
-              child: TextField(
-                onSubmitted: (value) {
-                  onSendMessage(textEditingController.text, 0);
-                },
-                style: TextStyle(fontSize: 15.0),
-                controller: textEditingController,
-                decoration: InputDecoration.collapsed(
-                  hintText: 'Type your message...',
-                  hintStyle: TextStyle(color: Colors.grey),
-                ),
-                focusNode: focusNode,
-              ),
-            ),
-          ),
-
-          // Button send message
-          Material(
-            child: Container(
-              margin: EdgeInsets.symmetric(horizontal: 8.0),
-              child: IconButton(
-                icon: Icon(Icons.send),
-                onPressed: () => onSendMessage(textEditingController.text, 0),
-              ),
-            ),
-            color: Colors.white,
-          ),
-        ],
-      ),
-      width: double.infinity,
-      height: 50.0,
-      decoration: BoxDecoration(
-          border: Border(top: BorderSide(color: Colors.grey, width: 0.5)),
-          color: Colors.white),
-    );
-  }
-
-  Widget buildListMessage() {
-    return Flexible(
-      child: groupChatId == ''
-          ? Center(
-          child: CircularProgressIndicator())
-          : StreamBuilder(
-        stream: Firestore.instance
-            .collection('messages')
-            .document(groupChatId)
-            .collection(groupChatId)
-            .orderBy('timestamp', descending: true)
-            .limit(_limit)
-            .snapshots(),
-        builder: (context, snapshot) {
-          if (!snapshot.hasData) {
-            return Center(child: CircularProgressIndicator());
-          } else {
-            listMessage.addAll(snapshot.data.documents);
-            return ListView.builder(
-              padding: EdgeInsets.all(10.0),
-              itemBuilder: (context, index) =>
-                  buildItem(index, snapshot.data.documents[index]),
-              itemCount: snapshot.data.documents.length,
-              reverse: true,
-              controller: listScrollController,
-            );
-          }
-        },
-      ),
-    );
-  }
-
-
-  Widget buildItem(int index, DocumentSnapshot document) {
-    if (document.data['idFrom'] == user.uid) {
-      // Right (my message)
-      return Row(
-        children: <Widget>[
-          // Text
-              Container(
-            child: Text(
-              document.data['content'],
-            ),
-            padding: EdgeInsets.fromLTRB(15.0, 10.0, 15.0, 10.0),
-            width: 200.0,
-            decoration: BoxDecoration(
-                color: Colors.grey,
-                borderRadius: BorderRadius.circular(8.0)),
-            margin: EdgeInsets.only(
-                bottom: isLastMessageRight(index) ? 20.0 : 10.0,
-                right: 10.0),
-          )
-        ],
-        mainAxisAlignment: MainAxisAlignment.end,
-      );
-    } else {
-      // Left (peer message)
-      return Container(
-        child: Column(
-          children: <Widget>[
-            Row(
-              children: <Widget>[
-                Container(
-                  child: Text(
-                    document.data['content'],
-                    style: TextStyle(color: Colors.white),
-                  ),
-                  padding: EdgeInsets.fromLTRB(15.0, 10.0, 15.0, 10.0),
-                  width: 200.0,
-                  decoration: BoxDecoration(
-                      borderRadius: BorderRadius.circular(8.0)),
-                  margin: EdgeInsets.only(left: 10.0),
-                )
-              ],
-            ),
-          ],
-          crossAxisAlignment: CrossAxisAlignment.start,
-        ),
-        margin: EdgeInsets.only(bottom: 10.0),
-      );
-    }
-  }
-
-  bool isLastMessageLeft(int index) {
-    if ((index > 0 &&
-        listMessage != null &&
-        listMessage[index - 1].data['idFrom'] == user.uid) ||
-        index == 0) {
-      return true;
-    } else {
-      return false;
-    }
-  }
-
-  bool isLastMessageRight(int index) {
-    if ((index > 0 &&
-        listMessage != null &&
-        listMessage[index - 1].data['idFrom'] != user.uid) ||
-        index == 0) {
-      return true;
-    } else {
-      return false;
-    }
-  }
-}
diff --git a/app/lib/screens/otp.dart b/app/lib/screens/otp.dart
deleted file mode 100644
index 31440b6..0000000
--- a/app/lib/screens/otp.dart
+++ /dev/null
@@ -1,236 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:fluttertoast/fluttertoast.dart';
-import 'package:firebase_auth/firebase_auth.dart';
-import 'package:cloud_firestore/cloud_firestore.dart';
-
-import 'package:DRUN/screens/home.dart';
-import 'package:DRUN/utils/otp.dart';
-
-class OTPScreen extends StatefulWidget {
-  final String phoneNumber;
-  OTPScreen({
-    Key key,
-    @required this.phoneNumber,
-  })  : assert(phoneNumber != null),
-        super(key: key);
-
-  @override
-  _OTPScreenState createState() => _OTPScreenState();
-}
-
-class _OTPScreenState extends State<OTPScreen> {
-  final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
-
-  /// Control the input text field.
-  TextEditingController _pinEditingController = TextEditingController();
-
-  /// Decorate the outside of the Pin.
-  PinDecoration _pinDecoration =
-      UnderlineDecoration(enteredColor: Colors.black, hintText: '333333');
-
-  bool isCodeSent = false;
-  String _verificationId;
-
-  @override
-  void initState() {
-    super.initState();
-    _onVerifyCode();
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    return Scaffold(
-      backgroundColor: Colors.white,
-      body: Center(
-        child: Padding(
-          padding: EdgeInsets.only(top: 60, right: 40, left: 40),
-          child: Column(
-            children: <Widget>[
-              Padding(
-                padding: EdgeInsets.only(bottom: 10),
-                child: Text(
-                  'Confirm number ownership',
-                  style: TextStyle(
-                      fontSize: 28, color: Theme.of(context).primaryColor),
-                ),
-              ),
-              Padding(
-                padding: EdgeInsets.only(bottom: 30),
-                child: Text(
-                  'We\'ve sent an authentication code to ${widget.phoneNumber}.',
-                  style: TextStyle(
-                      fontSize: 16,
-                      fontWeight: FontWeight.normal,
-                      color: Colors.grey),
-                ),
-              ),
-              Padding(
-                padding: const EdgeInsets.all(16.0),
-                child: PinInputTextField(
-                  pinLength: 6,
-                  decoration: _pinDecoration,
-                  controller: _pinEditingController,
-                  autoFocus: true,
-                  textInputAction: TextInputAction.done,
-                  onSubmit: (pin) {
-                    if (pin.length == 6) {
-                      _onFormSubmitted();
-                    } else {
-                      showToast("Invalid OTP", Colors.red);
-                    }
-                  },
-                ),
-              ),
-              Container(
-                padding: EdgeInsets.all(16),
-                width: MediaQuery.of(context).size.width,
-                margin: const EdgeInsets.only(left: 70.0, right: 70.0),
-                alignment: Alignment.center,
-                child: new Row(
-                  children: <Widget>[
-                    new Expanded(
-                      child: new FlatButton(
-                        shape: new RoundedRectangleBorder(
-                            borderRadius: new BorderRadius.circular(30.0)),
-                        color: Theme.of(context).primaryColor,
-                        onPressed: () {
-                          if (_pinEditingController.text.length == 6) {
-                            _onFormSubmitted();
-                          } else {
-                            showToast("Invalid OTP", Colors.red);
-                          }
-                        },
-                        child: new Container(
-                          padding: const EdgeInsets.symmetric(
-                            vertical: 20.0,
-                            horizontal: 20.0,
-                          ),
-                          child: Text(
-                            "Continue",
-                            textAlign: TextAlign.center,
-                            style: TextStyle(
-                                color: Colors.white,
-                                fontWeight: FontWeight.bold),
-                          ),
-                        ),
-                      ),
-                    ),
-                  ],
-                ),
-              ),
-            ],
-          ),
-        ),
-      ),
-    );
-  }
-
-  void showToast(message, Color color) {
-    Fluttertoast.showToast(
-      msg: message,
-      toastLength: Toast.LENGTH_LONG,
-      gravity: ToastGravity.CENTER,
-      timeInSecForIosWeb: 2,
-      backgroundColor: color,
-      textColor: Colors.white,
-      fontSize: 16.0,
-    );
-  }
-
-  void _onVerifyCode() async {
-    setState(() {
-      isCodeSent = true;
-    });
-    final PhoneVerificationCompleted verificationCompleted =
-        (AuthCredential phoneAuthCredential) {
-      _firebaseAuth
-          .signInWithCredential(phoneAuthCredential)
-          .then((AuthResult value) {
-        if (value.user != null) {
-          Navigator.pushAndRemoveUntil(
-              context,
-              MaterialPageRoute(
-                builder: (context) => HomeScreen(
-                  user: value.user,
-                ),
-              ),
-              (Route<dynamic> route) => false);
-        } else {
-          showToast("Error validating OTP, try again", Colors.red);
-        }
-      }).catchError((error) {
-        showToast("Try again in sometime", Colors.red);
-      });
-    };
-    final PhoneVerificationFailed verificationFailed =
-        (AuthException authException) {
-      showToast(authException.message, Colors.red);
-      setState(() {
-        isCodeSent = false;
-      });
-    };
-
-    final PhoneCodeSent codeSent =
-        (String verificationId, [int forceResendingToken]) async {
-      _verificationId = verificationId;
-      setState(() {
-        _verificationId = verificationId;
-      });
-    };
-    final PhoneCodeAutoRetrievalTimeout codeAutoRetrievalTimeout =
-        (String verificationId) {
-      _verificationId = verificationId;
-      setState(() {
-        _verificationId = verificationId;
-      });
-    };
-
-    await _firebaseAuth.verifyPhoneNumber(
-        phoneNumber: widget.phoneNumber,
-        timeout: const Duration(seconds: 60),
-        verificationCompleted: verificationCompleted,
-        verificationFailed: verificationFailed,
-        codeSent: codeSent,
-        codeAutoRetrievalTimeout: codeAutoRetrievalTimeout);
-  }
-
-  void _onFormSubmitted() async {
-    AuthCredential _authCredential = PhoneAuthProvider.getCredential(
-        verificationId: _verificationId, smsCode: _pinEditingController.text);
-
-    _firebaseAuth
-        .signInWithCredential(_authCredential)
-        .then((AuthResult value) async {
-      if (value.user != null) {
-        final QuerySnapshot result = await Firestore.instance
-            .collection('users')
-            .where('id', isEqualTo: value.user.uid)
-            .getDocuments();
-        final List<DocumentSnapshot> documents = result.documents;
-        if (documents.length == 0) {
-          Firestore.instance
-              .collection("users")
-              .document(value.user.uid)
-              .setData(
-            {
-              'uid': value.user.uid,
-              'phone': value.user.phoneNumber,
-            },
-          );
-        }
-        Navigator.pushAndRemoveUntil(
-            context,
-            MaterialPageRoute(
-              builder: (context) => HomeScreen(
-                user: value.user,
-              ),
-            ),
-            (Route<dynamic> route) => false);
-      } else {
-        showToast("Invalid code, try again", Colors.red);
-      }
-    }).catchError((error) {
-      showToast("Something went wrong: ${error.toString()}", Colors.red);
-    });
-  }
-}
diff --git a/app/lib/screens/phone_auth.dart b/app/lib/screens/phone_auth.dart
deleted file mode 100644
index bcddc2c..0000000
--- a/app/lib/screens/phone_auth.dart
+++ /dev/null
@@ -1,129 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:intl_phone_number_input/intl_phone_number_input.dart';
-import 'package:flutter_sim_country_code/flutter_sim_country_code.dart';
-
-import 'package:DRUN/screens/otp.dart';
-
-class PhoneAuthScreen extends StatefulWidget {
-  PhoneAuthScreen({Key key}) : super(key: key);
-
-  @override
-  _PhoneAuthScreenState createState() => _PhoneAuthScreenState();
-}
-
-class _PhoneAuthScreenState extends State<PhoneAuthScreen> {
-  bool isValid = false;
-  String phoneNumber;
-
-  @override
-  Widget build(BuildContext context) {
-    return FutureBuilder(
-      future: FlutterSimCountryCode.simCountryCode,
-      builder: (BuildContext context, AsyncSnapshot snapshot) {
-        Column column;
-        if (snapshot.connectionState == ConnectionState.waiting) {
-          column = Column(
-            children: <Widget>[
-              SizedBox(
-                child: CircularProgressIndicator(),
-                width: 60,
-                height: 60,
-              ),
-              const Padding(
-                padding: EdgeInsets.only(top: 16),
-                child: Text('Loading...'),
-              ),
-            ],
-          );
-        } else {
-          column = Column(
-            children: <Widget>[
-              Padding(
-                padding: EdgeInsets.only(bottom: 10),
-                child: Text(
-                  'Enter your phone number',
-                  style: TextStyle(
-                      fontSize: 28, color: Theme.of(context).primaryColor),
-                ),
-              ),
-              Padding(
-                padding: EdgeInsets.only(bottom: 30),
-                child: Text(
-                  'We will send you a SMS with a verification code',
-                  style: TextStyle(
-                    fontSize: 16,
-                    fontWeight: FontWeight.normal,
-                    color: Colors.grey,
-                  ),
-                ),
-              ),
-              Padding(
-                padding: EdgeInsets.only(bottom: 0),
-                child: InternationalPhoneNumberInput(
-                  onInputChanged: (PhoneNumber phoneNumber) =>
-                      this.phoneNumber = phoneNumber.toString(),
-                  onInputValidated: (bool isValid) => this.isValid = isValid,
-                  ignoreBlank: true,
-                  autoValidate: true,
-                  initialCountry2LetterCode: snapshot.data,
-                ),
-              ),
-              Container(
-                padding: EdgeInsets.all(16),
-                width: MediaQuery.of(context).size.width,
-                margin: const EdgeInsets.only(left: 70.0, right: 70.0),
-                alignment: Alignment.center,
-                child: new Row(
-                  children: <Widget>[
-                    new Expanded(
-                      child: new FlatButton(
-                        shape: new RoundedRectangleBorder(
-                            borderRadius: new BorderRadius.circular(30.0)),
-                        color: !isValid
-                            ? Theme.of(context).primaryColor.withOpacity(0.6)
-                            : Theme.of(context).primaryColor,
-                        onPressed: () => Navigator.push(
-                          context,
-                          MaterialPageRoute(
-                            builder: (context) => OTPScreen(
-                              phoneNumber: phoneNumber,
-                            ),
-                          ),
-                        ),
-                        child: new Container(
-                          padding: const EdgeInsets.symmetric(
-                            vertical: 20.0,
-                            horizontal: 20.0,
-                          ),
-                          child: Text(
-                            !isValid ? "Enter a phone number" : "Continue",
-                            textAlign: TextAlign.center,
-                            style: TextStyle(
-                                color: Colors.white,
-                                fontWeight: FontWeight.bold),
-                          ),
-                        ),
-                      ),
-                    ),
-                  ],
-                ),
-              ),
-            ],
-          );
-        }
-        return new Scaffold(
-          backgroundColor: Colors.white,
-          body: Center(
-            child: Padding(
-              padding: EdgeInsets.only(top: 60, right: 40, left: 40),
-              child: Container(
-                height: MediaQuery.of(context).size.height,
-                child: column,
-              ),
-            ),
-          ),
-        );
-      },
-    );
-  }
-}
diff --git a/app/lib/screens/settings.dart b/app/lib/screens/settings.dart
deleted file mode 100644
index 0d327b3..0000000
--- a/app/lib/screens/settings.dart
+++ /dev/null
@@ -1,8 +0,0 @@
-import 'package:flutter/material.dart';
-
-class SettingsScreen extends StatelessWidget {
-  @override
-  Widget build(BuildContext context) {
-    return Material(child: Container());
-  }
-}
diff --git a/app/lib/screens/setup.dart b/app/lib/screens/setup.dart
deleted file mode 100644
index f32f239..0000000
--- a/app/lib/screens/setup.dart
+++ /dev/null
@@ -1,148 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:video_player/video_player.dart';
-
-import 'package:DRUN/screens/phone_auth.dart';
-
-class SetupScreen extends StatefulWidget {
-  @override
-  _SetupScreenState createState() => new _SetupScreenState();
-}
-
-class _SetupScreenState extends State<SetupScreen>
-    with TickerProviderStateMixin {
-  VideoPlayerController _videoController;
-
-  @override
-  void initState() {
-    super.initState();
-    _videoController =
-        VideoPlayerController.asset('assets/setup_background.mp4')
-          ..initialize().then(
-            (_) {
-              setState(() {
-                _videoController.play();
-                _videoController.setLooping(true);
-              });
-            },
-          );
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    return Material(
-      child: new Stack(
-        children: <Widget>[
-          Container(
-            child: SizedBox.expand(
-              child: FittedBox(
-                fit: BoxFit.cover,
-                child: SizedBox(
-                  width: _videoController.value.size?.width ?? 0,
-                  height: _videoController.value.size?.height ?? 0,
-                  child: VideoPlayer(_videoController),
-                ),
-              ),
-            ),
-          ),
-          Container(
-            decoration: BoxDecoration(
-              color: Theme.of(context).primaryColor.withOpacity(0.7),
-            ),
-          ),
-          Container(
-            decoration: BoxDecoration(
-              gradient: LinearGradient(
-                begin: FractionalOffset.topCenter,
-                end: FractionalOffset.bottomCenter,
-                colors: [
-                  Colors.black.withOpacity(0.5),
-                  Colors.grey.withOpacity(0.0),
-                  Colors.black.withOpacity(0.5),
-                ],
-                stops: [0.0, 0.5, 1.0],
-              ),
-            ),
-          ),
-          Container(
-            padding: EdgeInsets.only(top: 150.0),
-            child: Column(
-              children: <Widget>[
-                Container(
-                  padding: EdgeInsets.only(top: 20.0),
-                  child: new Column(
-                    mainAxisAlignment: MainAxisAlignment.center,
-                    children: <Widget>[
-                      Text(
-                        "DRUN",
-                        style: TextStyle(
-                            color: Colors.white,
-                            fontSize: 100.0,
-                            fontWeight: FontWeight.bold),
-                      ),
-                      Text(
-                        "Delivery, but with drones",
-                        style: TextStyle(
-                            color: Colors.white70,
-                            fontSize: 25.0,
-                            fontWeight: FontWeight.normal),
-                      ),
-                    ],
-                  ),
-                ),
-                Container(
-                  padding: EdgeInsets.only(top: 120.0),
-                  width: MediaQuery.of(context).size.width,
-                  margin: const EdgeInsets.only(left: 70.0, right: 70.0),
-                  alignment: Alignment.center,
-                  child: new Row(
-                    children: <Widget>[
-                      new Expanded(
-                        child: new FlatButton(
-                          shape: new RoundedRectangleBorder(
-                              borderRadius: new BorderRadius.circular(30.0)),
-                          color: Colors.white,
-                          onPressed: () => Navigator.push(
-                            context,
-                            MaterialPageRoute(
-                              builder: (context) => PhoneAuthScreen(),
-                            ),
-                          ),
-                          child: new Container(
-                            padding: const EdgeInsets.symmetric(
-                              vertical: 20.0,
-                              horizontal: 20.0,
-                            ),
-                            child: new Row(
-                              mainAxisAlignment: MainAxisAlignment.center,
-                              children: <Widget>[
-                                new Expanded(
-                                  child: Text(
-                                    "Continue",
-                                    textAlign: TextAlign.center,
-                                    style: TextStyle(
-                                        color: Theme.of(context).primaryColor,
-                                        fontWeight: FontWeight.bold),
-                                  ),
-                                ),
-                              ],
-                            ),
-                          ),
-                        ),
-                      ),
-                    ],
-                  ),
-                ),
-              ],
-            ),
-          ),
-        ],
-      ),
-    );
-  }
-
-  @override
-  void dispose() {
-    super.dispose();
-    _videoController.dispose();
-  }
-}
diff --git a/app/lib/theme.dart b/app/lib/theme.dart
new file mode 100644
index 0000000..d5159a4
--- /dev/null
+++ b/app/lib/theme.dart
@@ -0,0 +1,71 @@
+import 'package:flutter/material.dart';
+
+ThemeData buildLightThemeData() {
+  return ThemeData(
+    brightness: Brightness.light,
+    primaryColor: Colors.indigo,
+    accentColor: Colors.indigoAccent,
+    textTheme: TextTheme(
+      headline1: TextStyle(
+        fontSize: 100.0,
+        fontWeight: FontWeight.bold,
+        color: Colors.indigo,
+      ),
+      headline2: TextStyle(
+        fontSize: 24.0,
+        fontWeight: FontWeight.normal,
+        color: Colors.black54,
+      ),
+      headline4: TextStyle(
+        fontSize: 24.0,
+        fontWeight: FontWeight.normal,
+        color: Colors.indigo,
+      ),
+      headline5: TextStyle(
+        fontSize: 16.0,
+        fontWeight: FontWeight.normal,
+        color: Colors.black54,
+      ),
+      headline6: TextStyle(
+        fontSize: 16.0,
+        fontWeight: FontWeight.normal,
+        color: Colors.white,
+      ),
+    ),
+  );
+}
+
+ThemeData buildDarkThemeData() {
+  return ThemeData(
+    brightness: Brightness.dark,
+    primaryColor: Colors.indigo,
+    accentColor: Colors.indigoAccent,
+    textTheme: TextTheme(
+      headline1: TextStyle(
+        fontSize: 100.0,
+        fontWeight: FontWeight.bold,
+        color: Colors.white,
+      ),
+      headline2: TextStyle(
+        fontSize: 24.0,
+        fontWeight: FontWeight.normal,
+        color: Colors.white54,
+      ),
+      headline4: TextStyle(
+        fontSize: 28.0,
+        fontWeight: FontWeight.normal,
+        color: Colors.indigoAccent,
+      ),
+      headline5: TextStyle(
+        fontSize: 16.0,
+        fontWeight: FontWeight.normal,
+        color: Colors.white60,
+      ),
+      headline6: TextStyle(
+        fontSize: 16.0,
+        fontWeight: FontWeight.normal,
+        color: Colors.white,
+      ),
+    ),
+  );
+}
diff --git a/app/lib/utils/otp.dart b/app/lib/utils/otp.dart
deleted file mode 100644
index 27a584e..0000000
--- a/app/lib/utils/otp.dart
+++ /dev/null
@@ -1,1124 +0,0 @@
-import 'package:flutter/foundation.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
-
-enum PinEntryType { underline, boxTight, boxLoose }
-
-abstract class PinDecoration {
-  /// The style of painting text.
-  final TextStyle textStyle;
-
-  /// The style of obscure text.
-  final ObscureStyle obscureStyle;
-
-  /// The error text that will be displayed if any
-  final String errorText;
-
-  /// The style of error text.
-  final TextStyle errorTextStyle;
-
-  final String hintText;
-
-  final TextStyle hintTextStyle;
-
-  PinEntryType get pinEntryType;
-
-  const PinDecoration({
-    this.textStyle,
-    this.obscureStyle,
-    this.errorText,
-    this.errorTextStyle,
-    this.hintText,
-    this.hintTextStyle,
-  });
-
-  /// Creates a copy of this pin decoration with the given fields replaced
-  /// by the new values.
-  PinDecoration copyWith({
-    TextStyle textStyle,
-    ObscureStyle obscureStyle,
-    String errorText,
-    TextStyle errorTextStyle,
-    String hintText,
-    TextStyle hintTextStyle,
-  });
-}
-
-/// The object determine the obscure display
-class ObscureStyle {
-  /// The wrap line string.
-  static final _wrapLine = '\n';
-
-  /// Determine whether replace [obscureText] with number.
-  final bool isTextObscure;
-
-  /// The display text when [isTextObscure] is true, default is '*'
-  /// Do Not pass multiline string, it's not a good idea.
-  final String obscureText;
-
-  ObscureStyle({
-    this.isTextObscure: false,
-    this.obscureText: '*',
-  })  :
-
-        /// Not allowed empty string and multiline string.
-        assert(obscureText.length > 0),
-        assert(obscureText.indexOf(_wrapLine) == -1);
-}
-
-/// The object determine the underline color etc.
-class UnderlineDecoration extends PinDecoration {
-  /// The space between text and underline.
-  final double gapSpace;
-
-  /// The gaps between every two adjacent box, higher priority than [gapSpace].
-  final List<double> gapSpaces;
-
-  /// The color of the underline.
-  final Color color;
-
-  /// The height of the underline.
-  final double lineHeight;
-
-  /// The underline changed color when user enter pin.
-  final Color enteredColor;
-
-  const UnderlineDecoration({
-    TextStyle textStyle,
-    ObscureStyle obscureStyle,
-    String errorText,
-    TextStyle errorTextStyle,
-    String hintText,
-    TextStyle hintTextStyle,
-    this.enteredColor,
-    this.gapSpace: 16.0,
-    this.gapSpaces,
-    this.color: Colors.cyan,
-    this.lineHeight: 2.0,
-  }) : super(
-          textStyle: textStyle,
-          obscureStyle: obscureStyle,
-          errorText: errorText,
-          errorTextStyle: errorTextStyle,
-          hintText: hintText,
-          hintTextStyle: hintTextStyle,
-        );
-
-  @override
-  PinEntryType get pinEntryType => PinEntryType.underline;
-
-  @override
-  PinDecoration copyWith({
-    TextStyle textStyle,
-    ObscureStyle obscureStyle,
-    String errorText,
-    TextStyle errorTextStyle,
-    String hintText,
-    TextStyle hintTextStyle,
-  }) {
-    return UnderlineDecoration(
-      textStyle: textStyle ?? this.textStyle,
-      obscureStyle: obscureStyle ?? this.obscureStyle,
-      errorText: errorText ?? this.errorText,
-      errorTextStyle: errorTextStyle ?? this.errorTextStyle,
-      hintText: hintText ?? this.hintText,
-      hintTextStyle: hintTextStyle ?? this.hintTextStyle,
-      enteredColor: this.enteredColor,
-      color: this.color,
-      gapSpace: this.gapSpace,
-      lineHeight: this.lineHeight,
-      gapSpaces: this.gapSpaces,
-    );
-  }
-}
-
-/// The object determine the box stroke etc.
-class BoxTightDecoration extends PinDecoration {
-  /// The box border width.
-  final double strokeWidth;
-
-  /// The box border radius.
-  final Radius radius;
-
-  /// The box border color.
-  final Color strokeColor;
-
-  /// The box inside solid color, sometimes it equals to the box background.
-  final Color solidColor;
-
-  const BoxTightDecoration({
-    TextStyle textStyle,
-    ObscureStyle obscureStyle,
-    String errorText,
-    TextStyle errorTextStyle,
-    String hintText,
-    TextStyle hintTextStyle,
-    this.solidColor,
-    this.strokeWidth: 1.0,
-    this.radius: const Radius.circular(8.0),
-    this.strokeColor: Colors.cyan,
-  }) : super(
-          textStyle: textStyle,
-          obscureStyle: obscureStyle,
-          errorText: errorText,
-          errorTextStyle: errorTextStyle,
-          hintText: hintText,
-          hintTextStyle: hintTextStyle,
-        );
-
-  @override
-  PinEntryType get pinEntryType => PinEntryType.boxTight;
-
-  @override
-  PinDecoration copyWith({
-    TextStyle textStyle,
-    ObscureStyle obscureStyle,
-    String errorText,
-    TextStyle errorTextStyle,
-    String hintText,
-    TextStyle hintTextStyle,
-  }) {
-    return BoxTightDecoration(
-      textStyle: textStyle ?? this.textStyle,
-      obscureStyle: obscureStyle ?? this.obscureStyle,
-      errorText: errorText ?? this.errorText,
-      errorTextStyle: errorTextStyle ?? this.errorTextStyle,
-      hintText: hintText ?? this.hintText,
-      hintTextStyle: hintTextStyle ?? this.hintTextStyle,
-      solidColor: this.solidColor,
-      strokeColor: this.strokeColor,
-      strokeWidth: this.strokeWidth,
-      radius: this.radius,
-    );
-  }
-}
-
-/// The object determine the box stroke etc.
-class BoxLooseDecoration extends PinDecoration {
-  /// The box border radius.
-  final Radius radius;
-
-  /// The box border width.
-  final double strokeWidth;
-
-  /// The adjacent box gap.
-  final double gapSpace;
-
-  /// The gaps between every two adjacent box, higher priority than [gapSpace].
-  final List<double> gapSpaces;
-
-  /// The box border color.
-  final Color strokeColor;
-
-  /// The box inside solid color, sometimes it equals to the box background.
-  final Color solidColor;
-
-  /// The border changed color when user enter pin.
-  final Color enteredColor;
-
-  const BoxLooseDecoration({
-    TextStyle textStyle,
-    ObscureStyle obscureStyle,
-    String errorText,
-    TextStyle errorTextStyle,
-    String hintText,
-    TextStyle hintTextStyle,
-    this.enteredColor,
-    this.solidColor,
-    this.radius: const Radius.circular(8.0),
-    this.strokeWidth: 1.0,
-    this.gapSpace: 16.0,
-    this.gapSpaces,
-    this.strokeColor: Colors.cyan,
-  }) : super(
-          textStyle: textStyle,
-          obscureStyle: obscureStyle,
-          errorText: errorText,
-          errorTextStyle: errorTextStyle,
-          hintText: hintText,
-          hintTextStyle: hintTextStyle,
-        );
-
-  @override
-  PinEntryType get pinEntryType => PinEntryType.boxLoose;
-
-  @override
-  PinDecoration copyWith({
-    TextStyle textStyle,
-    ObscureStyle obscureStyle,
-    String errorText,
-    TextStyle errorTextStyle,
-    String hintText,
-    TextStyle hintTextStyle,
-  }) {
-    return BoxLooseDecoration(
-      textStyle: textStyle ?? this.textStyle,
-      obscureStyle: obscureStyle ?? this.obscureStyle,
-      errorText: errorText ?? this.errorText,
-      errorTextStyle: errorTextStyle ?? this.errorTextStyle,
-      hintText: hintText ?? this.hintText,
-      hintTextStyle: hintTextStyle ?? this.hintTextStyle,
-      solidColor: this.solidColor,
-      strokeColor: this.strokeColor,
-      strokeWidth: this.strokeWidth,
-      radius: this.radius,
-      enteredColor: this.enteredColor,
-      gapSpace: this.gapSpace,
-      gapSpaces: this.gapSpaces,
-    );
-  }
-}
-
-class PinInputTextField extends StatefulWidget {
-  /// The max length of pin.
-  final int pinLength;
-
-  /// The callback will execute when user click done.
-  final ValueChanged<String> onSubmit;
-
-  /// Decorate the pin.
-  final PinDecoration decoration;
-
-  /// Just like [TextField]'s inputFormatter.
-  final List<TextInputFormatter> inputFormatters;
-
-  /// Just like [TextField]'s keyboardType.
-  final TextInputType keyboardType;
-
-  /// Controls the pin being edited.
-  final TextEditingController controller;
-
-  /// Same as [TextField]'s autoFocus.
-  final bool autoFocus;
-
-  /// Same as [TextField]'s focusNode.
-  final FocusNode focusNode;
-
-  /// Same as [TextField]'s textInputAction.
-  final TextInputAction textInputAction;
-
-  /// Same as [TextField]'s enabled.
-  final bool enabled;
-
-  /// Same as [TextField]'s onChanged.
-  final ValueChanged<String> onChanged;
-
-  PinInputTextField({
-    Key key,
-    this.pinLength: 6,
-    this.onSubmit,
-    this.decoration: const BoxLooseDecoration(),
-    List<TextInputFormatter> inputFormatter,
-    this.keyboardType: TextInputType.phone,
-    this.controller,
-    this.focusNode,
-    this.autoFocus = false,
-    this.textInputAction = TextInputAction.done,
-    this.enabled = true,
-    this.onChanged,
-  })  :
-
-        /// pinLength must larger than 0.
-        /// If pinEditingController isn't null, guarantee the [pinLength] equals to the pinEditingController's _pinMaxLength
-        assert(pinLength != null && pinLength > 0),
-        assert(decoration != null),
-
-        /// Hint length must equal to the [pinLength].
-        assert(decoration.hintText == null ||
-            decoration.hintText.length == pinLength),
-        assert(decoration is BoxTightDecoration ||
-            (decoration is UnderlineDecoration &&
-                pinLength - 1 ==
-                    (decoration.gapSpaces?.length ?? (pinLength - 1))) ||
-            (decoration is BoxLooseDecoration &&
-                pinLength - 1 ==
-                    (decoration.gapSpaces?.length ?? (pinLength - 1)))),
-        inputFormatters = inputFormatter == null
-            ? <TextInputFormatter>[
-                WhitelistingTextInputFormatter.digitsOnly,
-                LengthLimitingTextInputFormatter(pinLength)
-              ]
-            : inputFormatter
-          ..add(LengthLimitingTextInputFormatter(pinLength)),
-        super(key: key);
-
-  @override
-  State createState() {
-    return _PinInputTextFieldState();
-  }
-}
-
-class _PinInputTextFieldState extends State<PinInputTextField> {
-  /// The display text to the user.
-  String _text;
-
-  TextEditingController _controller;
-
-  TextEditingController get _effectiveController =>
-      widget.controller ?? _controller;
-
-  void _pinChanged() {
-    setState(() {
-      _updateText();
-
-      /// This below code will cause dead loop in iOS,
-      /// you should assign selection when you set text.
-//      _effectiveController.selection = TextSelection.collapsed(
-//          offset: _effectiveController.text.runes.length);
-    });
-  }
-
-  void _updateText() {
-    if (_effectiveController.text.runes.length > widget.pinLength) {
-      _text = String.fromCharCodes(
-          _effectiveController.text.runes.take(widget.pinLength));
-    } else {
-      _text = _effectiveController.text;
-    }
-  }
-
-  @override
-  void initState() {
-    super.initState();
-    if (widget.controller == null) {
-      _controller = TextEditingController();
-    }
-    _effectiveController.addListener(_pinChanged);
-
-    //Ensure the initial value will be displayed when the field didn't get the focus.
-    _updateText();
-  }
-
-  @override
-  void dispose() {
-    // Ensure no listener will execute after dispose.
-    _effectiveController.removeListener(_pinChanged);
-    super.dispose();
-  }
-
-  @override
-  void didUpdateWidget(PinInputTextField oldWidget) {
-    super.didUpdateWidget(oldWidget);
-
-    if (widget.controller == null && oldWidget.controller != null) {
-      oldWidget.controller.removeListener(_pinChanged);
-      _controller = TextEditingController.fromValue(oldWidget.controller.value);
-      _controller.addListener(_pinChanged);
-    } else if (widget.controller != null && oldWidget.controller == null) {
-      _controller.removeListener(_pinChanged);
-      _controller = null;
-      widget.controller.addListener(_pinChanged);
-      // Invalidate the text when controller hold different old text.
-      if (_text != widget.controller.text) {
-        _pinChanged();
-      }
-    } else if (widget.controller != oldWidget.controller) {
-      // The old controller and current controller is not null and not the same.
-      oldWidget.controller.removeListener(_pinChanged);
-      widget.controller.addListener(_pinChanged);
-    }
-
-    /// If the newLength is shorter than now and the current text length longer
-    /// than [pinLength], So we should cut the superfluous subString.
-    if (oldWidget.pinLength > widget.pinLength &&
-        _text.runes.length > widget.pinLength) {
-      setState(() {
-        _text = _text.substring(0, widget.pinLength);
-        _effectiveController.text = _text;
-        _effectiveController.selection =
-            TextSelection.collapsed(offset: _text.runes.length);
-      });
-    }
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    return CustomPaint(
-      /// The foreground paint to display pin.
-      foregroundPainter: _PinPaint(
-          text: _text ?? _text.trim(),
-          pinLength: widget.pinLength,
-          decoration: widget.decoration,
-          themeData: Theme.of(context)),
-      child: TextField(
-        /// Actual textEditingController.
-        controller: _effectiveController,
-
-        /// Fake the text style.
-        style: TextStyle(
-          /// Hide the editing text.
-          color: Colors.transparent,
-          fontSize: 1,
-        ),
-
-        /// Hide the Cursor.
-        cursorColor: Colors.transparent,
-
-        /// Hide the cursor.
-        cursorWidth: 0.0,
-
-        /// No need to correct the user input.
-        autocorrect: false,
-
-        /// Center the input to make more natural.
-        textAlign: TextAlign.center,
-
-        /// Disable the actual textField selection.
-        enableInteractiveSelection: false,
-
-        /// The maxLength of the pin input, the default value is 6.
-        maxLength: widget.pinLength,
-
-        /// If use system keyboard and user click done, it will execute callback
-        /// Note!!! Custom keyboard in Android will not execute, see the related issue [https://github.com/flutter/flutter/issues/19027]
-        onSubmitted: widget.onSubmit,
-
-        /// Default text input type is number.
-        keyboardType: widget.keyboardType,
-
-        /// only accept digits.
-        inputFormatters: widget.inputFormatters,
-
-        /// Defines the keyboard focus for this widget.
-        focusNode: widget.focusNode,
-
-        /// {@macro flutter.widgets.editableText.autofocus}
-        autofocus: widget.autoFocus,
-
-        /// The type of action button to use for the keyboard.
-        ///
-        /// Defaults to [TextInputAction.done]
-        textInputAction: widget.textInputAction,
-
-        /// {@macro flutter.widgets.editableText.obscureText}
-        /// Default value of the obscureText is false. Make
-        obscureText: true,
-
-        onChanged: widget.onChanged,
-
-        /// Clear default text decoration.
-        decoration: InputDecoration(
-          /// Hide the counterText
-          counterText: '',
-
-          /// Hide the outline border.
-          border: OutlineInputBorder(
-            borderSide: BorderSide.none,
-          ),
-
-          /// Bind the error text from pin decoration to this input decoration.
-          errorText: widget.decoration.errorText,
-
-          /// Bind the style of error text from pin decoration to
-          /// this input decoration.
-          errorStyle: widget.decoration.errorTextStyle,
-        ),
-        enabled: widget.enabled,
-      ),
-    );
-  }
-}
-
-class _PinPaint extends CustomPainter {
-  final String text;
-  final int pinLength;
-  final PinEntryType type;
-  final PinDecoration decoration;
-  final ThemeData themeData;
-
-  _PinPaint({
-    @required this.text,
-    @required this.pinLength,
-    PinDecoration decoration,
-    this.type: PinEntryType.boxTight,
-    this.themeData,
-  }) : this.decoration = decoration.copyWith(
-          textStyle: decoration.textStyle ?? themeData.textTheme.headline,
-          errorTextStyle: decoration.errorTextStyle ??
-              themeData.textTheme.caption.copyWith(color: themeData.errorColor),
-          hintTextStyle: decoration.hintTextStyle ??
-              themeData.textTheme.headline.copyWith(color: themeData.hintColor),
-        );
-
-  @override
-  bool shouldRepaint(CustomPainter oldDelegate) =>
-      !(oldDelegate is _PinPaint && oldDelegate.text == this.text);
-
-  _drawBoxTight(Canvas canvas, Size size) {
-    /// Calculate the height of paint area for drawing the pin field.
-    /// it should honor the error text (if any) drawn by
-    /// the actual texfield behind.
-    /// but, since we can access the drawn textfield behind from here,
-    /// we use a simple logic to calculate it.
-    double mainHeight;
-    if (decoration.errorText != null && decoration.errorText.isNotEmpty) {
-      mainHeight = size.height - (decoration.errorTextStyle.fontSize + 8.0);
-    } else {
-      mainHeight = size.height;
-    }
-
-    /// Force convert to [BoxTightDecoration].
-    var dr = decoration as BoxTightDecoration;
-    Paint borderPaint = Paint()
-      ..color = dr.strokeColor
-      ..strokeWidth = dr.strokeWidth
-      ..style = PaintingStyle.stroke
-      ..isAntiAlias = true;
-
-    /// Assign paint if [solidColor] is not null
-    Paint insidePaint;
-    if (dr.solidColor != null) {
-      insidePaint = Paint()
-        ..color = dr.solidColor
-        ..strokeWidth = dr.strokeWidth
-        ..style = PaintingStyle.fill
-        ..isAntiAlias = true;
-    }
-
-    Rect rect = Rect.fromLTRB(
-      dr.strokeWidth / 2,
-      dr.strokeWidth / 2,
-      size.width - dr.strokeWidth / 2,
-      mainHeight - dr.strokeWidth / 2,
-    );
-
-    if (insidePaint != null) {
-      canvas.drawRRect(RRect.fromRectAndRadius(rect, dr.radius), insidePaint);
-    }
-
-    /// Draw the whole rect.
-    canvas.drawRRect(RRect.fromRectAndRadius(rect, dr.radius), borderPaint);
-
-    /// Calculate the width of each underline.
-    double singleWidth =
-        (size.width - dr.strokeWidth * (pinLength + 1)) / pinLength;
-
-    for (int i = 1; i < pinLength; i++) {
-      double offsetX = singleWidth +
-          dr.strokeWidth * i +
-          dr.strokeWidth / 2 +
-          singleWidth * (i - 1);
-      canvas.drawLine(Offset(offsetX, dr.strokeWidth),
-          Offset(offsetX, mainHeight - dr.strokeWidth), borderPaint);
-    }
-
-    /// The char index of the [text]
-    var index = 0;
-    var startY = 0.0;
-    var startX = 0.0;
-
-    /// Determine whether display obscureText.
-    bool obscureOn = decoration.obscureStyle != null &&
-        decoration.obscureStyle.isTextObscure;
-
-    text.runes.forEach((rune) {
-      String code;
-      if (obscureOn) {
-        code = decoration.obscureStyle.obscureText;
-      } else {
-        code = String.fromCharCode(rune);
-      }
-      TextPainter textPainter = TextPainter(
-        text: TextSpan(
-          style: decoration.textStyle,
-          text: code,
-        ),
-        textAlign: TextAlign.center,
-        textDirection: TextDirection.ltr,
-      );
-
-      /// Layout the text.
-      textPainter.layout();
-
-      /// No need to compute again
-      if (startY == 0.0) {
-        startY = mainHeight / 2 - textPainter.height / 2;
-      }
-      startX = dr.strokeWidth * (index + 1) +
-          singleWidth * index +
-          singleWidth / 2 -
-          textPainter.width / 2;
-      textPainter.paint(canvas, Offset(startX, startY));
-      index++;
-    });
-
-    if (decoration.hintText != null) {
-      decoration.hintText.substring(index).runes.forEach((rune) {
-        String code = String.fromCharCode(rune);
-        TextPainter textPainter = TextPainter(
-          text: TextSpan(
-            style: decoration.hintTextStyle,
-            text: code,
-          ),
-          textAlign: TextAlign.center,
-          textDirection: TextDirection.ltr,
-        );
-
-        /// Layout the text.
-        textPainter.layout();
-
-        startY = mainHeight / 2 - textPainter.height / 2;
-        startX = dr.strokeWidth * (index + 1) +
-            singleWidth * index +
-            singleWidth / 2 -
-            textPainter.width / 2;
-        textPainter.paint(canvas, Offset(startX, startY));
-        index++;
-      });
-    }
-  }
-
-  _drawBoxLoose(Canvas canvas, Size size) {
-    /// Calculate the height of paint area for drawing the pin field.
-    /// it should honor the error text (if any) drawn by
-    /// the actual texfield behind.
-    /// but, since we can access the drawn textfield behind from here,
-    /// we use a simple logic to calculate it.
-    double mainHeight;
-    if (decoration.errorText != null && decoration.errorText.isNotEmpty) {
-      mainHeight = size.height - (decoration.errorTextStyle.fontSize + 8.0);
-    } else {
-      mainHeight = size.height;
-    }
-
-    /// Force convert to [BoxLooseDecoration].
-    var dr = decoration as BoxLooseDecoration;
-    Paint borderPaint = Paint()
-      ..color = dr.strokeColor
-      ..strokeWidth = dr.strokeWidth
-      ..style = PaintingStyle.stroke
-      ..isAntiAlias = true;
-
-    /// Assign paint if [solidColor] is not null
-    Paint insidePaint;
-    if (dr.solidColor != null) {
-      insidePaint = Paint()
-        ..color = dr.solidColor
-        ..style = PaintingStyle.fill
-        ..isAntiAlias = true;
-    }
-
-    double gapTotalLength =
-        dr.gapSpaces?.reduce((a, b) => a + b) ?? (pinLength - 1) * dr.gapSpace;
-
-    List<double> gapSpaces =
-        dr.gapSpaces ?? List.filled(pinLength - 1, dr.gapSpace);
-
-    /// Calculate the width of each underline.
-    double singleWidth =
-        (size.width - dr.strokeWidth * 2 * pinLength - gapTotalLength) /
-            pinLength;
-
-    var startX = dr.strokeWidth / 2;
-    var startY = mainHeight - dr.strokeWidth / 2;
-
-    /// Draw the each rect of pin.
-    for (int i = 0; i < pinLength; i++) {
-      if (i < text.length && dr.enteredColor != null) {
-        borderPaint.color = dr.enteredColor;
-      } else if (decoration.errorText != null &&
-          decoration.errorText.isNotEmpty) {
-        /// only draw error-color as border-color or solid-color
-        /// if errorText is not null
-        if (dr.solidColor == null) {
-          borderPaint.color = decoration.errorTextStyle.color;
-        } else {
-          insidePaint = Paint()
-            ..color = decoration.errorTextStyle.color
-            ..style = PaintingStyle.fill
-            ..isAntiAlias = true;
-        }
-      } else {
-        borderPaint.color = dr.strokeColor;
-      }
-      RRect rRect = RRect.fromRectAndRadius(
-          Rect.fromLTRB(
-            startX,
-            dr.strokeWidth / 2,
-            startX + singleWidth + dr.strokeWidth,
-            startY,
-          ),
-          dr.radius);
-      canvas.drawRRect(rRect, borderPaint);
-      if (insidePaint != null) {
-        canvas.drawRRect(rRect, insidePaint);
-      }
-      startX += singleWidth +
-          dr.strokeWidth * 2 +
-          (i == pinLength - 1 ? 0 : gapSpaces[i]);
-    }
-
-    /// The char index of the [text]
-    var index = 0;
-    startY = 0.0;
-
-    /// Determine whether display obscureText.
-    bool obscureOn = decoration.obscureStyle != null &&
-        decoration.obscureStyle.isTextObscure;
-
-    text.runes.forEach((rune) {
-      String code;
-      if (obscureOn) {
-        code = decoration.obscureStyle.obscureText;
-      } else {
-        code = String.fromCharCode(rune);
-      }
-      TextPainter textPainter = TextPainter(
-        text: TextSpan(
-          style: decoration.textStyle,
-          text: code,
-        ),
-        textAlign: TextAlign.center,
-        textDirection: TextDirection.ltr,
-      );
-
-      /// Layout the text.
-      textPainter.layout();
-
-      /// No need to compute again
-      if (startY == 0.0) {
-        startY = mainHeight / 2 - textPainter.height / 2;
-      }
-      startX = singleWidth * index +
-          singleWidth / 2 -
-          textPainter.width / 2 +
-          _sumList(gapSpaces.take(index)) +
-          dr.strokeWidth * index * 2 +
-          dr.strokeWidth;
-      textPainter.paint(canvas, Offset(startX, startY));
-      index++;
-    });
-
-    if (decoration.hintText != null) {
-      decoration.hintText.substring(index).runes.forEach((rune) {
-        String code = String.fromCharCode(rune);
-        TextPainter textPainter = TextPainter(
-          text: TextSpan(
-            style: decoration.hintTextStyle,
-            text: code,
-          ),
-          textAlign: TextAlign.center,
-          textDirection: TextDirection.ltr,
-        );
-
-        /// Layout the text.
-        textPainter.layout();
-
-        startY = mainHeight / 2 - textPainter.height / 2;
-        startX = singleWidth * index +
-            singleWidth / 2 -
-            textPainter.width / 2 +
-            _sumList(gapSpaces.take(index)) +
-            dr.strokeWidth * index * 2 +
-            dr.strokeWidth;
-        textPainter.paint(canvas, Offset(startX, startY));
-        index++;
-      });
-    }
-  }
-
-  _drawUnderLine(Canvas canvas, Size size) {
-    /// Calculate the height of paint area for drawing the pin field.
-    /// it should honor the error text (if any) drawn by
-    /// the actual texfield behind.
-    /// but, since we can access the drawn textfield behind from here,
-    /// we use a simple logic to calculate it.
-    double mainHeight;
-    if (decoration.errorText != null && decoration.errorText.isNotEmpty) {
-      mainHeight = size.height - (decoration.errorTextStyle.fontSize + 8.0);
-    } else {
-      mainHeight = size.height;
-    }
-
-    /// Force convert to [UnderlineDecoration].
-    var dr = decoration as UnderlineDecoration;
-    Paint underlinePaint = Paint()
-      ..color = dr.color
-      ..strokeWidth = dr.lineHeight
-      ..style = PaintingStyle.stroke
-      ..isAntiAlias = true;
-
-    var startX = 0.0;
-    var startY = mainHeight - dr.lineHeight;
-
-    double gapTotalLength =
-        dr.gapSpaces?.reduce((a, b) => a + b) ?? (pinLength - 1) * dr.gapSpace;
-
-    List<double> gapSpaces =
-        dr.gapSpaces ?? List.filled(pinLength - 1, dr.gapSpace);
-
-    /// Calculate the width of each underline.
-    double singleWidth = (size.width - gapTotalLength) / pinLength;
-
-    for (int i = 0; i < pinLength; i++) {
-      if (i < text.length && dr.enteredColor != null) {
-        underlinePaint.color = dr.enteredColor;
-      } else if (decoration.errorText != null &&
-          decoration.errorText.isNotEmpty) {
-        /// only draw error-color as underline-color if errorText is not null
-        underlinePaint.color = decoration.errorTextStyle.color;
-      } else {
-        underlinePaint.color = dr.color;
-      }
-      canvas.drawLine(Offset(startX, startY),
-          Offset(startX + singleWidth, startY), underlinePaint);
-      startX += singleWidth + (i == pinLength - 1 ? 0 : gapSpaces[i]);
-    }
-
-    /// The char index of the [text]
-    var index = 0;
-    startX = 0.0;
-    startY = 0.0;
-
-    /// Determine whether display obscureText.
-    bool obscureOn = decoration.obscureStyle != null &&
-        decoration.obscureStyle.isTextObscure;
-
-    text.runes.forEach((rune) {
-      String code;
-      if (obscureOn) {
-        code = decoration.obscureStyle.obscureText;
-      } else {
-        code = String.fromCharCode(rune);
-      }
-      TextPainter textPainter = TextPainter(
-        text: TextSpan(
-          style: decoration.textStyle,
-          text: code,
-        ),
-        textAlign: TextAlign.center,
-        textDirection: TextDirection.ltr,
-      );
-
-      /// Layout the text.
-      textPainter.layout();
-
-      /// No need to compute again
-      if (startY == 0.0) {
-        startY = mainHeight / 2 - textPainter.height / 2;
-      }
-      startX = singleWidth * index +
-          singleWidth / 2 -
-          textPainter.width / 2 +
-          _sumList(gapSpaces.take(index));
-      textPainter.paint(canvas, Offset(startX, startY));
-      index++;
-    });
-
-    if (decoration.hintText != null) {
-      decoration.hintText.substring(index).runes.forEach((rune) {
-        String code = String.fromCharCode(rune);
-        TextPainter textPainter = TextPainter(
-          text: TextSpan(
-            style: decoration.hintTextStyle,
-            text: code,
-          ),
-          textAlign: TextAlign.center,
-          textDirection: TextDirection.ltr,
-        );
-
-        /// Layout the text.
-        textPainter.layout();
-
-        startY = mainHeight / 2 - textPainter.height / 2;
-        startX = singleWidth * index +
-            singleWidth / 2 -
-            textPainter.width / 2 +
-            _sumList(gapSpaces.take(index));
-        textPainter.paint(canvas, Offset(startX, startY));
-        index++;
-      });
-    }
-  }
-
-  /// Return the sum of the [list] even the [list] is empty.
-  T _sumList<T extends num>(Iterable<T> list) {
-    T sum = 0 as T;
-    list.forEach((n) => sum += n);
-    return sum;
-  }
-
-  @override
-  void paint(Canvas canvas, Size size) {
-    switch (decoration.pinEntryType) {
-      case PinEntryType.boxTight:
-        {
-          _drawBoxTight(canvas, size);
-          break;
-        }
-      case PinEntryType.boxLoose:
-        {
-          _drawBoxLoose(canvas, size);
-          break;
-        }
-      case PinEntryType.underline:
-        {
-          _drawUnderLine(canvas, size);
-          break;
-        }
-    }
-  }
-}
-
-class PinInputTextFormField extends FormField<String> {
-  /// Controls the pin being edited.
-  final TextEditingController controller;
-
-  /// The max length of pin.
-  final int pinLength;
-
-  PinInputTextFormField({
-    Key key,
-    this.controller,
-    String initialValue,
-    this.pinLength = 6,
-    ValueChanged<String> onSubmit,
-    PinDecoration decoration = const BoxLooseDecoration(),
-    List<TextInputFormatter> inputFormatter,
-    TextInputType keyboardType = TextInputType.phone,
-    FocusNode focusNode,
-    bool autoFocus = false,
-    TextInputAction textInputAction = TextInputAction.done,
-    bool enabled = true,
-    FormFieldSetter<String> onSaved,
-    FormFieldValidator<String> validator,
-    bool autovalidate = false,
-    ValueChanged<String> onChanged,
-  })  : assert(initialValue == null || controller == null),
-        assert(autovalidate != null),
-        assert(pinLength != null && pinLength > 0),
-        super(
-            key: key,
-            initialValue:
-                controller != null ? controller.text : (initialValue ?? ''),
-            onSaved: onSaved,
-            validator: (value) {
-              var result = validator(value);
-              if (result == null) {
-                if (value.isEmpty) {
-                  return 'Input field is empty.';
-                }
-                if (value.length < pinLength) {
-                  if (pinLength - value.length > 1) {
-                    return 'Missing ${pinLength - value.length} digits of input.';
-                  } else {
-                    return 'Missing last digit of input.';
-                  }
-                }
-              }
-              return result;
-            },
-            autovalidate: autovalidate,
-            enabled: enabled,
-            builder: (FormFieldState<String> field) {
-              final _PinInputTextFormFieldState state = field;
-              return PinInputTextField(
-                pinLength: pinLength,
-                onSubmit: onSubmit,
-                decoration: decoration.copyWith(errorText: field.errorText),
-                inputFormatter: inputFormatter,
-                keyboardType: keyboardType,
-                controller: state._effectiveController,
-                focusNode: focusNode,
-                autoFocus: autoFocus,
-                textInputAction: textInputAction,
-                enabled: enabled,
-                onChanged: onChanged,
-              );
-            });
-
-  @override
-  _PinInputTextFormFieldState createState() => _PinInputTextFormFieldState();
-}
-
-class _PinInputTextFormFieldState extends FormFieldState<String> {
-  TextEditingController _controller;
-
-  TextEditingController get _effectiveController =>
-      widget.controller ?? _controller;
-
-  @override
-  PinInputTextFormField get widget => super.widget;
-
-  @override
-  void initState() {
-    super.initState();
-    if (widget.controller == null) {
-      _controller = TextEditingController(text: widget.initialValue);
-    }
-    _effectiveController.addListener(_handleControllerChanged);
-  }
-
-  @override
-  void didUpdateWidget(PinInputTextFormField oldWidget) {
-    super.didUpdateWidget(oldWidget);
-
-    if (widget.controller == null && oldWidget.controller != null) {
-      oldWidget.controller.removeListener(_handleControllerChanged);
-      _controller = TextEditingController.fromValue(oldWidget.controller.value);
-      _controller.addListener(_handleControllerChanged);
-    } else if (widget.controller != null && oldWidget.controller == null) {
-      _controller.removeListener(_handleControllerChanged);
-      _controller = null;
-      widget.controller.addListener(_handleControllerChanged);
-      // Invalidate the text when controller hold different old text.
-      if (value != widget.controller.text) {
-        _handleControllerChanged();
-      }
-    } else if (widget.controller != oldWidget.controller) {
-      // The old controller and current controller is not null and not the same.
-      oldWidget.controller.removeListener(_handleControllerChanged);
-      widget.controller.addListener(_handleControllerChanged);
-    }
-
-    /// If the newLength is shorter than now and the current text length longer
-    /// than [pinLength], So we should cut the superfluous subString.
-    if (oldWidget.pinLength > widget.pinLength &&
-        value.runes.length > widget.pinLength) {
-      setState(() {
-        setValue(value.substring(0, widget.pinLength));
-        _effectiveController.text = value;
-        _effectiveController.selection = TextSelection.collapsed(
-          offset: value.runes.length,
-        );
-      });
-    }
-  }
-
-  @override
-  void dispose() {
-    _effectiveController.removeListener(_handleControllerChanged);
-    super.dispose();
-  }
-
-  @override
-  void reset() {
-    super.reset();
-    setState(() {
-      _effectiveController.text = widget.initialValue;
-    });
-  }
-
-  @override
-  void didChange(String value) {
-    if (value.runes.length > widget.pinLength) {
-      super.didChange(String.fromCharCodes(
-        value.runes.take(widget.pinLength),
-      ));
-    } else {
-      super.didChange(value);
-    }
-  }
-
-  void _handleControllerChanged() {
-    // Suppress changes that originated from within this class.
-    //
-    // In the case where a controller has been passed in to this widget, we
-    // register this change listener. In these cases, we'll also receive change
-    // notifications for changes originating from within this class -- for
-    // example, the reset() method. In such cases, the FormField value will
-    // already have been set.
-    if (_effectiveController.text != value)
-      didChange(_effectiveController.text);
-  }
-}
diff --git a/app/pubspec.lock b/app/pubspec.lock
index c5c7d75..7922650 100644
--- a/app/pubspec.lock
+++ b/app/pubspec.lock
@@ -1,6 +1,27 @@
 # Generated by pub
 # See https://dart.dev/tools/pub/glossary#lockfile
 packages:
+  _fe_analyzer_shared:
+    dependency: transitive
+    description:
+      name: _fe_analyzer_shared
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "14.0.0"
+  analyzer:
+    dependency: transitive
+    description:
+      name: analyzer
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.41.2"
+  args:
+    dependency: transitive
+    description:
+      name: args
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.6.0"
   async:
     dependency: transitive
     description:
@@ -8,6 +29,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "2.5.0-nullsafety.1"
+  bloc:
+    dependency: transitive
+    description:
+      name: bloc
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.0.0"
   boolean_selector:
     dependency: transitive
     description:
@@ -15,6 +43,27 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "2.1.0-nullsafety.1"
+  build:
+    dependency: transitive
+    description:
+      name: build
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.6.2"
+  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:
@@ -29,34 +78,27 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.2.0-nullsafety.1"
-  clock:
+  cli_util:
     dependency: transitive
     description:
-      name: clock
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "1.1.0-nullsafety.1"
-  cloud_firestore:
-    dependency: "direct main"
-    description:
-      name: cloud_firestore
+      name: cli_util
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.13.7"
-  cloud_firestore_platform_interface:
+    version: "0.2.0"
+  clock:
     dependency: transitive
     description:
-      name: cloud_firestore_platform_interface
+      name: clock
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.1.2"
-  cloud_firestore_web:
+    version: "1.1.0-nullsafety.1"
+  code_builder:
     dependency: transitive
     description:
-      name: cloud_firestore_web
+      name: code_builder
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.1.1+2"
+    version: "3.6.0"
   collection:
     dependency: transitive
     description:
@@ -71,74 +113,88 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "0.4.6"
-  cupertino_icons:
-    dependency: "direct main"
+  convert:
+    dependency: transitive
     description:
-      name: cupertino_icons
+      name: convert
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.0.0"
-  fake_async:
+    version: "2.1.1"
+  crypto:
     dependency: transitive
     description:
-      name: fake_async
+      name: crypto
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.2.0-nullsafety.1"
-  firebase:
+    version: "2.1.5"
+  dart_style:
     dependency: transitive
     description:
-      name: firebase
+      name: dart_style
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "7.3.0"
-  firebase_auth:
+    version: "1.3.12"
+  dartz:
     dependency: "direct main"
     description:
-      name: firebase_auth
+      name: dartz
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.16.1"
-  firebase_auth_platform_interface:
-    dependency: transitive
+    version: "0.8.9"
+  data_connection_checker:
+    dependency: "direct main"
     description:
-      name: firebase_auth_platform_interface
+      name: data_connection_checker
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.1.8"
-  firebase_auth_web:
+    version: "0.3.4"
+  equatable:
+    dependency: "direct main"
+    description:
+      name: equatable
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.2.6"
+  fake_async:
     dependency: transitive
     description:
-      name: firebase_auth_web
+      name: fake_async
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.1.3+1"
-  firebase_core:
-    dependency: "direct main"
+    version: "1.2.0-nullsafety.1"
+  ffi:
+    dependency: transitive
     description:
-      name: firebase_core
+      name: ffi
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.4.5"
-  firebase_core_platform_interface:
+    version: "0.1.3"
+  file:
     dependency: transitive
     description:
-      name: firebase_core_platform_interface
+      name: file
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.0.4"
-  firebase_core_web:
+    version: "5.2.1"
+  fixnum:
     dependency: transitive
     description:
-      name: firebase_core_web
+      name: fixnum
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.1.1+2"
+    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: "3.2.0"
   flutter_sim_country_code:
     dependency: "direct main"
     description:
@@ -156,15 +212,22 @@ packages:
     description: flutter
     source: sdk
     version: "0.0.0"
-  fluttertoast:
+  get_it:
     dependency: "direct main"
     description:
-      name: fluttertoast
+      name: get_it
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "7.1.1"
-  http:
+    version: "2.1.0"
+  glob:
     dependency: transitive
+    description:
+      name: glob
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.2.0"
+  http:
+    dependency: "direct main"
     description:
       name: http
       url: "https://pub.dartlang.org"
@@ -177,13 +240,20 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "3.1.4"
+  intl:
+    dependency: transitive
+    description:
+      name: intl
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.16.1"
   intl_phone_number_input:
     dependency: "direct main"
     description:
       name: intl_phone_number_input
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.3.0"
+    version: "0.6.0"
   js:
     dependency: transitive
     description:
@@ -198,6 +268,55 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.0.2"
+  libphonenumber_platform_interface:
+    dependency: transitive
+    description:
+      name: libphonenumber_platform_interface
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.2.0"
+  libphonenumber_plugin:
+    dependency: transitive
+    description:
+      name: libphonenumber_plugin
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.1.0"
+  libphonenumber_web:
+    dependency: transitive
+    description:
+      name: libphonenumber_web
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.1.1"
+  location:
+    dependency: "direct main"
+    description:
+      name: location
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.2.4"
+  location_platform_interface:
+    dependency: transitive
+    description:
+      name: location_platform_interface
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.1.0"
+  location_web:
+    dependency: transitive
+    description:
+      name: location_web
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.0.1"
+  logging:
+    dependency: transitive
+    description:
+      name: logging
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.11.4"
   matcher:
     dependency: transitive
     description:
@@ -212,6 +331,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.3.0-nullsafety.3"
+  mockito:
+    dependency: "direct dev"
+    description:
+      name: mockito
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "4.1.4"
   nested:
     dependency: transitive
     description:
@@ -219,6 +345,27 @@ packages:
       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.2.1"
+  node_io:
+    dependency: transitive
+    description:
+      name: node_io
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.2.0"
+  package_config:
+    dependency: transitive
+    description:
+      name: package_config
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.9.3"
   path:
     dependency: transitive
     description:
@@ -226,6 +373,27 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.8.0-nullsafety.1"
+  path_provider_linux:
+    dependency: transitive
+    description:
+      name: path_provider_linux
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.0.1+2"
+  path_provider_platform_interface:
+    dependency: transitive
+    description:
+      name: path_provider_platform_interface
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.0.4"
+  path_provider_windows:
+    dependency: transitive
+    description:
+      name: path_provider_windows
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.0.4+3"
   pedantic:
     dependency: transitive
     description:
@@ -239,14 +407,28 @@ packages:
       name: permission_handler
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "5.0.1+1"
+    version: "4.4.0+hotfix.4"
   permission_handler_platform_interface:
     dependency: transitive
     description:
       name: permission_handler_platform_interface
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.0.1"
+    version: "1.0.0"
+  pin_code_fields:
+    dependency: "direct main"
+    description:
+      name: pin_code_fields
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "6.1.0"
+  platform:
+    dependency: transitive
+    description:
+      name: platform
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.2.1"
   plugin_platform_interface:
     dependency: transitive
     description:
@@ -254,25 +436,95 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.0.3"
+  process:
+    dependency: transitive
+    description:
+      name: process
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.0.13"
   provider:
     dependency: transitive
     description:
       name: provider
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "4.3.2+2"
+    version: "4.3.3"
+  pub_semver:
+    dependency: transitive
+    description:
+      name: pub_semver
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.4.4"
   quiver:
     dependency: transitive
     description:
       name: quiver
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.1.3"
+    version: "2.1.5"
+  rxdart:
+    dependency: transitive
+    description:
+      name: rxdart
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.23.1"
+  shared_preferences:
+    dependency: "direct main"
+    description:
+      name: shared_preferences
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.5.12+4"
+  shared_preferences_linux:
+    dependency: transitive
+    description:
+      name: shared_preferences_linux
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.0.2+4"
+  shared_preferences_macos:
+    dependency: transitive
+    description:
+      name: shared_preferences_macos
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.0.1+11"
+  shared_preferences_platform_interface:
+    dependency: transitive
+    description:
+      name: shared_preferences_platform_interface
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.0.4"
+  shared_preferences_web:
+    dependency: transitive
+    description:
+      name: shared_preferences_web
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.1.2+7"
+  shared_preferences_windows:
+    dependency: transitive
+    description:
+      name: shared_preferences_windows
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.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.10+2"
   source_span:
     dependency: transitive
     description:
@@ -301,6 +553,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.1.0-nullsafety.1"
+  stripe_payment:
+    dependency: "direct main"
+    description:
+      name: stripe_payment
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.0.11"
   term_glyph:
     dependency: transitive
     description:
@@ -329,27 +588,34 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "2.1.0-nullsafety.3"
-  video_player:
-    dependency: "direct main"
+  watcher:
+    dependency: transitive
+    description:
+      name: watcher
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.9.7+15"
+  win32:
+    dependency: transitive
     description:
-      name: video_player
+      name: win32
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.11.0"
-  video_player_platform_interface:
+    version: "1.7.4+1"
+  xdg_directories:
     dependency: transitive
     description:
-      name: video_player_platform_interface
+      name: xdg_directories
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.2.0"
-  video_player_web:
+    version: "0.1.2"
+  yaml:
     dependency: transitive
     description:
-      name: video_player_web
+      name: yaml
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.1.4"
+    version: "2.2.1"
 sdks:
-  dart: ">=2.10.0-110 <2.11.0"
-  flutter: ">=1.16.0 <2.0.0"
+  dart: ">=2.10.0 <2.11.0"
+  flutter: ">=1.22.0 <2.0.0"
diff --git a/app/pubspec.yaml b/app/pubspec.yaml
index dfb24de..7382821 100644
--- a/app/pubspec.yaml
+++ b/app/pubspec.yaml
@@ -1,5 +1,8 @@
 name: DRUN
-description: Drone delivery service.
+description: A new Flutter project.
+
+publish_to: "none" # Remove this line if you wish to publish to pub.dev
+
 version: 1.0.0+1
 
 environment:
@@ -8,22 +11,25 @@ environment:
 dependencies:
   flutter:
     sdk: flutter
-  cupertino_icons:
-  firebase_core:
-  firebase_auth: 0.16.1
-  video_player:
-  intl_phone_number_input: ^0.3.0
-  flutter_sim_country_code: ^0.0.3
-  fluttertoast:
-  cloud_firestore:
   contacts_service: ^0.4.6
-  permission_handler: ^5.0.0
+  dartz: ^0.8.6
+  data_connection_checker: ^0.3.4
+  equatable: ^1.2.6
+  flutter_bloc: ^3.1.2
+  flutter_sim_country_code: ^0.0.3
+  get_it: ^2.0.1
+  http: ^0.12.0+2
+  intl_phone_number_input: ^0.6.0
+  location: ^3.0.0
+  permission_handler: ^4.4.0+hotfix.2
+  pin_code_fields: ^6.1.0
+  shared_preferences: ^0.5.3+4
+  stripe_payment: ^1.0.11
 
 dev_dependencies:
   flutter_test:
     sdk: flutter
+  mockito: ^4.1.0
 
 flutter:
   uses-material-design: true
-  assets:
-    - assets/
\ No newline at end of file


Test patch:
diff --git a/app/test/core/data/sources/network_info_test.dart b/app/test/core/data/sources/network_info_test.dart
new file mode 100644
index 0000000..91d1f17
--- /dev/null
+++ b/app/test/core/data/sources/network_info_test.dart
@@ -0,0 +1,37 @@
+import 'package:DRUN/core/data/sources/network_info.dart';
+import 'package:data_connection_checker/data_connection_checker.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/mockito.dart';
+
+class MockDataConnectionChecker extends Mock implements DataConnectionChecker {}
+
+void main() {
+  NetworkInfoImpl networkInfoImpl;
+  MockDataConnectionChecker mockDataConnectionChecker;
+
+  setUp(() {
+    mockDataConnectionChecker = MockDataConnectionChecker();
+    networkInfoImpl = NetworkInfoImpl(
+      mockDataConnectionChecker,
+    );
+  });
+
+  group('isConnected', () {
+    test(
+      'should forward the call to DataConnectionChecker.hasConnection',
+      () async {
+        // Arrange
+        final tHasConnectionFuture = Future.value(true);
+        when(mockDataConnectionChecker.hasConnection)
+            .thenAnswer((_) => tHasConnectionFuture);
+
+        // Act
+        final result = networkInfoImpl.isConnected;
+
+        // Assert
+        verify(mockDataConnectionChecker.hasConnection);
+        expect(result, tHasConnectionFuture);
+      },
+    );
+  });
+}
diff --git a/app/test/core/presentation/utils/input_validator_test.dart b/app/test/core/presentation/utils/input_validator_test.dart
new file mode 100644
index 0000000..448dd12
--- /dev/null
+++ b/app/test/core/presentation/utils/input_validator_test.dart
@@ -0,0 +1,66 @@
+import 'package:DRUN/core/errors/failures.dart';
+import 'package:DRUN/core/presentation/util/input_validator.dart';
+import 'package:dartz/dartz.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+  InputValidator inputValidator;
+
+  setUp(() {
+    inputValidator = InputValidator();
+  });
+
+  group('stringAsPhoneNumber', () {
+    test(
+      'should return the same phone number on a validly formated phone number',
+      () async {
+        // Arrange
+        final tPhoneNumber1 = '+359735780085';
+        final tPhoneNumber2 = '+359 735 780 085';
+        final tPhoneNumber3 = '+359 (735) 780 085';
+        final tPhoneNumber4 = '+359 735-780-085';
+        final tPhoneNumber5 = '+1735780085';
+
+        // Act
+        final result1 = inputValidator.stringAsPhoneNumber(tPhoneNumber1);
+        final result2 = inputValidator.stringAsPhoneNumber(tPhoneNumber2);
+        final result3 = inputValidator.stringAsPhoneNumber(tPhoneNumber3);
+        final result4 = inputValidator.stringAsPhoneNumber(tPhoneNumber4);
+        final result5 = inputValidator.stringAsPhoneNumber(tPhoneNumber5);
+
+        // Assert
+        expect(result1, Right(tPhoneNumber1));
+        expect(result2, Right(tPhoneNumber2));
+        expect(result3, Right(tPhoneNumber3));
+        expect(result4, Right(tPhoneNumber4));
+        expect(result5, Right(tPhoneNumber5));
+      },
+    );
+
+    test(
+      'should return InvalidInputFailure on a invalid phone number',
+      () async {
+        // Arrange
+        final tPhoneNumber1 = 'aaaaaaa';
+        final tPhoneNumber2 = 'a8a8a8a8a8@';
+        final tPhoneNumber3 = '0735780085';
+        final tPhoneNumber4 = '+359 8';
+        final tPhoneNumber5 = '+1808080808080808080';
+
+        // Act
+        final result1 = inputValidator.stringAsPhoneNumber(tPhoneNumber1);
+        final result2 = inputValidator.stringAsPhoneNumber(tPhoneNumber2);
+        final result3 = inputValidator.stringAsPhoneNumber(tPhoneNumber3);
+        final result4 = inputValidator.stringAsPhoneNumber(tPhoneNumber4);
+        final result5 = inputValidator.stringAsPhoneNumber(tPhoneNumber5);
+
+        // Assert
+        expect(result1, Left(InvalidInputFailure()));
+        expect(result2, Left(InvalidInputFailure()));
+        expect(result3, Left(InvalidInputFailure()));
+        expect(result4, Left(InvalidInputFailure()));
+        expect(result5, Left(InvalidInputFailure()));
+      },
+    );
+  });
+}
diff --git a/app/test/features/authentication/data/models/authentication_sms_status_model_test.dart b/app/test/features/authentication/data/models/authentication_sms_status_model_test.dart
new file mode 100644
index 0000000..afe0a28
--- /dev/null
+++ b/app/test/features/authentication/data/models/authentication_sms_status_model_test.dart
@@ -0,0 +1,40 @@
+import 'dart:convert';
+
+import 'package:DRUN/features/authentication/data/models/authentication_sms_status_model.dart';
+import 'package:DRUN/features/authentication/domain/entities/authentication_sms_status.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import '../../../../fixtures/fixture_parser.dart';
+
+void main() {
+  final tAuthenticationSmsStatusModelSuccess = AuthenticationSmsStatusModel(
+    phoneNumber: '+359735780085',
+    succeeded: true,
+  );
+
+  test(
+    'should be an extension of AuthenticationSmsStatus entity',
+    () async {
+      // Assert
+      expect(
+        tAuthenticationSmsStatusModelSuccess,
+        isA<AuthenticationSmsStatus>(),
+      );
+    },
+  );
+
+  test(
+    'should parse from JSON',
+    () async {
+      // Arrange
+      final Map<String, dynamic> jsonMap =
+          json.decode(fixture('authentication_sms_status_success.json'));
+
+      // Act
+      final result = AuthenticationSmsStatusModel.fromJSON(jsonMap);
+
+      // Assert
+      expect(result, tAuthenticationSmsStatusModelSuccess);
+    },
+  );
+}
diff --git a/app/test/features/authentication/data/models/user_credentials_model_test.dart b/app/test/features/authentication/data/models/user_credentials_model_test.dart
new file mode 100644
index 0000000..ef40005
--- /dev/null
+++ b/app/test/features/authentication/data/models/user_credentials_model_test.dart
@@ -0,0 +1,51 @@
+import 'dart:convert';
+
+import 'package:DRUN/features/authentication/data/models/user_credentials_model.dart';
+import 'package:DRUN/features/authentication/domain/entities/user_credentials.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import '../../../../fixtures/fixture_parser.dart';
+
+void main() {
+  final tUserCredentialsModelSuccess = UserCredentialsModel(
+    userId: '4ade5874-c573-4c8f-b2b8-7db5fccd983b',
+    userToken: '713ADC1F515B3E0BBDF964DBAE6257A5B8A617115816A1705EACF9C00394A5',
+  );
+
+  test(
+    'should be an extension of AuthenticationSmsStatus entity',
+    () async {
+      // Assert
+      expect(
+        tUserCredentialsModelSuccess,
+        isA<UserCredentials>(),
+      );
+    },
+  );
+
+  test(
+    'should parse from JSON',
+    () async {
+      // Arrange
+      final Map<String, dynamic> jsonMap =
+          json.decode(fixture('user_credentials_success.json'));
+
+      // Act
+      final result = UserCredentialsModel.fromJSON(jsonMap);
+
+      // Assert
+      expect(result, tUserCredentialsModelSuccess);
+    },
+  );
+
+  test(
+    'should convert to JSON',
+    () async {
+      // Act
+      final result = tUserCredentialsModelSuccess.toJSON();
+
+      // Assert
+      expect(result, json.decode(fixture('user_credentials_success.json')));
+    },
+  );
+}
diff --git a/app/test/features/authentication/data/repositories/user_authentication_repository_impl_test.dart b/app/test/features/authentication/data/repositories/user_authentication_repository_impl_test.dart
new file mode 100644
index 0000000..865f744
--- /dev/null
+++ b/app/test/features/authentication/data/repositories/user_authentication_repository_impl_test.dart
@@ -0,0 +1,298 @@
+import 'package:DRUN/core/errors/exceptions.dart';
+import 'package:DRUN/core/errors/failures.dart';
+import 'package:DRUN/core/data/sources/network_info.dart';
+import 'package:DRUN/features/authentication/data/models/authentication_sms_status_model.dart';
+import 'package:DRUN/features/authentication/data/models/user_credentials_model.dart';
+import 'package:DRUN/features/authentication/data/repositories/user_authentication_repository_impl.dart';
+import 'package:DRUN/features/authentication/data/sources/user_authentication_local_source.dart';
+import 'package:DRUN/features/authentication/data/sources/user_authentication_remote_source.dart';
+import 'package:dartz/dartz.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/mockito.dart';
+
+class MockUserAuthenticationRemoteSource extends Mock
+    implements UserAuthenticationRemoteSource {}
+
+class MockUserAuthenticationLocalSource extends Mock
+    implements UserAuthenticationLocalSource {}
+
+class MockNetworkInfo extends Mock implements NetworkInfo {}
+
+void main() {
+  UserAuthenticationRepositoryImpl repository;
+  MockUserAuthenticationRemoteSource mockUserAuthenticationRemoteSource;
+  MockUserAuthenticationLocalSource mockUserAuthenticationLocalSource;
+  MockNetworkInfo mockNetworkInfo;
+
+  setUp(() {
+    mockUserAuthenticationRemoteSource = MockUserAuthenticationRemoteSource();
+    mockUserAuthenticationLocalSource = MockUserAuthenticationLocalSource();
+    mockNetworkInfo = MockNetworkInfo();
+    repository = UserAuthenticationRepositoryImpl(
+      remoteSource: mockUserAuthenticationRemoteSource,
+      localSource: mockUserAuthenticationLocalSource,
+      networkInfo: mockNetworkInfo,
+    );
+  });
+
+  final String tPhoneNumber = '+359735780085';
+  final String tCode = '888888';
+  final AuthenticationSmsStatusModel tAuthenticationSmsStatusModelSuccess =
+      AuthenticationSmsStatusModel(
+    phoneNumber: '+359735780085',
+    succeeded: true,
+  );
+  final UserCredentialsModel tUserCredentialsModel = UserCredentialsModel(
+    userId: "4ade5874-c573-4c8f-b2b8-7db5fccd983b",
+    userToken: "713ADC1F515B3E0BBDF964DBAE6257A5B8A617115816A1705EACF9C00394A5",
+  );
+
+  group('sendAuthenticationSms', () {
+    test(
+      'should check if device is online',
+      () async {
+        // Arrange
+        when(mockNetworkInfo.isConnected).thenAnswer((_) async => true);
+
+        // Act
+        repository.sendAuthenticationSms(tPhoneNumber);
+
+        // Assert
+        verify(mockNetworkInfo.isConnected);
+      },
+    );
+
+    group('device is online', () {
+      setUp(() {
+        when(mockNetworkInfo.isConnected).thenAnswer((_) async => true);
+      });
+
+      test(
+        'should return remote data when the call to remote source is successful',
+        () async {
+          // Arrange
+          when(mockUserAuthenticationRemoteSource.sendAuthenticationSms(
+            tPhoneNumber,
+          )).thenAnswer((_) async => tAuthenticationSmsStatusModelSuccess);
+
+          // Act
+          final result = await repository.sendAuthenticationSms(tPhoneNumber);
+
+          // Assert
+          verify(mockUserAuthenticationRemoteSource.sendAuthenticationSms(
+            tPhoneNumber,
+          ));
+          expect(result, Right(tAuthenticationSmsStatusModelSuccess));
+        },
+      );
+
+      test(
+        'should return server failure when the call to remote data source is unsuccessful',
+        () async {
+          // Arrange
+          when(mockUserAuthenticationRemoteSource.sendAuthenticationSms(
+            tPhoneNumber,
+          )).thenThrow(ServerException());
+
+          // Act
+          final result = await repository.sendAuthenticationSms(tPhoneNumber);
+
+          // Assert
+          verify(mockUserAuthenticationRemoteSource.sendAuthenticationSms(
+            tPhoneNumber,
+          ));
+          verifyZeroInteractions(mockUserAuthenticationLocalSource);
+          expect(result, Left(ServerFailure()));
+        },
+      );
+    });
+
+    group('device is offline', () {
+      setUp(() {
+        when(mockNetworkInfo.isConnected).thenAnswer((_) async => false);
+      });
+
+      test(
+        'should return network failure when there is no internet connection',
+        () async {
+          // Arrange
+          when(mockUserAuthenticationRemoteSource.sendAuthenticationSms(
+            tPhoneNumber,
+          )).thenAnswer((_) async => tAuthenticationSmsStatusModelSuccess);
+
+          // Act
+          final result = await repository.sendAuthenticationSms(tPhoneNumber);
+
+          // Assert
+          verifyNever(mockUserAuthenticationRemoteSource.sendAuthenticationSms(
+            tPhoneNumber,
+          ));
+          verifyZeroInteractions(mockUserAuthenticationLocalSource);
+          expect(result, Left(NetworkFailure()));
+        },
+      );
+    });
+  });
+
+  group('verifyAuthenticationSms', () {
+    test(
+      'should check if device is online',
+      () async {
+        // Arrange
+        when(mockNetworkInfo.isConnected).thenAnswer((_) async => true);
+
+        // Act
+        repository.verifyAuthenticationSms(tPhoneNumber, tCode);
+
+        // Assert
+        verify(mockNetworkInfo.isConnected);
+      },
+    );
+
+    group('device is online', () {
+      setUp(() {
+        when(mockNetworkInfo.isConnected).thenAnswer((_) async => true);
+      });
+
+      test(
+        'should return remote data when the call to remote source is successful',
+        () async {
+          // Arrange
+          when(mockUserAuthenticationRemoteSource.verifyAuthenticationSms(
+            tPhoneNumber,
+            tCode,
+          )).thenAnswer((_) async => tUserCredentialsModel);
+
+          // Act
+          final result = await repository.verifyAuthenticationSms(
+            tPhoneNumber,
+            tCode,
+          );
+
+          // Assert
+          verify(mockUserAuthenticationRemoteSource.verifyAuthenticationSms(
+            tPhoneNumber,
+            tCode,
+          ));
+          expect(result, Right(tUserCredentialsModel));
+        },
+      );
+
+      test(
+        'should cache the data locally',
+        () async {
+          // Arrange
+          when(mockUserAuthenticationRemoteSource.verifyAuthenticationSms(
+            tPhoneNumber,
+            tCode,
+          )).thenAnswer((_) async => tUserCredentialsModel);
+
+          // Act
+          await repository.verifyAuthenticationSms(tPhoneNumber, tCode);
+
+          // Assert
+          verify(mockUserAuthenticationRemoteSource.verifyAuthenticationSms(
+            tPhoneNumber,
+            tCode,
+          ));
+          verify(mockUserAuthenticationLocalSource.cacheUserCredentials(
+            tUserCredentialsModel,
+          ));
+        },
+      );
+
+      test(
+        'should return server failure when the call to remote data source is unsuccessful',
+        () async {
+          // Arrange
+          when(mockUserAuthenticationRemoteSource.verifyAuthenticationSms(
+            tPhoneNumber,
+            tCode,
+          )).thenThrow(ServerException());
+
+          // Act
+          final result = await repository.verifyAuthenticationSms(
+            tPhoneNumber,
+            tCode,
+          );
+
+          // Assert
+          verify(mockUserAuthenticationRemoteSource.verifyAuthenticationSms(
+            tPhoneNumber,
+            tCode,
+          ));
+          verifyZeroInteractions(mockUserAuthenticationLocalSource);
+          expect(result, Left(ServerFailure()));
+        },
+      );
+    });
+
+    group('device is offline', () {
+      setUp(() {
+        when(mockNetworkInfo.isConnected).thenAnswer((_) async => false);
+      });
+
+      test(
+        'should return network failure when there is no internet connection',
+        () async {
+          // Arrange
+          when(mockUserAuthenticationRemoteSource.verifyAuthenticationSms(
+            tPhoneNumber,
+            tCode,
+          )).thenAnswer((_) async => tUserCredentialsModel);
+
+          // Act
+          final result = await repository.verifyAuthenticationSms(
+            tPhoneNumber,
+            tCode,
+          );
+
+          // Assert
+          verifyNever(
+            mockUserAuthenticationRemoteSource.verifyAuthenticationSms(
+              tPhoneNumber,
+              tCode,
+            ),
+          );
+          verifyZeroInteractions(mockUserAuthenticationLocalSource);
+          expect(result, Left(NetworkFailure()));
+        },
+      );
+    });
+  });
+
+  group('getLoggedInUser', () {
+    test(
+      'should use locally cached data',
+      () async {
+        // Arrange
+        when(mockUserAuthenticationLocalSource.getUserCredentials())
+            .thenAnswer((_) async => tUserCredentialsModel);
+
+        // Act
+        final result = await repository.getLoggedInUser();
+
+        // Assert
+        verify(mockUserAuthenticationLocalSource.getUserCredentials());
+        verifyZeroInteractions(mockUserAuthenticationRemoteSource);
+        expect(result, Right(tUserCredentialsModel));
+      },
+    );
+
+    test(
+      'should return cache failure when there is no user credentials stored',
+      () async {
+        // Arrange
+        when(mockUserAuthenticationLocalSource.getUserCredentials())
+            .thenThrow(CacheException());
+
+        // Act
+        final result = await repository.getLoggedInUser();
+
+        // Assert
+        verifyZeroInteractions(mockUserAuthenticationRemoteSource);
+        expect(result, Left(CacheFailure()));
+      },
+    );
+  });
+}
diff --git a/app/test/features/authentication/data/sources/user_authentication_local_source_test.dart b/app/test/features/authentication/data/sources/user_authentication_local_source_test.dart
new file mode 100644
index 0000000..e2da09c
--- /dev/null
+++ b/app/test/features/authentication/data/sources/user_authentication_local_source_test.dart
@@ -0,0 +1,84 @@
+import 'dart:convert';
+
+import 'package:DRUN/core/constants/constants.dart';
+import 'package:DRUN/core/errors/exceptions.dart';
+import 'package:DRUN/features/authentication/data/models/user_credentials_model.dart';
+import 'package:DRUN/features/authentication/data/sources/user_authentication_local_source.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/mockito.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+
+import '../../../../fixtures/fixture_parser.dart';
+
+class MockSharedPreferences extends Mock implements SharedPreferences {}
+
+void main() {
+  UserAuthenticationLocalSourceImpl dataSource;
+  MockSharedPreferences mockSharedPreferences;
+
+  setUp(() {
+    mockSharedPreferences = MockSharedPreferences();
+    dataSource = UserAuthenticationLocalSourceImpl(
+      sharedPreferences: mockSharedPreferences,
+    );
+  });
+
+  group('getUserCredentials', () {
+    final tUserCredentialsModelSuccess = UserCredentialsModel.fromJSON(
+      json.decode(fixture('user_credentials_success.json')),
+    );
+
+    test(
+      'should return UserCredentialsModel from SharedPreferences when there is one cached',
+      () async {
+        // Arrange
+        when(mockSharedPreferences.getString(any))
+            .thenReturn(fixture('user_credentials_success.json'));
+
+        // Act
+        final result = await dataSource.getUserCredentials();
+
+        // Assert
+        verify(mockSharedPreferences.getString(USER_CREDENTIALS_CACHE_NAME));
+        expect(result, tUserCredentialsModelSuccess);
+      },
+    );
+
+    test(
+      'should throw CacheException when there are no user credentials cached',
+      () async {
+        // Arrange
+        when(mockSharedPreferences.getString(any)).thenReturn(null);
+
+        // Act
+        final call = dataSource.getUserCredentials;
+
+        // Assert
+        expect(() => call(), throwsA(isA<CacheException>()));
+      },
+    );
+  });
+
+  group('cacheUserCredentials', () {
+    final tUserCredentialsModelSuccess = UserCredentialsModel(
+      userId: '4ade5874-c573-4c8f-b2b8-7db5fccd983b',
+      userToken:
+          '713ADC1F515B3E0BBDF964DBAE6257A5B8A617115816A1705EACF9C00394A5',
+    );
+
+    test(
+      'should call SharedPreferences to cache the data',
+      () async {
+        // Act
+        dataSource.cacheUserCredentials(tUserCredentialsModelSuccess);
+
+        // Assert
+        final jsonString = json.encode(tUserCredentialsModelSuccess.toJSON());
+        verify(mockSharedPreferences.setString(
+          USER_CREDENTIALS_CACHE_NAME,
+          jsonString,
+        ));
+      },
+    );
+  });
+}
diff --git a/app/test/features/authentication/data/sources/user_authentication_remote_source_test.dart b/app/test/features/authentication/data/sources/user_authentication_remote_source_test.dart
new file mode 100644
index 0000000..f8312db
--- /dev/null
+++ b/app/test/features/authentication/data/sources/user_authentication_remote_source_test.dart
@@ -0,0 +1,182 @@
+import 'dart:convert';
+
+import 'package:DRUN/core/constants/constants.dart';
+import 'package:DRUN/core/errors/exceptions.dart';
+import 'package:DRUN/features/authentication/data/models/authentication_sms_status_model.dart';
+import 'package:DRUN/features/authentication/data/models/user_credentials_model.dart';
+import 'package:DRUN/features/authentication/data/sources/user_authentication_remote_source.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/mockito.dart';
+import 'package:http/http.dart' as http;
+
+import '../../../../fixtures/fixture_parser.dart';
+
+class MockHttpClient extends Mock implements http.Client {}
+
+void main() {
+  UserAuthenticationRemoteSourceImpl dataSource;
+  MockHttpClient mockHttpClient;
+
+  void arrangeMockHttpClientPost(body, statusCode) {
+    when(mockHttpClient.post(
+      any,
+      body: anyNamed('body'),
+      headers: anyNamed('headers'),
+    )).thenAnswer(
+      (_) async => http.Response(
+        body,
+        statusCode,
+      ),
+    );
+  }
+
+  setUp(() {
+    mockHttpClient = MockHttpClient();
+    dataSource = UserAuthenticationRemoteSourceImpl(
+      httpClient: mockHttpClient,
+    );
+  });
+
+  group('sendAuthenticationSms', () {
+    final tPhoneNumber = '+359735780085';
+    final tAuthenticationSmsStatusModelSuccess = AuthenticationSmsStatusModel(
+      phoneNumber: '+359735780085',
+      succeeded: true,
+    );
+
+    test(
+      'should perform a POST on "/api/users/authenticate/send-sms-code" endpoint',
+      () async {
+        // Arrange
+        arrangeMockHttpClientPost(
+          fixture('authentication_sms_status_success.json'),
+          200,
+        );
+
+        // Act
+        dataSource.sendAuthenticationSms(tPhoneNumber);
+
+        // Assert
+        verify(mockHttpClient.post(
+          '$SERVER_ADDRESS/api/users/authenticate/send-sms-code',
+          body: jsonEncode({
+            'phoneNumber': tPhoneNumber,
+          }),
+          headers: {
+            'Content-Type': 'application/json',
+          },
+        ));
+      },
+    );
+
+    test(
+      'should return AuthenticationSmsStatusModel when the status code is 200',
+      () async {
+        // Arrange
+        arrangeMockHttpClientPost(
+          fixture('authentication_sms_status_success.json'),
+          200,
+        );
+
+        // Act
+        final result = await dataSource.sendAuthenticationSms(tPhoneNumber);
+
+        // Assert
+        expect(result, tAuthenticationSmsStatusModelSuccess);
+      },
+    );
+
+    test(
+      'should throw a ServerException when the status code is 4xx',
+      () async {
+        // Arrange
+        arrangeMockHttpClientPost(
+          fixture('authentication_sms_status_failure.json'),
+          400,
+        );
+
+        // Act
+        final call = dataSource.sendAuthenticationSms;
+
+        // Assert
+        expect(() async => call(tPhoneNumber), throwsA(isA<ServerException>()));
+      },
+    );
+  });
+
+  group('verifyAuthenticationSms', () {
+    final tPhoneNumber = '+359735780085';
+    final tCode = '888888';
+    final tUserCredentialsModelSuccess = UserCredentialsModel(
+      userId: "4ade5874-c573-4c8f-b2b8-7db5fccd983b",
+      userToken:
+          "713ADC1F515B3E0BBDF964DBAE6257A5B8A617115816A1705EACF9C00394A5",
+    );
+
+    test(
+      'should perform a POST on "/api/users/authenticate/verify-sms-code" endpoint',
+      () async {
+        // Arrange
+        arrangeMockHttpClientPost(
+          fixture('user_credentials_success.json'),
+          200,
+        );
+
+        // Act
+        dataSource.verifyAuthenticationSms(tPhoneNumber, tCode);
+
+        // Assert
+        verify(mockHttpClient.post(
+          '$SERVER_ADDRESS/api/users/authenticate/verify-sms-code',
+          body: jsonEncode({
+            'phoneNumber': tPhoneNumber,
+            'code': tCode,
+          }),
+          headers: {
+            'Content-Type': 'application/json',
+          },
+        ));
+      },
+    );
+
+    test(
+      'should return AuthenticationSmsStatusModel when the status code is 200',
+      () async {
+        // Arrange
+        arrangeMockHttpClientPost(
+          fixture('user_credentials_success.json'),
+          200,
+        );
+
+        // Act
+        final result = await dataSource.verifyAuthenticationSms(
+          tPhoneNumber,
+          tCode,
+        );
+
+        // Assert
+        expect(result, tUserCredentialsModelSuccess);
+      },
+    );
+
+    test(
+      'should throw a ServerException when the status code is 4xx',
+      () async {
+        // Arrange
+        arrangeMockHttpClientPost(
+          fixture('user_credentials_failure.json'),
+          400,
+        );
+
+        // Act
+        final call = dataSource.verifyAuthenticationSms;
+
+        // Assert
+        expect(
+          () async => call(tPhoneNumber, tCode),
+          throwsA(isA<ServerException>()),
+        );
+      },
+    );
+  });
+}
diff --git a/app/test/features/authentication/domain/usecases/get_logged_in_user_test.dart b/app/test/features/authentication/domain/usecases/get_logged_in_user_test.dart
new file mode 100644
index 0000000..0200475
--- /dev/null
+++ b/app/test/features/authentication/domain/usecases/get_logged_in_user_test.dart
@@ -0,0 +1,44 @@
+import 'package:DRUN/core/usecases/usecase.dart';
+import 'package:DRUN/features/authentication/domain/entities/user_credentials.dart';
+import 'package:DRUN/features/authentication/domain/repositories/user_authentication_repository.dart';
+import 'package:DRUN/features/authentication/domain/usecases/get_logged_in_user.dart';
+import 'package:dartz/dartz.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/mockito.dart';
+
+class MockUserAuthenticationRepository extends Mock
+    implements UserAuthenticationRepository {}
+
+void main() {
+  GetLoggedInUser usecase;
+  MockUserAuthenticationRepository mockUserAuthenticationRepository;
+
+  setUp(() {
+    mockUserAuthenticationRepository = MockUserAuthenticationRepository();
+    usecase = GetLoggedInUser(mockUserAuthenticationRepository);
+  });
+
+  final tUserCredentials = UserCredentials(
+    userId: '4ade5874-c573-4c8f-b2b8-7db5fccd983b',
+    userToken: '713ADC1F515B3E0BBDF964DBAE6257A5B8A617115816A1705EACF9C00394A5',
+  );
+
+  test(
+    'should get a already logged in user from the repository',
+    () async {
+      // Arrange
+      when(mockUserAuthenticationRepository.getLoggedInUser())
+          .thenAnswer((_) async => Right(tUserCredentials));
+
+      // Act
+      final result = await usecase(NoParams());
+
+      // Assert
+      expect(result, Right(tUserCredentials));
+      verify(
+        mockUserAuthenticationRepository.getLoggedInUser(),
+      );
+      verifyNoMoreInteractions(mockUserAuthenticationRepository);
+    },
+  );
+}
diff --git a/app/test/features/authentication/domain/usecases/send_authentication_sms_test.dart b/app/test/features/authentication/domain/usecases/send_authentication_sms_test.dart
new file mode 100644
index 0000000..f29342e
--- /dev/null
+++ b/app/test/features/authentication/domain/usecases/send_authentication_sms_test.dart
@@ -0,0 +1,44 @@
+import 'package:DRUN/features/authentication/domain/entities/authentication_sms_status.dart';
+import 'package:DRUN/features/authentication/domain/repositories/user_authentication_repository.dart';
+import 'package:DRUN/features/authentication/domain/usecases/send_authentication_sms.dart';
+import 'package:dartz/dartz.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/mockito.dart';
+
+class MockUserAuthenticationRepository extends Mock
+    implements UserAuthenticationRepository {}
+
+void main() {
+  SendAuthenticationSms usecase;
+  MockUserAuthenticationRepository mockUserAuthenticationRepository;
+
+  setUp(() {
+    mockUserAuthenticationRepository = MockUserAuthenticationRepository();
+    usecase = SendAuthenticationSms(mockUserAuthenticationRepository);
+  });
+
+  final tPhoneNumber = '+359735780085';
+  final tAuthenticationSmsStatus = AuthenticationSmsStatus(
+    phoneNumber: '+359735780085',
+    succeeded: true,
+  );
+
+  test(
+    'should request an authentication sms from the repository',
+    () async {
+      // Arrange
+      when(mockUserAuthenticationRepository.sendAuthenticationSms(tPhoneNumber))
+          .thenAnswer((_) async => Right(tAuthenticationSmsStatus));
+
+      // Act
+      final result = await usecase(SendParams(phoneNumber: tPhoneNumber));
+
+      // Assert
+      expect(result, Right(tAuthenticationSmsStatus));
+      verify(
+        mockUserAuthenticationRepository.sendAuthenticationSms(tPhoneNumber),
+      );
+      verifyNoMoreInteractions(mockUserAuthenticationRepository);
+    },
+  );
+}
diff --git a/app/test/features/authentication/domain/usecases/verify_authentication_sms_test.dart b/app/test/features/authentication/domain/usecases/verify_authentication_sms_test.dart
new file mode 100644
index 0000000..157ea7b
--- /dev/null
+++ b/app/test/features/authentication/domain/usecases/verify_authentication_sms_test.dart
@@ -0,0 +1,51 @@
+import 'package:DRUN/features/authentication/domain/entities/user_credentials.dart';
+import 'package:DRUN/features/authentication/domain/repositories/user_authentication_repository.dart';
+import 'package:DRUN/features/authentication/domain/usecases/verify_authentication_sms.dart';
+import 'package:dartz/dartz.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/mockito.dart';
+
+class MockUserAuthenticationRepository extends Mock
+    implements UserAuthenticationRepository {}
+
+void main() {
+  VerifyAuthenticationSms usecase;
+  MockUserAuthenticationRepository mockUserAuthenticationRepository;
+
+  setUp(() {
+    mockUserAuthenticationRepository = MockUserAuthenticationRepository();
+    usecase = VerifyAuthenticationSms(mockUserAuthenticationRepository);
+  });
+
+  final tPhoneNumber = '+359735780085';
+  final tCode = '888888';
+  final tUserCredentials = UserCredentials(
+    userId: '4ade5874-c573-4c8f-b2b8-7db5fccd983b',
+    userToken: '713ADC1F515B3E0BBDF964DBAE6257A5B8A617115816A1705EACF9C00394A5',
+  );
+
+  test(
+    'should verify an authentication sms from the repository',
+    () async {
+      // Arrange
+      when(mockUserAuthenticationRepository.verifyAuthenticationSms(
+        tPhoneNumber,
+        tCode,
+      )).thenAnswer((_) async => Right(tUserCredentials));
+
+      // Act
+      final result = await usecase(VerifyParams(
+        phoneNumber: tPhoneNumber,
+        code: tCode,
+      ));
+
+      // Assert
+      expect(result, Right(tUserCredentials));
+      verify(mockUserAuthenticationRepository.verifyAuthenticationSms(
+        tPhoneNumber,
+        tCode,
+      ));
+      verifyNoMoreInteractions(mockUserAuthenticationRepository);
+    },
+  );
+}
diff --git a/app/test/features/authentication/presentation/bloc/authentication_bloc_test.dart b/app/test/features/authentication/presentation/bloc/authentication_bloc_test.dart
new file mode 100644
index 0000000..e22e1ac
--- /dev/null
+++ b/app/test/features/authentication/presentation/bloc/authentication_bloc_test.dart
@@ -0,0 +1,315 @@
+import 'package:DRUN/core/errors/failures.dart';
+import 'package:DRUN/core/presentation/util/input_validator.dart';
+import 'package:DRUN/core/usecases/usecase.dart';
+import 'package:DRUN/features/authentication/domain/entities/authentication_sms_status.dart';
+import 'package:DRUN/features/authentication/domain/entities/user_credentials.dart';
+import 'package:DRUN/features/authentication/domain/usecases/get_logged_in_user.dart';
+import 'package:DRUN/features/authentication/domain/usecases/send_authentication_sms.dart';
+import 'package:DRUN/features/authentication/domain/usecases/verify_authentication_sms.dart';
+import 'package:DRUN/features/authentication/presentation/bloc/authentication_bloc.dart';
+import 'package:dartz/dartz.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/mockito.dart';
+
+class MockGetLoggedInUser extends Mock implements GetLoggedInUser {}
+
+class MockSendAuthenticationSms extends Mock implements SendAuthenticationSms {}
+
+class MockVerifyAuthenticationSms extends Mock
+    implements VerifyAuthenticationSms {}
+
+class MockInputValidator extends Mock implements InputValidator {}
+
+void main() {
+  AuthenticationBloc bloc;
+  MockGetLoggedInUser mockGetLoggedInUser;
+  MockSendAuthenticationSms mockSendAuthenticationSms;
+  MockVerifyAuthenticationSms mockVerifyAuthenticationSms;
+  MockInputValidator mockInputValidator;
+
+  setUp(() {
+    mockGetLoggedInUser = MockGetLoggedInUser();
+    mockSendAuthenticationSms = MockSendAuthenticationSms();
+    mockVerifyAuthenticationSms = MockVerifyAuthenticationSms();
+    mockInputValidator = MockInputValidator();
+    bloc = AuthenticationBloc(
+      getLoggedInUser: mockGetLoggedInUser,
+      sendAuthenticationSms: mockSendAuthenticationSms,
+      verifyAuthenticationSms: mockVerifyAuthenticationSms,
+      inputValidator: mockInputValidator,
+    );
+  });
+
+  test(
+    'initialState should be AuthenticationInitialState',
+    () async {
+      expect(bloc.initialState, AuthenticationInitialState());
+    },
+  );
+
+  group('GetLoggedInUserEvent', () {
+    final tUserCredentials = UserCredentials(
+      userId: "4ade5874-c573-4c8f-b2b8-7db5fccd983b",
+      userToken:
+          "713ADC1F515B3E0BBDF964DBAE6257A5B8A617115816A1705EACF9C00394A5",
+    );
+
+    test(
+      'should get data from the GetLoggedInUser usecase',
+      () async {
+        // Arrange
+        when(mockGetLoggedInUser(any))
+            .thenAnswer((_) async => Right(tUserCredentials));
+
+        // Act
+        bloc.add(GetLoggedInUserEvent());
+        await untilCalled(mockGetLoggedInUser(any));
+
+        // Assert
+        verify(mockGetLoggedInUser(NoParams()));
+      },
+    );
+
+    test(
+      'should do nothing when there are no cached UserCredentials',
+      () async {
+        // Arrange
+        when(mockGetLoggedInUser(any))
+            .thenAnswer((_) async => Left(CacheFailure()));
+
+        // Assert later
+        final expected = [
+          AuthenticationInitialState(),
+        ];
+        expectLater(bloc, emitsInOrder(expected));
+
+        // Act
+        bloc.add(GetLoggedInUserEvent());
+      },
+    );
+
+    test(
+      'should emit AuthenticationSuccessfulState when there are UserCredentials cached',
+      () async {
+        // Arrange
+        when(mockGetLoggedInUser(any))
+            .thenAnswer((_) async => Right(tUserCredentials));
+
+        // Assert later
+        final expected = [
+          AuthenticationInitialState(),
+          AuthenticationSuccessfulState(userCredentials: tUserCredentials),
+        ];
+        expectLater(bloc, emitsInOrder(expected));
+
+        // Act
+        bloc.add(GetLoggedInUserEvent());
+      },
+    );
+  });
+
+  group('StartAuthenticationEvent', () {
+    test(
+      'should emit AuthenticationPhoneInputState',
+      () async {
+        // Assert later
+        final expected = [
+          AuthenticationInitialState(),
+          AuthenticationPhoneInputState(),
+        ];
+        expectLater(bloc, emitsInOrder(expected));
+
+        // Act
+        bloc.add(StartAuthenticationEvent());
+      },
+    );
+  });
+
+  group('SendAuthenticationSmsEvent', () {
+    final tPhoneNumber = '+359735780085';
+    final tAuthenticationSmsStatus = AuthenticationSmsStatus(
+      phoneNumber: '+359735780085',
+      succeeded: true,
+    );
+
+    test(
+      'should call the InputValidator to validate phoneNumber',
+      () async {
+        // Arrange
+        when(mockInputValidator.stringAsPhoneNumber(tPhoneNumber))
+            .thenReturn(Right(tPhoneNumber));
+
+        // Act
+        bloc.add(SendAuthenticationSmsEvent(tPhoneNumber));
+        await untilCalled(mockInputValidator.stringAsPhoneNumber(any));
+
+        // Assert
+        verify(mockInputValidator.stringAsPhoneNumber(tPhoneNumber));
+      },
+    );
+
+    test(
+      'should emit AuthenticationPhoneInputErrorState when the input is invalid',
+      () async {
+        // Arrange
+        when(mockInputValidator.stringAsPhoneNumber(any)).thenReturn(
+          Left(InvalidInputFailure()),
+        );
+
+        // Assert later
+        final expected = [
+          AuthenticationInitialState(),
+          AuthenticationPhoneInputErrorState(
+              message: InvalidInputFailure().message)
+        ];
+        expectLater(bloc, emitsInOrder(expected));
+
+        // Act
+        bloc.add(SendAuthenticationSmsEvent(tPhoneNumber));
+      },
+    );
+
+    test(
+      'should get data from the SendAuthenticationSms usecase',
+      () async {
+        // Arrange
+        when(mockInputValidator.stringAsPhoneNumber(tPhoneNumber))
+            .thenReturn(Right(tPhoneNumber));
+
+        when(mockSendAuthenticationSms(any))
+            .thenAnswer((_) async => Right(tAuthenticationSmsStatus));
+
+        // Act
+        bloc.add(SendAuthenticationSmsEvent(tPhoneNumber));
+        await untilCalled(mockSendAuthenticationSms(any));
+
+        // Assert
+        verify(
+          mockSendAuthenticationSms(SendParams(phoneNumber: tPhoneNumber)),
+        );
+      },
+    );
+
+    test(
+      'should emit AuthenticationLoadingState and AuthenticationCodeInputState when data is gotten',
+      () async {
+        // Arrange
+        when(mockInputValidator.stringAsPhoneNumber(tPhoneNumber))
+            .thenReturn(Right(tPhoneNumber));
+
+        when(mockSendAuthenticationSms(any))
+            .thenAnswer((_) async => Right(tAuthenticationSmsStatus));
+
+        // Assert later
+        final expected = [
+          AuthenticationInitialState(),
+          AuthenticationLoadingState(),
+          AuthenticationCodeInputState(
+            authenticationSmsStatus: tAuthenticationSmsStatus,
+          ),
+        ];
+        expectLater(bloc, emitsInOrder(expected));
+
+        // Act
+        bloc.add(SendAuthenticationSmsEvent(tPhoneNumber));
+      },
+    );
+
+    test(
+      'should emit AuthenticationLoadingState and AuthenticationPhoneInputErrorState when the usecase fails',
+      () async {
+        // Arrange
+        when(mockInputValidator.stringAsPhoneNumber(tPhoneNumber))
+            .thenReturn(Right(tPhoneNumber));
+
+        when(mockSendAuthenticationSms(any))
+            .thenAnswer((_) async => Left(ServerFailure()));
+
+        // Assert later
+        final expected = [
+          AuthenticationInitialState(),
+          AuthenticationLoadingState(),
+          AuthenticationPhoneInputErrorState(message: ServerFailure().message),
+        ];
+        expectLater(bloc, emitsInOrder(expected));
+
+        // Act
+        bloc.add(SendAuthenticationSmsEvent(tPhoneNumber));
+      },
+    );
+  });
+
+  group('VerifyAuthenticationSmsEvent', () {
+    final tPhoneNumber = '+359735780085';
+    final tCode = '888888';
+    final tUserCredentials = UserCredentials(
+      userId: "4ade5874-c573-4c8f-b2b8-7db5fccd983b",
+      userToken:
+          "713ADC1F515B3E0BBDF964DBAE6257A5B8A617115816A1705EACF9C00394A5",
+    );
+
+    test(
+      'should get data from the VerifyAuthenticationSms usecase',
+      () async {
+        // Arrange
+        when(mockVerifyAuthenticationSms(any))
+            .thenAnswer((_) async => Right(tUserCredentials));
+
+        // Act
+        bloc.add(VerifyAuthenticationSmsEvent(tPhoneNumber, tCode));
+        await untilCalled(mockVerifyAuthenticationSms(any));
+
+        // Assert
+        verify(
+          mockVerifyAuthenticationSms(VerifyParams(
+            phoneNumber: tPhoneNumber,
+            code: tCode,
+          )),
+        );
+      },
+    );
+
+    test(
+      'should emit AuthenticationLoadingState and AuthenticationSuccessfulState when data is gotten',
+      () async {
+        // Arrange
+        when(mockVerifyAuthenticationSms(any))
+            .thenAnswer((_) async => Right(tUserCredentials));
+
+        // Assert later
+        final expected = [
+          AuthenticationInitialState(),
+          AuthenticationLoadingState(),
+          AuthenticationSuccessfulState(userCredentials: tUserCredentials),
+        ];
+        expectLater(bloc, emitsInOrder(expected));
+
+        // Act
+        bloc.add(VerifyAuthenticationSmsEvent(tPhoneNumber, tCode));
+      },
+    );
+
+    test(
+      'should emit AuthenticationLoadingState and AuthenticationCodeInputErrorState when the usecase fails',
+      () async {
+        // Arrange
+        when(mockVerifyAuthenticationSms(any))
+            .thenAnswer((_) async => Left(ServerFailure()));
+
+        // Assert later
+        final expected = [
+          AuthenticationInitialState(),
+          AuthenticationLoadingState(),
+          AuthenticationCodeInputErrorState(
+            phoneNumber: tPhoneNumber,
+            message: ServerFailure().message,
+          ),
+        ];
+        expectLater(bloc, emitsInOrder(expected));
+
+        // Act
+        bloc.add(VerifyAuthenticationSmsEvent(tPhoneNumber, tCode));
+      },
+    );
+  });
+}
diff --git a/app/test/features/home/data/models/local_contact_model_test.dart b/app/test/features/home/data/models/local_contact_model_test.dart
new file mode 100644
index 0000000..c320025
--- /dev/null
+++ b/app/test/features/home/data/models/local_contact_model_test.dart
@@ -0,0 +1,36 @@
+import 'package:DRUN/features/home/data/models/local_contact_model.dart';
+import 'package:DRUN/features/home/domain/entities/local_contact.dart';
+import 'package:contacts_service/contacts_service.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+  final tLocalContactModel = LocalContactModel(
+    name: 'Gosho Losho',
+    phoneNumber: '+359735780085',
+  );
+
+  test(
+    'should be an extension of LocalContactModel entity',
+    () async {
+      // Assert
+      expect(tLocalContactModel, isA<LocalContact>());
+    },
+  );
+
+  test(
+    'should parse from ContactService',
+    () async {
+      // Arrange
+      final contact = Contact(
+        displayName: 'Gosho Losho',
+        phones: [Item(label: 'mobile', value: '+359735780085')],
+      );
+
+      // Act
+      final result = LocalContactModel.fromContactsService(contact);
+
+      // Assert
+      expect(result, tLocalContactModel);
+    },
+  );
+}
diff --git a/app/test/features/home/data/models/registered_contact_model_test.dart b/app/test/features/home/data/models/registered_contact_model_test.dart
new file mode 100644
index 0000000..6c410c5
--- /dev/null
+++ b/app/test/features/home/data/models/registered_contact_model_test.dart
@@ -0,0 +1,46 @@
+import 'dart:convert';
+
+import 'package:DRUN/features/home/data/models/registered_contact_model.dart';
+import 'package:DRUN/features/home/domain/entities/registered_contact.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import '../../../../fixtures/fixture_parser.dart';
+
+void main() {
+  final tRegisteredContactModel1 = RegisteredContactModel(
+    phoneNumber: '+359735780085',
+    userId: '35040675-6bdb-4d26-8d38-bc46bdeaf56f',
+  );
+
+  final tRegisteredContactModel2 = RegisteredContactModel(
+    phoneNumber: '+359888888888',
+    userId: '2cc9754b-2520-4dad-be5f-2da3e3341800',
+  );
+
+  test(
+    'should be an extension of RegisteredContactModel entity',
+    () async {
+      // Assert
+      expect(tRegisteredContactModel1, isA<RegisteredContact>());
+    },
+  );
+
+  test(
+    'should parse form JSON',
+    () async {
+      // Arrange
+      final List<Map<String, dynamic>> jsonMap =
+          List<Map<String, dynamic>>.from(
+        json.decode(fixture('registered_contacts.json')),
+      );
+
+      // Act
+      final result1 = RegisteredContactModel.fromJSON(jsonMap[0]);
+      final result2 = RegisteredContactModel.fromJSON(jsonMap[1]);
+
+      // Assert
+      expect(result1, tRegisteredContactModel1);
+      expect(result2, tRegisteredContactModel2);
+    },
+  );
+}
diff --git a/app/test/features/home/data/repositories/contacts_repository_impl_test.dart b/app/test/features/home/data/repositories/contacts_repository_impl_test.dart
new file mode 100644
index 0000000..9c239cc
--- /dev/null
+++ b/app/test/features/home/data/repositories/contacts_repository_impl_test.dart
@@ -0,0 +1,207 @@
+import 'package:DRUN/core/data/sources/network_info.dart';
+import 'package:DRUN/core/errors/exceptions.dart';
+import 'package:DRUN/core/errors/failures.dart';
+import 'package:DRUN/features/home/data/models/local_contact_model.dart';
+import 'package:DRUN/features/home/data/models/registered_contact_model.dart';
+import 'package:DRUN/features/home/data/repository/contacts_repository_impl.dart';
+import 'package:DRUN/features/home/data/sources/contacts_local_source.dart';
+import 'package:DRUN/features/home/data/sources/contacts_remote_source.dart';
+import 'package:DRUN/features/home/domain/entities/complete_contact.dart';
+import 'package:dartz/dartz.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/mockito.dart';
+
+class MockContactsRemoteSource extends Mock implements ContactsRemoteSource {}
+
+class MockContactsLocalSource extends Mock implements ContactsLocalSource {}
+
+class MockNetworkInfo extends Mock implements NetworkInfo {}
+
+void main() {
+  ContactsRepositoryImpl repository;
+  MockContactsRemoteSource mockContactsRemoteSource;
+  MockContactsLocalSource mockContactsLocalSource;
+  MockNetworkInfo mockNetworkInfo;
+
+  setUp(() {
+    mockContactsRemoteSource = MockContactsRemoteSource();
+    mockContactsLocalSource = MockContactsLocalSource();
+    mockNetworkInfo = MockNetworkInfo();
+    repository = ContactsRepositoryImpl(
+      remoteSource: mockContactsRemoteSource,
+      localSource: mockContactsLocalSource,
+      networkInfo: mockNetworkInfo,
+    );
+  });
+
+  final tLocalContacts = <LocalContactModel>[
+    LocalContactModel(name: 'Gosho Losho', phoneNumber: '+359735780085'),
+    LocalContactModel(name: 'Posho Mosho', phoneNumber: '+359800857357'),
+    LocalContactModel(name: 'Oshte Gosho', phoneNumber: '+359888888888'),
+  ];
+
+  final tRegisteredContacts = <RegisteredContactModel>[
+    RegisteredContactModel(
+      phoneNumber: '+359735780085',
+      userId: '35040675-6bdb-4d26-8d38-bc46bdeaf56f',
+    ),
+    RegisteredContactModel(
+      phoneNumber: '+359888888888',
+      userId: '2cc9754b-2520-4dad-be5f-2da3e3341800',
+    ),
+  ];
+
+  final tCompleteContacts = <CompleteContact>[
+    CompleteContact(
+      name: 'Gosho Losho',
+      phoneNumber: '+359735780085',
+      userId: '35040675-6bdb-4d26-8d38-bc46bdeaf56f',
+    ),
+    CompleteContact(
+      name: 'Oshte Gosho',
+      phoneNumber: '+359888888888',
+      userId: '2cc9754b-2520-4dad-be5f-2da3e3341800',
+    ),
+  ];
+
+  group('getLocalContacts', () {
+    test(
+      'should return contacts loaded from the local phone address book',
+      () async {
+        // Arrange
+        when(mockContactsLocalSource.getLocalContactsFromAddressBook())
+            .thenAnswer((_) async => tLocalContacts);
+
+        // Act
+        final result = await repository.getLocalContacts();
+
+        // Assert
+        verify(mockContactsLocalSource.getLocalContactsFromAddressBook());
+        verifyZeroInteractions(mockContactsRemoteSource);
+        expect(result, Right(tLocalContacts));
+      },
+    );
+
+    test(
+      'should return permission failure when local contacts cannot be loaded',
+      () async {
+        // Arrange
+        when(mockContactsLocalSource.getLocalContactsFromAddressBook())
+            .thenThrow(PermissionException());
+
+        // Act
+        final result = await repository.getLocalContacts();
+
+        // Assert
+        verify(mockContactsLocalSource.getLocalContactsFromAddressBook());
+        verifyZeroInteractions(mockContactsRemoteSource);
+        expect(result, Left(PermissionFailure()));
+      },
+    );
+  });
+
+  group('getRegisteredUsersInLocalContacts', () {
+    test(
+      'should check if device is online',
+      () async {
+        // Arrange
+        when(mockNetworkInfo.isConnected).thenAnswer((_) async => true);
+        when(
+          mockContactsRemoteSource.getRegisteredUsersFromPhonesNumbers(any),
+        ).thenAnswer((_) async => tRegisteredContacts);
+
+        // Act
+        repository.getRegisteredUsersInLocalContacts(tLocalContacts);
+
+        // Assert
+        verify(mockNetworkInfo.isConnected);
+      },
+    );
+
+    group('device is online', () {
+      setUp(() {
+        when(mockNetworkInfo.isConnected).thenAnswer((_) async => true);
+      });
+
+      test(
+        'should get data from remote source',
+        () async {
+          // Arrange
+          when(
+            mockContactsRemoteSource.getRegisteredUsersFromPhonesNumbers(any),
+          ).thenAnswer((_) async => tRegisteredContacts);
+
+          // Act
+          await repository.getRegisteredUsersInLocalContacts(tLocalContacts);
+
+          // Assert
+          verify(
+            mockContactsRemoteSource.getRegisteredUsersFromPhonesNumbers(any),
+          );
+        },
+      );
+
+      test(
+        'should return server failure when the call to remote data source is unsuccessful',
+        () async {
+          // Arrange
+          when(
+            mockContactsRemoteSource.getRegisteredUsersFromPhonesNumbers(any),
+          ).thenThrow(ServerException());
+
+          // Act
+          final result = await repository.getRegisteredUsersInLocalContacts(
+            tLocalContacts,
+          );
+
+          // Assert
+          expect(result, Left(ServerFailure()));
+        },
+      );
+
+      test(
+        'should return a list of CompleteContact entities of only registered contacts',
+        () async {
+          // Arrange
+          when(
+            mockContactsRemoteSource.getRegisteredUsersFromPhonesNumbers(any),
+          ).thenAnswer((_) async => tRegisteredContacts);
+
+          // Act
+          final result = await repository.getRegisteredUsersInLocalContacts(
+            tLocalContacts,
+          );
+
+          // Assert
+          result.fold(
+            (l) => expect(l, null),
+            (r) => {
+              for (var i = 0; i < tCompleteContacts.length; i++)
+                {expect(r[0], tCompleteContacts[0])}
+            },
+          );
+        },
+      );
+    });
+
+    group('device is offline', () {
+      setUp(() {
+        when(mockNetworkInfo.isConnected).thenAnswer((_) async => false);
+      });
+
+      test(
+        'should return network failure when there is no internet connection',
+        () async {
+          // Act
+          final result = await repository
+              .getRegisteredUsersInLocalContacts(tLocalContacts);
+
+          // Assert
+          verifyZeroInteractions(mockContactsLocalSource);
+          verifyZeroInteractions(mockContactsRemoteSource);
+          expect(result, Left(NetworkFailure()));
+        },
+      );
+    });
+  });
+}
diff --git a/app/test/features/home/data/sources/contacts_local_source_test.dart b/app/test/features/home/data/sources/contacts_local_source_test.dart
new file mode 100644
index 0000000..4efd9c9
--- /dev/null
+++ b/app/test/features/home/data/sources/contacts_local_source_test.dart
@@ -0,0 +1,84 @@
+import 'package:DRUN/core/errors/exceptions.dart';
+import 'package:DRUN/features/home/data/models/local_contact_model.dart';
+import 'package:DRUN/features/home/data/sources/contacts_local_source.dart';
+import 'package:contacts_service/contacts_service.dart';
+import 'package:permission_handler/permission_handler.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/mockito.dart';
+
+class MockPermissionHandler extends Mock implements PermissionHandler {}
+
+class MockGetContacts extends Mock {
+  Future<Iterable<Contact>> call();
+}
+
+void main() {
+  ContactsLocalSourceImpl dataSource;
+  PermissionHandler mockPermissionHandler;
+  Future<Iterable<Contact>> Function() mockGetContacts;
+
+  setUp(() {
+    mockPermissionHandler = MockPermissionHandler();
+    mockGetContacts = MockGetContacts();
+    dataSource = ContactsLocalSourceImpl(
+      permissionHandler: mockPermissionHandler,
+      getContacts: mockGetContacts,
+    );
+  });
+
+  final tLocalContactsRaw = <Contact>[
+    Contact(
+      displayName: 'Gosho Losho',
+      phones: [Item(label: 'mobile', value: '+359735780085')],
+    ),
+    Contact(
+      displayName: 'Posho Mosho',
+      phones: [Item(label: 'mobile', value: '+359800857357')],
+    ),
+    Contact(
+      displayName: 'Oshte Gosho',
+      phones: [Item(label: 'mobile', value: '+359888888888')],
+    ),
+  ];
+
+  final tLocalContacts = <LocalContactModel>[
+    LocalContactModel(name: 'Gosho Losho', phoneNumber: '+359735780085'),
+    LocalContactModel(name: 'Posho Mosho', phoneNumber: '+359800857357'),
+    LocalContactModel(name: 'Oshte Gosho', phoneNumber: '+359888888888'),
+  ];
+
+  group('getLocalContactsFromAddressBook', () {
+    test(
+      'should return local contacts list when permissions are granted',
+      () async {
+        // Arrange
+        when(
+          mockPermissionHandler.checkPermissionStatus(any),
+        ).thenAnswer((_) async => PermissionStatus.granted);
+        when(mockGetContacts()).thenAnswer((_) async => tLocalContactsRaw);
+
+        // Act
+        final result = await dataSource.getLocalContactsFromAddressBook();
+
+        // Assert
+        expect(result, tLocalContacts);
+      },
+    );
+
+    test(
+      'should throw a PermissionException when permissions are denied',
+      () async {
+        // Arrange
+        when(
+          mockPermissionHandler.checkPermissionStatus(any),
+        ).thenAnswer((_) async => PermissionStatus.denied);
+
+        // Act
+        final call = dataSource.getLocalContactsFromAddressBook;
+
+        // Assert
+        expect(() async => call(), throwsA(isA<PermissionException>()));
+      },
+    );
+  });
+}
diff --git a/app/test/features/home/data/sources/contacts_repository_source_test.dart b/app/test/features/home/data/sources/contacts_repository_source_test.dart
new file mode 100644
index 0000000..68c8e4c
--- /dev/null
+++ b/app/test/features/home/data/sources/contacts_repository_source_test.dart
@@ -0,0 +1,116 @@
+import 'dart:convert';
+
+import 'package:DRUN/core/constants/constants.dart';
+import 'package:DRUN/core/errors/exceptions.dart';
+import 'package:DRUN/features/home/data/models/registered_contact_model.dart';
+import 'package:DRUN/features/home/data/sources/contacts_remote_source.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:http/http.dart' as http;
+import 'package:mockito/mockito.dart';
+
+import '../../../../fixtures/fixture_parser.dart';
+
+class MockHttpClient extends Mock implements http.Client {}
+
+void main() {
+  ContactsRemoteSourceImpl dataSource;
+  MockHttpClient mockHttpClient;
+
+  void arrangeMockHttpClientPost(body, statusCode) {
+    when(mockHttpClient.post(
+      any,
+      body: anyNamed('body'),
+      headers: anyNamed('headers'),
+    )).thenAnswer(
+      (_) async => http.Response(
+        body,
+        statusCode,
+      ),
+    );
+  }
+
+  setUp(() {
+    mockHttpClient = MockHttpClient();
+    dataSource = ContactsRemoteSourceImpl(
+      httpClient: mockHttpClient,
+    );
+  });
+
+  final tPhoneNumbers = <String>[
+    '+359735780085',
+    '+359800857357',
+    '+359888888888'
+  ];
+
+  final tRegisteredContacts = <RegisteredContactModel>[
+    RegisteredContactModel(
+      phoneNumber: '+359735780085',
+      userId: '35040675-6bdb-4d26-8d38-bc46bdeaf56f',
+    ),
+    RegisteredContactModel(
+      phoneNumber: '+359888888888',
+      userId: '2cc9754b-2520-4dad-be5f-2da3e3341800',
+    ),
+  ];
+
+  group('sendAuthenticationSms', () {
+    test(
+      'should perform a GET on "/api/users/check-registered" endpoint',
+      () async {
+        // Arrange
+        arrangeMockHttpClientPost(fixture('registered_contacts.json'), 200);
+
+        // Act
+        dataSource.getRegisteredUsersFromPhonesNumbers(tPhoneNumbers);
+
+        // Assert
+        verify(mockHttpClient.post(
+          '$SERVER_ADDRESS/api/users/check-registered',
+          body: jsonEncode({
+            'phoneNumbers': tPhoneNumbers,
+          }),
+          headers: {
+            'Content-Type': 'application/json',
+          },
+        ));
+      },
+    );
+
+    test(
+      'should return a list of RegisteredContactModels when the status code is 200',
+      () async {
+        // Arrange
+        arrangeMockHttpClientPost(fixture('registered_contacts.json'), 200);
+
+        // Act
+        final result = await dataSource.getRegisteredUsersFromPhonesNumbers(
+          tPhoneNumbers,
+        );
+
+        // Assert
+        for (var i = 0; i < tRegisteredContacts.length; i++) {
+          expect(result[i], tRegisteredContacts[i]);
+        }
+      },
+    );
+
+    test(
+      'should throw a ServerException when the status code is 4xx',
+      () async {
+        // Arrange
+        arrangeMockHttpClientPost(fixture('registered_contacts.json'), 400);
+
+        // Act
+        final call = dataSource.getRegisteredUsersFromPhonesNumbers;
+
+        // Assert
+        expect(
+          () async => call(tPhoneNumbers),
+          throwsA(
+            isA<ServerException>(),
+          ),
+        );
+      },
+    );
+  });
+}
diff --git a/app/test/features/home/domain/usecases/get_contacts_test.dart b/app/test/features/home/domain/usecases/get_contacts_test.dart
new file mode 100644
index 0000000..e7bdf06
--- /dev/null
+++ b/app/test/features/home/domain/usecases/get_contacts_test.dart
@@ -0,0 +1,75 @@
+import 'package:DRUN/core/usecases/usecase.dart';
+import 'package:DRUN/features/home/domain/entities/complete_contact.dart';
+import 'package:DRUN/features/home/domain/entities/local_contact.dart';
+import 'package:DRUN/features/home/domain/repositories/contacts_repository.dart';
+import 'package:DRUN/features/home/domain/usecases/get_contacts.dart';
+import 'package:dartz/dartz.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/mockito.dart';
+
+class MockContactsRepository extends Mock implements ContactsRepository {}
+
+void main() {
+  GetContacts usecase;
+  MockContactsRepository mockContactsRepository;
+
+  setUp(() {
+    mockContactsRepository = MockContactsRepository();
+    usecase = GetContacts(mockContactsRepository);
+  });
+
+  final tLocalContacts = <LocalContact>[
+    LocalContact(name: 'Gosho Losho', phoneNumber: '+359735780085'),
+    LocalContact(name: 'Posho Mosho', phoneNumber: '+359800857357'),
+    LocalContact(name: 'Oshte Gosho', phoneNumber: '+359888888888'),
+  ];
+
+  final tCompleteContacts = <CompleteContact>[
+    CompleteContact(
+      name: 'Gosho Losho',
+      phoneNumber: '+359735780085',
+      userId: '35040675-6bdb-4d26-8d38-bc46bdeaf56f',
+    ),
+    CompleteContact(
+      name: 'Oshte Gosho',
+      phoneNumber: '+359888888888',
+      userId: '2cc9754b-2520-4dad-be5f-2da3e3341800',
+    ),
+  ];
+
+  test(
+    'should get local phone contacts list from the repository',
+    () async {
+      // Arrange
+      when(mockContactsRepository.getLocalContacts())
+          .thenAnswer((_) async => Right(tLocalContacts));
+
+      // Act
+      await usecase(NoParams());
+
+      // Assert
+      verify(mockContactsRepository.getLocalContacts());
+    },
+  );
+
+  test(
+    'should get registered users from local contacts list from the repository',
+    () async {
+      // Arrange
+      when(mockContactsRepository.getLocalContacts())
+          .thenAnswer((_) async => Right(tLocalContacts));
+
+      when(mockContactsRepository.getRegisteredUsersInLocalContacts(any))
+          .thenAnswer((_) async => Right(tCompleteContacts));
+
+      // Act
+      final result = await usecase(NoParams());
+
+      // Assert
+      expect(result, Right(tCompleteContacts));
+      verify(mockContactsRepository.getRegisteredUsersInLocalContacts(
+        tLocalContacts,
+      ));
+    },
+  );
+}
diff --git a/app/test/features/home/presentation/bloc/home_bloc_test.dart b/app/test/features/home/presentation/bloc/home_bloc_test.dart
new file mode 100644
index 0000000..aa8909b
--- /dev/null
+++ b/app/test/features/home/presentation/bloc/home_bloc_test.dart
@@ -0,0 +1,186 @@
+import 'package:DRUN/core/errors/failures.dart';
+import 'package:DRUN/core/usecases/usecase.dart';
+import 'package:DRUN/features/authentication/domain/entities/user_credentials.dart';
+import 'package:DRUN/features/home/domain/entities/complete_contact.dart';
+import 'package:DRUN/features/home/domain/usecases/get_contacts.dart';
+import 'package:DRUN/features/home/presentation/bloc/home_bloc.dart';
+import 'package:dartz/dartz.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/mockito.dart';
+
+class MockGetContacts extends Mock implements GetContacts {}
+
+void main() {
+  HomeBloc bloc;
+  MockGetContacts mockGetContacts;
+
+  setUp(() {
+    mockGetContacts = MockGetContacts();
+    bloc = HomeBloc(getContacts: mockGetContacts);
+  });
+
+  test(
+    'initialState should be HomeInitialState',
+    () async {
+      expect(bloc.initialState, HomeInitialState());
+    },
+  );
+
+  group('GetContactsEvent', () {
+    final tUserCredentials = UserCredentials(
+      userId: "4ade5874-c573-4c8f-b2b8-7db5fccd983b",
+      userToken:
+          "713ADC1F515B3E0BBDF964DBAE6257A5B8A617115816A1705EACF9C00394A5",
+    );
+
+    final tCompleteContacts = <CompleteContact>[
+      CompleteContact(
+        name: 'Gosho Losho',
+        phoneNumber: '+359735780085',
+        userId: '35040675-6bdb-4d26-8d38-bc46bdeaf56f',
+      ),
+      CompleteContact(
+        name: 'Oshte Gosho',
+        phoneNumber: '+359888888888',
+        userId: '2cc9754b-2520-4dad-be5f-2da3e3341800',
+      ),
+    ];
+
+    test(
+      'should get data from the GetContacts usecase',
+      () async {
+        // Arrange
+        when(mockGetContacts(any))
+            .thenAnswer((_) async => Right(tCompleteContacts));
+
+        // Act
+        bloc.add(GetContactsEvent(tUserCredentials));
+        await untilCalled(mockGetContacts(any));
+
+        // Assert
+        verify(mockGetContacts(NoParams()));
+      },
+    );
+
+    test(
+      'should emit HomeFailedState when there is a failure',
+      () async {
+        // Arrange
+        when(mockGetContacts(any))
+            .thenAnswer((_) async => Left(PermissionFailure()));
+
+        // Assert later
+        final expected = [
+          HomeInitialState(),
+          HomeFailureState(message: PermissionFailure().message)
+        ];
+        expectLater(bloc, emitsInOrder(expected));
+
+        // Act
+        bloc.add(GetContactsEvent(tUserCredentials));
+      },
+    );
+
+    test(
+      'should emit HomeAuthenticatedState when contacts are successfully loaded',
+      () async {
+        // Arrange
+        when(mockGetContacts(any))
+            .thenAnswer((_) async => Right(tCompleteContacts));
+
+        // Assert later
+        final expected = [
+          HomeInitialState(),
+          HomeAuthenticatedState(
+            userCredentials: tUserCredentials,
+            contacts: tCompleteContacts,
+          ),
+        ];
+        expectLater(bloc, emitsInOrder(expected));
+
+        // Act
+        bloc.add(GetContactsEvent(tUserCredentials));
+      },
+    );
+  });
+
+  group('HomeContactSelectedEvent', () {
+    final tUserCredentials = UserCredentials(
+      userId: "4ade5874-c573-4c8f-b2b8-7db5fccd983b",
+      userToken:
+          "713ADC1F515B3E0BBDF964DBAE6257A5B8A617115816A1705EACF9C00394A5",
+    );
+
+    final tCompleteContacts = <CompleteContact>[
+      CompleteContact(
+        name: 'Gosho Losho',
+        phoneNumber: '+359735780085',
+        userId: '35040675-6bdb-4d26-8d38-bc46bdeaf56f',
+      ),
+      CompleteContact(
+        name: 'Oshte Gosho',
+        phoneNumber: '+359888888888',
+        userId: '2cc9754b-2520-4dad-be5f-2da3e3341800',
+      ),
+    ];
+
+    test(
+      'should emit HomeContactSelectedState and HomeFailureState when an invalid userId is selected',
+      () async {
+        // Arrange
+        when(mockGetContacts(any))
+            .thenAnswer((_) async => Right(tCompleteContacts));
+
+        // Assert later
+        final expected = [
+          HomeInitialState(),
+          HomeAuthenticatedState(
+            userCredentials: tUserCredentials,
+            contacts: tCompleteContacts,
+          ),
+          HomeFailureState(message: ContactSelectionFailure().message),
+        ];
+        expectLater(bloc, emitsInOrder(expected));
+
+        // Act
+        bloc.add(GetContactsEvent(tUserCredentials));
+        bloc.add(HomeContactSelectedEvent(
+          tUserCredentials,
+          tCompleteContacts,
+          'non-existent id',
+        ));
+      },
+    );
+
+    test(
+      'should emit HomeContactSelectedState and HomeContactSelectedState when an valid userId is selected',
+      () async {
+        // Arrange
+        when(mockGetContacts(any))
+            .thenAnswer((_) async => Right(tCompleteContacts));
+
+        // Assert later
+        final expected = [
+          HomeInitialState(),
+          HomeAuthenticatedState(
+            userCredentials: tUserCredentials,
+            contacts: tCompleteContacts,
+          ),
+          HomeContactSelectedState(
+            userCredentials: tUserCredentials,
+            selectedContact: tCompleteContacts[0],
+          )
+        ];
+        expectLater(bloc, emitsInOrder(expected));
+
+        // Act
+        bloc.add(GetContactsEvent(tUserCredentials));
+        bloc.add(HomeContactSelectedEvent(
+          tUserCredentials,
+          tCompleteContacts,
+          '35040675-6bdb-4d26-8d38-bc46bdeaf56f',
+        ));
+      },
+    );
+  });
+}
diff --git a/app/test/features/order/data/models/delivery_order_model_test.dart b/app/test/features/order/data/models/delivery_order_model_test.dart
new file mode 100644
index 0000000..81fc24a
--- /dev/null
+++ b/app/test/features/order/data/models/delivery_order_model_test.dart
@@ -0,0 +1,42 @@
+import 'dart:convert';
+
+import 'package:DRUN/features/order/data/models/delivery_order_model.dart';
+import 'package:DRUN/features/order/domain/entities/delivery_order.dart';
+import 'package:DRUN/features/order/domain/entities/location.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import '../../../../fixtures/fixture_parser.dart';
+
+void main() {
+  final tDeliveryOrderModel = DeliveryOrderModel(
+    senderUserId: '4ade5874-c573-4c8f-b2b8-7db5fccd983b',
+    senderLocationCoordinates:
+        LocationCoordinates(latitude: 42.662388, longitude: 23.373416),
+    receiverUserId: '35040675-6bdb-4d26-8d38-bc46bdeaf56f',
+    receiverLocationCoordinates:
+        LocationCoordinates(latitude: 42.652900, longitude: 23.354952),
+    paymentCardToken: 'tok_1IMSCX2eZvKYlo2CE3RnWinh',
+  );
+
+  test(
+    'should be an extension of DeliveryOrder entity',
+    () async {
+      // Assert
+      expect(
+        tDeliveryOrderModel,
+        isA<DeliveryOrder>(),
+      );
+    },
+  );
+
+  test(
+    'should convert to JSON',
+    () async {
+      // Act
+      final result = tDeliveryOrderModel.toJSON();
+
+      // Assert
+      expect(result, json.decode(fixture('delivery_order_request.json')));
+    },
+  );
+}
diff --git a/app/test/features/order/data/models/location_model_test.dart b/app/test/features/order/data/models/location_model_test.dart
new file mode 100644
index 0000000..8053749
--- /dev/null
+++ b/app/test/features/order/data/models/location_model_test.dart
@@ -0,0 +1,36 @@
+import 'dart:convert';
+
+import 'package:DRUN/features/order/data/models/location_model.dart';
+import 'package:DRUN/features/order/domain/entities/location.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import '../../../../fixtures/fixture_parser.dart';
+
+void main() {
+  final tLocationCoordinatesModel = LocationCoordinatesModel(
+    latitude: 42.662388,
+    longitude: 23.373416,
+  );
+
+  test(
+    'should be an extension of DeliveryOrder entity',
+    () async {
+      // Assert
+      expect(
+        tLocationCoordinatesModel,
+        isA<LocationCoordinates>(),
+      );
+    },
+  );
+
+  test(
+    'should convert to JSON',
+    () async {
+      // Act
+      final result = tLocationCoordinatesModel.toJSON();
+
+      // Assert
+      expect(result, json.decode(fixture('location.json')));
+    },
+  );
+}
diff --git a/app/test/features/order/data/repositories/orders_repository_impl_test.dart b/app/test/features/order/data/repositories/orders_repository_impl_test.dart
new file mode 100644
index 0000000..08e537d
--- /dev/null
+++ b/app/test/features/order/data/repositories/orders_repository_impl_test.dart
@@ -0,0 +1,221 @@
+import 'package:DRUN/core/data/sources/network_info.dart';
+import 'package:DRUN/core/errors/exceptions.dart';
+import 'package:DRUN/core/errors/failures.dart';
+import 'package:DRUN/features/order/data/models/delivery_order_model.dart';
+import 'package:DRUN/features/order/data/repositories/orders_repository_impl.dart';
+import 'package:DRUN/features/order/data/sources/orders_local_source.dart';
+import 'package:DRUN/features/order/data/sources/orders_remote_source.dart';
+import 'package:DRUN/features/order/domain/entities/location.dart';
+import 'package:dartz/dartz.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/mockito.dart';
+
+class MockOrdersRemoteSource extends Mock implements OrdersRemoteSource {}
+
+class MockOrdersLocalSource extends Mock implements OrdersLocalSource {}
+
+class MockNetworkInfo extends Mock implements NetworkInfo {}
+
+void main() {
+  OrdersRepositoryImpl repository;
+  MockOrdersRemoteSource mockOrdersRemoteSource;
+  MockOrdersLocalSource mockOrdersLocalSource;
+  MockNetworkInfo mockNetworkInfo;
+
+  setUp(() {
+    mockOrdersRemoteSource = MockOrdersRemoteSource();
+    mockOrdersLocalSource = MockOrdersLocalSource();
+    mockNetworkInfo = MockNetworkInfo();
+    repository = OrdersRepositoryImpl(
+      remoteSource: mockOrdersRemoteSource,
+      localSource: mockOrdersLocalSource,
+      networkInfo: mockNetworkInfo,
+    );
+  });
+
+  final tLocationCoordinates =
+      LocationCoordinates(latitude: 42.662388, longitude: 23.373416);
+  final tPaymentCardToken = '4ade5874-c573-4c8f-b2b8-7db5fccd983b';
+  final tDeliveryOrderModel = DeliveryOrderModel(
+    senderUserId: '4ade5874-c573-4c8f-b2b8-7db5fccd983b',
+    senderLocationCoordinates:
+        LocationCoordinates(latitude: 42.662388, longitude: 23.373416),
+    receiverUserId: '35040675-6bdb-4d26-8d38-bc46bdeaf56f',
+    receiverLocationCoordinates:
+        LocationCoordinates(latitude: 42.652900, longitude: 23.354952),
+    paymentCardToken: 'tok_1IMSCX2eZvKYlo2CE3RnWinh',
+  );
+  final tDeliveryOrderId = '905c801d-c0cc-47db-851e-9b1aaf3adb13';
+
+  group('getPaymentCardToken', () {
+    test(
+      'should return payment card token string',
+      () async {
+        // Arrange
+        when(
+          mockOrdersRemoteSource.getPaymentCardToken(),
+        ).thenAnswer((_) async => tPaymentCardToken);
+
+        // Act
+        final result = await repository.getPaymentCardToken();
+
+        // Assert
+        verify(mockOrdersRemoteSource.getPaymentCardToken());
+        verifyZeroInteractions(mockOrdersLocalSource);
+        expect(result, Right(tPaymentCardToken));
+      },
+    );
+
+    test(
+      'should return payment card failure when a token cannot be created',
+      () async {
+        // Arrange
+        when(
+          mockOrdersRemoteSource.getPaymentCardToken(),
+        ).thenThrow(PaymentCardException());
+
+        // Act
+        final result = await repository.getPaymentCardToken();
+
+        // Assert
+        verify(mockOrdersRemoteSource.getPaymentCardToken());
+        verifyZeroInteractions(mockOrdersLocalSource);
+        expect(result, Left(PaymentCardFailure()));
+      },
+    );
+  });
+
+  group('getCurrentLocation', () {
+    test(
+      'should return current device location',
+      () async {
+        // Arrange
+        when(mockOrdersLocalSource.getCurrentLocationCoordinates())
+            .thenAnswer((_) async => tLocationCoordinates);
+
+        // Act
+        final result = await repository.getCurrentLocation();
+
+        // Assert
+        verify(mockOrdersLocalSource.getCurrentLocationCoordinates());
+        verifyZeroInteractions(mockOrdersRemoteSource);
+        expect(result, Right(tLocationCoordinates));
+      },
+    );
+
+    test(
+      'should return permission failure when local contacts cannot be loaded',
+      () async {
+        // Arrange
+        when(mockOrdersLocalSource.getCurrentLocationCoordinates())
+            .thenThrow(PermissionException());
+
+        // Act
+        final result = await repository.getCurrentLocation();
+
+        // Assert
+        verify(mockOrdersLocalSource.getCurrentLocationCoordinates());
+        verifyZeroInteractions(mockOrdersRemoteSource);
+        expect(result, Left(PermissionFailure()));
+      },
+    );
+  });
+
+  group('makeDeliveryOrder', () {
+    test(
+      'should check if device is online',
+      () async {
+        // Arrange
+        when(mockNetworkInfo.isConnected).thenAnswer((_) async => true);
+        when(
+          mockOrdersRemoteSource.makeDeliveryOrder(any),
+        ).thenAnswer((_) async => tDeliveryOrderId);
+
+        // Act
+        await repository.makeDeliveryOrder(tDeliveryOrderModel);
+
+        // Assert
+        verify(mockNetworkInfo.isConnected);
+      },
+    );
+
+    group('device is online', () {
+      setUp(() {
+        when(mockNetworkInfo.isConnected).thenAnswer((_) async => true);
+      });
+
+      test(
+        'should get data from remote source',
+        () async {
+          // Arrange
+          when(
+            mockOrdersRemoteSource.makeDeliveryOrder(any),
+          ).thenAnswer((_) async => tDeliveryOrderId);
+
+          // Act
+          await repository.makeDeliveryOrder(tDeliveryOrderModel);
+
+          // Assert
+          verify(mockOrdersRemoteSource.makeDeliveryOrder(any));
+        },
+      );
+
+      test(
+        'should return server failure when the call to remote data source is unsuccessful',
+        () async {
+          // Arrange
+          when(
+            mockOrdersRemoteSource.makeDeliveryOrder(any),
+          ).thenThrow(ServerException());
+
+          // Act
+          final result = await repository.makeDeliveryOrder(
+            tDeliveryOrderModel,
+          );
+
+          // Assert
+          expect(result, Left(ServerFailure()));
+        },
+      );
+
+      test(
+        'should return an order id from remote source',
+        () async {
+          // Arrange
+          when(
+            mockOrdersRemoteSource.makeDeliveryOrder(any),
+          ).thenAnswer((_) async => tDeliveryOrderId);
+
+          // Act
+          final result = await repository.makeDeliveryOrder(
+            tDeliveryOrderModel,
+          );
+
+          // Assert
+          expect(result, Right(tDeliveryOrderId));
+        },
+      );
+    });
+
+    group('device is offline', () {
+      setUp(() {
+        when(mockNetworkInfo.isConnected).thenAnswer((_) async => false);
+      });
+
+      test(
+        'should return network failure when there is no internet connection',
+        () async {
+          // Act
+          final result = await repository.makeDeliveryOrder(
+            tDeliveryOrderModel,
+          );
+
+          // Assert
+          verifyZeroInteractions(mockOrdersLocalSource);
+          verifyZeroInteractions(mockOrdersRemoteSource);
+          expect(result, Left(NetworkFailure()));
+        },
+      );
+    });
+  });
+}
diff --git a/app/test/features/order/domain/usecases/get_current_location_coordinates_test.dart b/app/test/features/order/domain/usecases/get_current_location_coordinates_test.dart
new file mode 100644
index 0000000..9e02f5b
--- /dev/null
+++ b/app/test/features/order/domain/usecases/get_current_location_coordinates_test.dart
@@ -0,0 +1,44 @@
+import 'package:DRUN/core/usecases/usecase.dart';
+import 'package:DRUN/features/order/domain/entities/location.dart';
+import 'package:DRUN/features/order/domain/repositories/orders_repository.dart';
+import 'package:DRUN/features/order/domain/usecases/get_current_location_coordinates.dart';
+import 'package:dartz/dartz.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/mockito.dart';
+
+class MockOrderRepository extends Mock implements OrdersRepository {}
+
+void main() {
+  GetCurrentLocationCoordinates usecase;
+  MockOrderRepository mockOrderRepository;
+
+  setUp(() {
+    mockOrderRepository = MockOrderRepository();
+    usecase = GetCurrentLocationCoordinates(
+      mockOrderRepository,
+    );
+  });
+
+  final tLocation =
+      LocationCoordinates(latitude: 42.662388, longitude: 23.373416);
+
+  test(
+    'should get current location from the repository',
+    () async {
+      // Arrange
+      when(
+        mockOrderRepository.getCurrentLocation(),
+      ).thenAnswer((_) async => Right(tLocation));
+
+      // Act
+      final result = await usecase(NoParams());
+
+      // Assert
+      expect(result, Right(tLocation));
+      verify(
+        mockOrderRepository.getCurrentLocation(),
+      );
+      verifyNoMoreInteractions(mockOrderRepository);
+    },
+  );
+}
diff --git a/app/test/features/order/domain/usecases/get_payment_card_token_test.dart b/app/test/features/order/domain/usecases/get_payment_card_token_test.dart
new file mode 100644
index 0000000..1ab56fc
--- /dev/null
+++ b/app/test/features/order/domain/usecases/get_payment_card_token_test.dart
@@ -0,0 +1,40 @@
+import 'package:DRUN/core/usecases/usecase.dart';
+import 'package:DRUN/features/order/domain/repositories/orders_repository.dart';
+import 'package:DRUN/features/order/domain/usecases/get_payment_card_token.dart';
+import 'package:dartz/dartz.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/mockito.dart';
+
+class MockOrderRepository extends Mock implements OrdersRepository {}
+
+void main() {
+  GetPaymentCardToken usecase;
+  MockOrderRepository mockOrderRepository;
+
+  setUp(() {
+    mockOrderRepository = MockOrderRepository();
+    usecase = GetPaymentCardToken(mockOrderRepository);
+  });
+
+  final tPaymentCardToken = 'tok_1IMSCX2eZvKYlo2CE3RnWinh';
+
+  test(
+    'should get payment card token from the repository',
+    () async {
+      // Arrange
+      when(
+        mockOrderRepository.getPaymentCardToken(),
+      ).thenAnswer((_) async => Right(tPaymentCardToken));
+
+      // Act
+      final result = await usecase(NoParams());
+
+      // Assert
+      expect(result, Right(tPaymentCardToken));
+      verify(
+        mockOrderRepository.getPaymentCardToken(),
+      );
+      verifyNoMoreInteractions(mockOrderRepository);
+    },
+  );
+}
diff --git a/app/test/features/order/domain/usecases/make_delivery_order_test.dart b/app/test/features/order/domain/usecases/make_delivery_order_test.dart
new file mode 100644
index 0000000..3a20717
--- /dev/null
+++ b/app/test/features/order/domain/usecases/make_delivery_order_test.dart
@@ -0,0 +1,51 @@
+import 'package:DRUN/features/order/domain/entities/location.dart';
+import 'package:DRUN/features/order/domain/entities/delivery_order.dart';
+import 'package:DRUN/features/order/domain/repositories/orders_repository.dart';
+import 'package:DRUN/features/order/domain/usecases/make_delivery_order.dart';
+import 'package:dartz/dartz.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/mockito.dart';
+
+class MockOrderRepository extends Mock implements OrdersRepository {}
+
+void main() {
+  MakeDeliveryOrder usecase;
+  MockOrderRepository mockOrderRepository;
+
+  setUp(() {
+    mockOrderRepository = MockOrderRepository();
+    usecase = MakeDeliveryOrder(mockOrderRepository);
+  });
+
+  final tDeliveryOrder = DeliveryOrder(
+    senderUserId: '4ade5874-c573-4c8f-b2b8-7db5fccd983b',
+    senderLocationCoordinates:
+        LocationCoordinates(latitude: 42.662388, longitude: 23.373416),
+    receiverUserId: '35040675-6bdb-4d26-8d38-bc46bdeaf56f',
+    receiverLocationCoordinates:
+        LocationCoordinates(latitude: 42.652900, longitude: 23.354952),
+    paymentCardToken: 'tok_1IMSCX2eZvKYlo2CE3RnWinh',
+  );
+
+  final tDeliveryOrderId = '905c801d-c0cc-47db-851e-9b1aaf3adb13';
+
+  test(
+    'should make delivery order request to the repository',
+    () async {
+      // Arrange
+      when(
+        mockOrderRepository.makeDeliveryOrder(any),
+      ).thenAnswer((_) async => Right(tDeliveryOrderId));
+
+      // Act
+      final result = await usecase(DeliveryOrderParams(order: tDeliveryOrder));
+
+      // Assert
+      expect(result, Right(tDeliveryOrderId));
+      verify(
+        mockOrderRepository.makeDeliveryOrder(tDeliveryOrder),
+      );
+      verifyNoMoreInteractions(mockOrderRepository);
+    },
+  );
+}
diff --git a/app/test/fixtures/authentication_sms_status_failure.json b/app/test/fixtures/authentication_sms_status_failure.json
new file mode 100644
index 0000000..57cf58d
--- /dev/null
+++ b/app/test/fixtures/authentication_sms_status_failure.json
@@ -0,0 +1,3 @@
+{
+    "error": "Invalid phone number."
+}
diff --git a/app/test/fixtures/authentication_sms_status_success.json b/app/test/fixtures/authentication_sms_status_success.json
new file mode 100644
index 0000000..fcf12e8
--- /dev/null
+++ b/app/test/fixtures/authentication_sms_status_success.json
@@ -0,0 +1,4 @@
+{
+    "phoneNumber": "+359735780085",
+    "succeeded": true
+}
diff --git a/app/test/fixtures/delivery_order_request.json b/app/test/fixtures/delivery_order_request.json
new file mode 100644
index 0000000..6ddc3a5
--- /dev/null
+++ b/app/test/fixtures/delivery_order_request.json
@@ -0,0 +1,17 @@
+{
+    "sender": {
+      "id": "4ade5874-c573-4c8f-b2b8-7db5fccd983b",
+      "location": {
+        "latitude": 42.662388,
+        "longitude": 23.373416
+      }
+    },
+    "receiver": {
+      "id": "35040675-6bdb-4d26-8d38-bc46bdeaf56f",
+      "location": {
+        "latitude": 42.652900,
+        "longitude": 23.354952
+      }
+    },
+    "paymentCardToken": "tok_1IMSCX2eZvKYlo2CE3RnWinh"
+}
\ No newline at end of file
diff --git a/app/test/fixtures/fixture_parser.dart b/app/test/fixtures/fixture_parser.dart
new file mode 100644
index 0000000..fcb2f09
--- /dev/null
+++ b/app/test/fixtures/fixture_parser.dart
@@ -0,0 +1,3 @@
+import 'dart:io';
+
+String fixture(String name) => File('test/fixtures/$name').readAsStringSync();
diff --git a/app/test/fixtures/location.json b/app/test/fixtures/location.json
new file mode 100644
index 0000000..197dae6
--- /dev/null
+++ b/app/test/fixtures/location.json
@@ -0,0 +1,4 @@
+{
+    "latitude": 42.662388,
+    "longitude": 23.373416
+}
diff --git a/app/test/fixtures/registered_contacts.json b/app/test/fixtures/registered_contacts.json
new file mode 100644
index 0000000..a5a5119
--- /dev/null
+++ b/app/test/fixtures/registered_contacts.json
@@ -0,0 +1,10 @@
+[
+    {
+        "phoneNumber": "+359735780085",
+        "userId": "35040675-6bdb-4d26-8d38-bc46bdeaf56f"
+    },
+    {
+        "phoneNumber": "+359888888888",
+        "userId": "2cc9754b-2520-4dad-be5f-2da3e3341800"
+    }
+]
diff --git a/app/test/fixtures/user_credentials_failure.json b/app/test/fixtures/user_credentials_failure.json
new file mode 100644
index 0000000..9379532
--- /dev/null
+++ b/app/test/fixtures/user_credentials_failure.json
@@ -0,0 +1,3 @@
+{
+    "error": "Invalid verification code."
+}
diff --git a/app/test/fixtures/user_credentials_success.json b/app/test/fixtures/user_credentials_success.json
new file mode 100644
index 0000000..4b46ff5
--- /dev/null
+++ b/app/test/fixtures/user_credentials_success.json
@@ -0,0 +1,4 @@
+{
+    "userId": "4ade5874-c573-4c8f-b2b8-7db5fccd983b",
+    "userToken": "713ADC1F515B3E0BBDF964DBAE6257A5B8A617115816A1705EACF9C00394A5"
+}
diff --git a/app/test/widget_test.dart b/app/test/widget_test.dart
deleted file mode 100644
index bf114dd..0000000
--- a/app/test/widget_test.dart
+++ /dev/null
@@ -1,30 +0,0 @@
-// This is a basic Flutter widget test.
-//
-// To perform an interaction with a widget in your test, use the WidgetTester
-// utility that Flutter provides. For example, you can send tap and scroll
-// gestures. You can also use WidgetTester to find child widgets in the widget
-// tree, read text, and verify that the values of widget properties are correct.
-
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-
-import 'package:DRUN/main.dart';
-
-void main() {
-  testWidgets('Counter increments smoke test', (WidgetTester tester) async {
-    // Build our app and trigger a frame.
-    await tester.pumpWidget(DRUN());
-
-    // Verify that our counter starts at 0.
-    expect(find.text('0'), findsOneWidget);
-    expect(find.text('1'), findsNothing);
-
-    // Tap the '+' icon and trigger a frame.
-    await tester.tap(find.byIcon(Icons.add));
-    await tester.pump();
-
-    // Verify that our counter has incremented.
-    expect(find.text('0'), findsNothing);
-    expect(find.text('1'), findsOneWidget);
-  });
-}