- Firebase emulator added

- Update to Espresso tests
 - Updated gradle dependencies for espresso

Took 9 hours 56 minutes
This commit is contained in:
2023-03-13 22:36:53 +00:00
parent 88604e6970
commit 4b0db6cd19
52 changed files with 464 additions and 207 deletions

View File

@@ -37,11 +37,16 @@ jobs:
# The next step will run the unit tests
- android/run-tests:
test-command: ./gradlew testDriverDebugUnitTest --continue
# Then start the emulator and run the Instrumentation tests!
# - android/start-emulator-and-run-tests:
# test-command: ./gradlew connectedDebugAndroidTest
# system-image: system-images;android-25;google_apis;x86
# Then start the firebase emulators for the Instrumentation tests!
- run:
name: Setup Firebase Emulators
command: |
curl -sL firebase.tools | bash
firebase emulators:start
# Then start the emulator and run the Instrumentation tests!
- android/start-emulator-and-run-tests:
test-command: ./gradlew connectedDebugAndroidTest
system-image: system-images;android-25;google_apis;x86
# And finally run the release build
# - run:

5
.firebaserc Normal file
View File

@@ -0,0 +1,5 @@
{
"projects": {
"default": "driver-8f4a1"
}
}

1
.gitignore vendored
View File

@@ -13,3 +13,4 @@
/build
/captures
.externalNativeBuild
*.log

2
.idea/misc.xml generated
View File

@@ -39,7 +39,7 @@
</value>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_17_PREVIEW" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View File

@@ -29,6 +29,17 @@ android {
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}
android {
sourceSets {
test {
resources.srcDirs += ['src/test/resources']
}
androidTest {
resources.srcDirs += ['src/androidTest/resources']
}
}
}
signingConfigs {
release {
storePassword relStorePassword
@@ -94,42 +105,47 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
implementation 'androidx.viewpager:viewpager:1.0.0'
testImplementation "junit:junit:4.13.2"
androidTestImplementation "androidx.test.ext:junit:1.1.5"
androidTestImplementation "androidx.test.espresso:espresso-core:3.5.1"
androidTestImplementation "androidx.test:rules:1.5.0"
implementation 'androidx.test.espresso:espresso-idling-resource:3.5.1'
implementation "androidx.legacy:legacy-support-v4:1.0.0"
// Google play services
testImplementation "junit:junit:4.13.2"
/ * Android Espresso */
def testJunitVersion = "1.1.5"
def testRunnerVersion = "1.5.2"
def espressoVersion = "3.5.1"
androidTestImplementation "androidx.test.ext:junit:$testJunitVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
androidTestImplementation "androidx.test.espresso.idling:idling-concurrent:$espressoVersion"
implementation "androidx.test.espresso:espresso-idling-resource:$espressoVersion"
androidTestImplementation "androidx.test:runner:$testRunnerVersion"
/ * Google play services */
implementation "com.google.android.gms:play-services-auth:20.4.1"
// Google firebase
def firebaseVer = "20.1.0"
implementation "com.google.firebase:firebase-core:$firebaseVer"
implementation "com.google.firebase:firebase-auth:21.0.0"
implementation "com.google.firebase:firebase-storage:$firebaseVer"
implementation "com.google.firebase:firebase-database:$firebaseVer"
// Photoviewer
/ * Google firebase */
def firebaseCore = "21.1.1"
def firebaseAuth = "20.0.0"
def firebaseStorage = "20.0.0"
def firebaseDatabase = "19.4.0"
implementation "com.google.firebase:firebase-core:$firebaseCore"
implementation "com.google.firebase:firebase-auth:$firebaseAuth"
implementation "com.google.firebase:firebase-storage:$firebaseStorage"
implementation "com.google.firebase:firebase-database:$firebaseDatabase"
/ * Photoviewer */
implementation "com.github.chrisbanes:PhotoView:2.1.0"
/* Picasso photo loader */
/ * Picasso photo loader */
implementation "com.squareup.picasso:picasso:2.71828"
/* Gson */
/ * Gson */
implementation "com.google.code.gson:gson:2.8.9"
/* coroutines support for firebase operations */
/ * coroutines support for firebase operations */
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.6.1"
// Circle Image View
/ * Circle Image View */
implementation "com.mikhaellopez:circularimageview:4.2.0"
//Kodein Dependency Injection
/ * Kodein Dependency Injection */
def kodein_version = "6.2.1"
implementation "org.kodein.di:kodein-di-generic-jvm:$kodein_version"
implementation "org.kodein.di:kodein-di-framework-android-x:$kodein_version"
/* Image Carousal */
/ * Image Carousal */
implementation 'com.synnapps:carouselview:0.1.5'
/ * Glide */
implementation 'com.github.bumptech.glide:glide:4.12.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
/ * OKHttp */
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
}

View File

@@ -6,15 +6,15 @@ import androidx.test.espresso.Espresso.onData
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.ViewInteraction
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.withId
import org.hamcrest.CoreMatchers.*
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.anything
open class BaseTestRobot {
fun fillEditText(resId: Int, text: String): ViewInteraction =
fun fillEditText(resId: Int, text: String?): ViewInteraction =
onView(withId(resId)).perform(ViewActions.replaceText(text), ViewActions.closeSoftKeyboard())
fun clickButton(resId: Int): ViewInteraction = onView((withId(resId))).perform(ViewActions.click())
@@ -22,7 +22,7 @@ open class BaseTestRobot {
fun textView(resId: Int): ViewInteraction = onView(withId(resId))
fun matchText(viewInteraction: ViewInteraction, text: String): ViewInteraction = viewInteraction
.check(ViewAssertions.matches(ViewMatchers.withText(text)))
.check(matches(ViewMatchers.withText(text)))
fun matchText(resId: Int, text: String): ViewInteraction = matchText(textView(resId), text)

View File

@@ -1,38 +1,60 @@
package h_mal.appttude.com
import android.view.View
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.IdlingRegistry
import androidx.test.rule.ActivityTestRule
import h_mal.appttude.com.espresso.IdlingResourceClass
import org.junit.AfterClass
import org.junit.BeforeClass
import org.junit.Ignore
import org.junit.Rule
import androidx.test.espresso.IdlingResource
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import androidx.test.platform.app.InstrumentationRegistry
import h_mal.appttude.com.base.BaseActivity
import org.hamcrest.Matcher
import org.junit.After
import org.junit.Before
abstract class BaseUiTest<T : AppCompatActivity> {
@Ignore
abstract fun getApplicationClass(): Class<T>
open class BaseUiTest<T : BaseActivity<*>>(
private val activity: Class<T>
) {
@get:Rule
var mActivityTestRule = ActivityTestRule(getApplicationClass())
private lateinit var mActivityScenarioRule: ActivityScenario<T>
private var mIdlingResource: IdlingResource? = null
companion object {
@BeforeClass
@JvmStatic
fun setUp() {
IdlingRegistry.getInstance().register(IdlingResourceClass.countingIdlingResource)
}
@AfterClass
@JvmStatic
fun tearDown() {
IdlingRegistry.getInstance().unregister(IdlingResourceClass.countingIdlingResource)
@Before
fun setup() {
beforeLaunch()
mActivityScenarioRule = ActivityScenario.launch(activity)
mActivityScenarioRule.onActivity {
mIdlingResource = it.getIdlingResource()!!
IdlingRegistry.getInstance().register(mIdlingResource)
}
}
fun getResourceString(@StringRes stringRes: Int): String {
return mActivityTestRule.activity.getString(stringRes)
@After
fun tearDown() {
mIdlingResource?.let {
IdlingRegistry.getInstance().unregister(it)
}
}
fun getResourceString(@StringRes stringRes: Int): String {
return InstrumentationRegistry.getInstrumentation().targetContext.resources.getString(
stringRes
)
}
fun waitFor(delay: Long) {
onView(isRoot()).perform(object : ViewAction {
override fun getConstraints(): Matcher<View> = isRoot()
override fun getDescription(): String = "wait for $delay milliseconds"
override fun perform(uiController: UiController, v: View?) {
uiController.loopMainThreadForAtLeast(delay)
}
})
}
open fun beforeLaunch() {}
}

View File

@@ -0,0 +1,9 @@
package h_mal.appttude.com
private const val apiKey = "test_key"
const val signUpFirebase = "http://identitytoolkit.googleapis.com/v1/accounts:signUp?key=$apiKey"
const val deleteAccountFirebase = "http://10.0.2.2:9099/identitytoolkit.googleapis.com/v1/accounts:delete?key=$apiKey"
const val USER_PASSWORD = "LetMeIn123!"

View File

@@ -0,0 +1,78 @@
package h_mal.appttude.com
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.database.FirebaseDatabase
import com.google.firebase.storage.FirebaseStorage
import h_mal.appttude.com.base.BaseActivity
import h_mal.appttude.com.data.FirebaseAuthSource
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.tasks.await
import org.junit.After
import org.junit.BeforeClass
open class FirebaseTest<T : BaseActivity<*>>(
activity: Class<T>,
private val registered: Boolean = false,
private val signedIn: Boolean = false
) : BaseUiTest<T>(activity) {
private val firebaseAuthSource by lazy { FirebaseAuthSource() }
private var email: String? = null
companion object {
/**
* Setup firebase emulators before all tests
*/
@JvmStatic
@BeforeClass
fun setupFirebase() {
FirebaseAuth.getInstance().useEmulator("10.0.2.2", 9099)
FirebaseDatabase.getInstance().useEmulator("10.0.2.2", 9000)
FirebaseStorage.getInstance().useEmulator("10.0.2.2", 9199)
}
}
override fun beforeLaunch() {
if (registered) {
runBlocking {
setupUser()
}
if (!signedIn) firebaseAuthSource.logOut()
}
}
@After
fun tearDownFirebase() = runBlocking {
removeUser()
firebaseAuthSource.logOut()
}
suspend fun setupUser(
signInEmail: String = generateEmailAddress(),
password: String = USER_PASSWORD
) {
email = signInEmail
firebaseAuthSource.registerUser(signInEmail, password).await().user
}
// remove the user we created for testing
suspend fun removeUser() {
getEmail()?.let {
firebaseAuthSource.reauthenticate(it, USER_PASSWORD).await()
firebaseAuthSource.deleteProfile().await()
}
}
fun generateEmailAddress(): String {
val suffix = (1000..50000).random()
return "test-${suffix}@test-account.com"
}
fun getEmail(): String? {
firebaseAuthSource.getUser()?.email?.let {
return it
}
return email
}
}

View File

@@ -0,0 +1,62 @@
package h_mal.appttude.com
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.suspendCancellableCoroutine
import okhttp3.*
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.IOException
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
class WebUtils {
private val okHttpClient by lazy { OkHttpClient() }
suspend fun <T : Any> post(url: String, body: String): T? {
val requestBody = body.toRequestBody()
val request = Request.Builder()
.post(requestBody)
.url(url)
.build()
return okHttpClient.newCall(request).await()
}
suspend fun get(url: String): String? {
val request: Request = Request.Builder()
.url(url)
.build()
return okHttpClient.newCall(request).await()
}
private suspend fun <T> Call.await(): T? {
val objectMapper = Gson()
val typeToken: TypeToken<T> = object : TypeToken<T>() {}
return suspendCancellableCoroutine { continuation ->
enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
continuation.resume(
objectMapper.fromJson(
response.body?.string(),
typeToken.type
)
)
}
override fun onFailure(call: Call, e: IOException) {
// Don't bother with resuming the continuation if it is already cancelled.
if (continuation.isCancelled) return
continuation.resumeWithException(e)
}
})
continuation.invokeOnCancellation {
try {
cancel()
} catch (ex: Throwable) {
//Ignore cancel exception
}
}
}
}
}

View File

@@ -0,0 +1,10 @@
package h_mal.appttude.com.firebase
data class SignUpResponse(
val expiresIn: String? = null,
val kind: String? = null,
val idToken: String? = null,
val localId: String? = null,
val email: String? = null,
val refreshToken: String? = null
)

View File

@@ -1,42 +0,0 @@
package h_mal.appttude.com.tests
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.rule.ActivityTestRule
import com.google.firebase.auth.FirebaseAuth
import h_mal.appttude.com.BaseUiTest
import h_mal.appttude.com.R
import h_mal.appttude.com.robots.home
import h_mal.appttude.com.ui.user.LoginActivity
import h_mal.appttude.com.robots.login
import h_mal.appttude.com.robots.register
import org.junit.*
import org.junit.runner.RunWith
@LargeTest
@RunWith(AndroidJUnit4::class)
class LoginActivityTest: BaseUiTest<LoginActivity>() {
@Ignore
override fun getApplicationClass() = LoginActivity::class.java
@After
fun afterTest(){
FirebaseAuth.getInstance().signOut()
}
@Test
fun verifyUserLogin_validUsernameAndPassword_loggedIn() {
login {
setEmail("test-user@testuserdriver.com")
setPassword("Password1234")
clickLogin()
}
home {
checkTitleExists(getResourceString(R.string.welcome_title))
}
}
}

View File

@@ -8,10 +8,4 @@ class HomeRobot: BaseTestRobot() {
fun checkTitleExists(title: String) = matchText(R.id.prova_title_tv, title)
fun clickLogin() = clickButton(R.id.email_sign_in_button)
fun clickRegister() = clickButton(R.id.register_button)
fun clickForgotPassword() = clickButton(R.id.forgot)
}

View File

@@ -7,7 +7,7 @@ import h_mal.appttude.com.R
fun login(func: LoginRobot.() -> Unit) = LoginRobot().apply { func() }
class LoginRobot: BaseTestRobot() {
fun setEmail(email: String) = fillEditText(R.id.email, email);
fun setEmail(email: String?) = fillEditText(R.id.email, email)
fun setPassword(pass: String) = fillEditText(R.id.password, pass)

View File

@@ -0,0 +1,55 @@
package h_mal.appttude.com.tests
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import h_mal.appttude.com.FirebaseTest
import h_mal.appttude.com.R
import h_mal.appttude.com.USER_PASSWORD
import h_mal.appttude.com.robots.home
import h_mal.appttude.com.robots.login
import h_mal.appttude.com.robots.register
import h_mal.appttude.com.ui.user.LoginActivity
import org.junit.*
import org.junit.runner.RunWith
@LargeTest
@RunWith(AndroidJUnit4::class)
class UserAuthenticationActivityTest : FirebaseTest<LoginActivity>(LoginActivity::class.java, registered = true, signedIn = false) {
@Before
fun waitForSplashScreen() {
waitFor(100)
}
@Test
fun verifyUserRegistration_validUsernameAndPassword_loggedIn() {
login {
clickRegister()
}
register {
setName("Test User")
setEmail(generateEmailAddress())
setPassword(USER_PASSWORD)
setPasswordConfirm(USER_PASSWORD)
clickLogin()
}
home {
checkTitleExists(getResourceString(R.string.welcome_title))
}
}
@Test
fun verifyUserLogin_validUsernameAndPassword_loggedIn() {
login {
println("lets get the email = ${getEmail()}")
setEmail(getEmail())
setPassword(USER_PASSWORD)
clickLogin()
}
home {
checkTitleExists(getResourceString(R.string.welcome_title))
}
}
}

View File

@@ -0,0 +1,7 @@
<manifest xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<application android:usesCleartextTraffic="true"
tools:ignore="MissingApplicationIcon" />
</manifest>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain>10.0.2.2</domain>
</domain-config>
</network-security-config>

View File

@@ -4,10 +4,7 @@ import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import h_mal.appttude.com.R
import h_mal.appttude.com.base.BaseFragment
import h_mal.appttude.com.utils.navigateTo
import h_mal.appttude.com.viewmodels.DriverLicenseViewModel
import kotlinx.android.synthetic.main.fragment_driver_overall.*
class DriverOverallFragment : Fragment(R.layout.fragment_driver_overall) {

View File

@@ -10,10 +10,8 @@ import h_mal.appttude.com.utils.navigateTo
import h_mal.appttude.com.utils.show
import h_mal.appttude.com.viewmodels.RoleViewModel
import kotlinx.android.synthetic.main.driver_profile_request.*
import kotlinx.android.synthetic.main.fragment_home_driver.*
import kotlinx.android.synthetic.main.home_buttons_container.*
import kotlinx.android.synthetic.main.home_buttons_container.driver
class HomeFragment : DataSubmissionBaseFragment<RoleViewModel, String>(R.layout.fragment_home_driver) {

View File

@@ -4,9 +4,7 @@ import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import h_mal.appttude.com.R
import h_mal.appttude.com.base.BaseFragment
import h_mal.appttude.com.utils.navigateTo
import h_mal.appttude.com.viewmodels.DriverLicenseViewModel
import kotlinx.android.synthetic.main.fragment_vehicle_overall.*

View File

@@ -1,8 +1,8 @@
package h_mal.appttude.com.ui
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.View
import androidx.fragment.app.Fragment
import h_mal.appttude.com.R
import h_mal.appttude.com.utils.navigateTo
import kotlinx.android.synthetic.driver.fragment_welcome.*

View File

@@ -3,9 +3,9 @@ package h_mal.appttude.com.ui.driverprofile
import android.net.Uri
import android.os.Bundle
import android.view.View
import h_mal.appttude.com.dialogs.DateDialog
import h_mal.appttude.com.R
import h_mal.appttude.com.base.DataSubmissionBaseFragment
import h_mal.appttude.com.dialogs.DateDialog
import h_mal.appttude.com.model.DriverProfileObject
import h_mal.appttude.com.utils.setGlideImage
import h_mal.appttude.com.viewmodels.DriverProfileViewModel

View File

@@ -3,9 +3,9 @@ package h_mal.appttude.com.ui.driverprofile
import android.net.Uri
import android.os.Bundle
import android.view.View
import h_mal.appttude.com.dialogs.DateDialog
import h_mal.appttude.com.R
import h_mal.appttude.com.base.DataSubmissionBaseFragment
import h_mal.appttude.com.dialogs.DateDialog
import h_mal.appttude.com.model.PrivateHireObject
import h_mal.appttude.com.utils.setGlideImage
import h_mal.appttude.com.viewmodels.PrivateHireLicenseViewModel

View File

@@ -3,9 +3,9 @@ package h_mal.appttude.com.ui.vehicleprofile
import android.net.Uri
import android.os.Bundle
import android.view.View
import h_mal.appttude.com.model.LogbookObject
import h_mal.appttude.com.R
import h_mal.appttude.com.base.DataSubmissionBaseFragment
import h_mal.appttude.com.model.LogbookObject
import h_mal.appttude.com.utils.setGlideImage
import h_mal.appttude.com.viewmodels.LogbookViewModel
import kotlinx.android.synthetic.main.fragment_logbook.*

View File

@@ -3,10 +3,9 @@ package h_mal.appttude.com.ui.vehicleprofile
import android.net.Uri
import android.os.Bundle
import android.view.View
import h_mal.appttude.com.dialogs.DateDialog
import h_mal.appttude.com.R
import h_mal.appttude.com.base.DataSubmissionBaseFragment
import h_mal.appttude.com.dialogs.DateDialog
import h_mal.appttude.com.model.MotObject
import h_mal.appttude.com.utils.setGlideImage
import h_mal.appttude.com.viewmodels.MotViewModel

View File

@@ -3,11 +3,10 @@ package h_mal.appttude.com.ui.vehicleprofile
import android.net.Uri
import android.os.Bundle
import android.view.View
import h_mal.appttude.com.dialogs.DateDialog
import h_mal.appttude.com.model.PrivateHireVehicleObject
import h_mal.appttude.com.R
import h_mal.appttude.com.base.DataSubmissionBaseFragment
import h_mal.appttude.com.dialogs.DateDialog
import h_mal.appttude.com.model.PrivateHireVehicleObject
import h_mal.appttude.com.utils.setGlideImage
import h_mal.appttude.com.viewmodels.PrivateHireVehicleViewModel
import kotlinx.android.synthetic.main.fragment_private_hire_vehicle.*

View File

@@ -2,10 +2,9 @@ package h_mal.appttude.com.ui.vehicleprofile
import android.os.Bundle
import android.view.View
import h_mal.appttude.com.dialogs.DateDialog
import h_mal.appttude.com.R
import h_mal.appttude.com.base.DataSubmissionBaseFragment
import h_mal.appttude.com.dialogs.DateDialog
import h_mal.appttude.com.model.VehicleProfileObject
import h_mal.appttude.com.viewmodels.VehicleProfileViewModel
import kotlinx.android.synthetic.main.fragment_vehicle_setup.*

View File

@@ -6,11 +6,7 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ListView
import androidx.fragment.app.Fragment
import com.google.firebase.database.DataSnapshot
import com.google.firebase.database.DatabaseError
import com.google.firebase.database.DatabaseReference
import com.google.firebase.database.ValueEventListener
import h_mal.appttude.com.R
class ArchiveFragment : Fragment() {

View File

@@ -1,19 +1,13 @@
package h_mal.appttude.com.Archive
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
//import h_mal.appttude.com.Global.FirebaseClass
//import h_mal.appttude.com.Global.ImageSwiperClass
//import h_mal.appttude.com.Objects.ArchiveObject
import h_mal.appttude.com.model.InsuranceObject
import h_mal.appttude.com.R
import h_mal.appttude.com.model.VehicleProfileObject
import android.content.Context
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.TextView
import h_mal.appttude.com.utils.DateUtils.convertDateStringDatePattern
import java.text.ParseException

View File

@@ -4,7 +4,6 @@ import android.app.Application
import h_mal.appttude.com.data.FirebaseAuthSource
import h_mal.appttude.com.data.FirebaseDatabaseSource
import h_mal.appttude.com.data.FirebaseStorageSource
import h_mal.appttude.com.espresso.IdlingResourceClass
import org.kodein.di.Kodein
import org.kodein.di.KodeinAware
import org.kodein.di.android.x.androidXModule

View File

@@ -4,27 +4,25 @@ import android.content.Intent
import android.os.Bundle
import android.view.View
import android.view.ViewGroup.LayoutParams
import android.view.ViewGroup.LayoutParams.*
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import androidx.activity.viewModels
import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import h_mal.appttude.com.application.ApplicationViewModelFactory
import androidx.test.espresso.IdlingResource
import h_mal.appttude.com.R
import h_mal.appttude.com.application.ApplicationViewModelFactory
import h_mal.appttude.com.data.ViewState
import h_mal.appttude.com.espresso.IdlingResourceClass
import h_mal.appttude.com.utils.displayToast
import h_mal.appttude.com.utils.hide
import h_mal.appttude.com.utils.show
import h_mal.appttude.com.utils.triggerAnimation
import h_mal.appttude.com.utils.*
import org.kodein.di.KodeinAware
import org.kodein.di.android.kodein
import org.kodein.di.generic.instance
abstract class BaseActivity<V : BaseViewModel> : AppCompatActivity(), KodeinAware {
// The Idling Resource which will be null in production.
private var mIdlingResource: BasicIdlingResource? = null
private lateinit var loadingView: View
abstract fun getViewModel(): V?
@@ -32,7 +30,6 @@ abstract class BaseActivity<V : BaseViewModel> : AppCompatActivity(), KodeinAwar
override val kodein by kodein()
val factory by instance<ApplicationViewModelFactory>()
private val idlingResource by instance<IdlingResourceClass>()
inline fun <reified VM : ViewModel> createLazyViewModel(): Lazy<VM> = viewModels { factory }
inline fun <reified VM : ViewModel> createViewModel(): VM =
@@ -54,6 +51,7 @@ abstract class BaseActivity<V : BaseViewModel> : AppCompatActivity(), KodeinAwar
* loading
*/
private fun instantiateLoadingView(){
// loadingView = View.inflate(this, R.layout.progress_layout, null)
loadingView = layoutInflater.inflate(R.layout.progress_layout, null)
loadingView.setOnClickListener(null)
addContentView(loadingView, LayoutParams(MATCH_PARENT, MATCH_PARENT))
@@ -76,7 +74,7 @@ abstract class BaseActivity<V : BaseViewModel> : AppCompatActivity(), KodeinAwar
open fun onStarted() {
loadingView.fadeIn()
loading = true
IdlingResourceClass.increment()
mIdlingResource?.setIdleState(false)
}
/**
@@ -85,7 +83,7 @@ abstract class BaseActivity<V : BaseViewModel> : AppCompatActivity(), KodeinAwar
open fun onSuccess(data: Any?) {
loadingView.fadeOut()
loading = false
IdlingResourceClass.decrement()
mIdlingResource?.setIdleState(true)
}
/**
@@ -95,17 +93,17 @@ abstract class BaseActivity<V : BaseViewModel> : AppCompatActivity(), KodeinAwar
error?.let { displayToast(it) }
loadingView.fadeOut()
loading = false
IdlingResourceClass.decrement()
mIdlingResource?.setIdleState(true)
}
private fun configureObserver() {
getViewModel()?.uiState?.observe(this, Observer {
getViewModel()?.uiState?.observe(this) {
when (it) {
is ViewState.HasStarted -> onStarted()
is ViewState.HasData<*> -> onSuccess(it.data.getContentIfNotHandled())
is ViewState.HasError -> onFailure(it.error.getContentIfNotHandled())
}
})
}
}
private fun View.fadeIn() = apply {
@@ -123,4 +121,14 @@ abstract class BaseActivity<V : BaseViewModel> : AppCompatActivity(), KodeinAwar
if (!loading) super.onBackPressed()
}
/**
* Only called from test, creates and returns a new [BasicIdlingResource].
*/
@VisibleForTesting
fun getIdlingResource(): IdlingResource? {
if (mIdlingResource == null) {
mIdlingResource = BasicIdlingResource()
}
return mIdlingResource
}
}

View File

@@ -1,10 +1,12 @@
package h_mal.appttude.com.base
import android.net.Uri
import androidx.lifecycle.MutableLiveData
import com.google.firebase.database.DatabaseReference
import com.google.firebase.storage.StorageReference
import h_mal.appttude.com.data.*
import h_mal.appttude.com.data.FirebaseAuthentication
import h_mal.appttude.com.data.FirebaseCompletion
import h_mal.appttude.com.data.FirebaseDatabaseSource
import h_mal.appttude.com.data.FirebaseStorageSource
import h_mal.appttude.com.utils.Coroutines.io
import h_mal.appttude.com.utils.DateUtils.getDateTimeStamp
import h_mal.appttude.com.utils.getDataFromDatabaseRef

View File

@@ -28,7 +28,7 @@ class FirebaseAuthSource: FirebaseAuthentication{
): Task<Void> {
val profileUpdates = UserProfileChangeRequest.Builder().apply {
name?.let { setDisplayName(it) }
profilePic?.let { setPhotoUri(it) }
profilePic?.let { photoUri = it }
}.build()
return getCurrentUser().updateProfile(profileUpdates)
}

View File

@@ -2,7 +2,6 @@ package h_mal.appttude.com.data
import androidx.lifecycle.LiveData
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseUser
class FirebaseLiveData(
private val firebaseAuth: FirebaseAuth

View File

@@ -1,7 +1,6 @@
package h_mal.appttude.com.data
import com.google.firebase.auth.FirebaseUser
import h_mal.appttude.com.utils.Event
sealed class UserAuthState {
object LoggedOut : UserAuthState()

View File

@@ -5,16 +5,13 @@ import android.app.AlertDialog
import android.app.DatePickerDialog
import android.app.DatePickerDialog.OnDateSetListener
import android.icu.util.Calendar
import android.os.Build
import android.view.View
import android.widget.EditText
import androidx.annotation.RequiresApi
import h_mal.appttude.com.R
import h_mal.appttude.com.utils.DateUtils
private const val DATE_FORMAT = "dd/MM/yyyy"
@RequiresApi(api = Build.VERSION_CODES.N)
@Suppress("DEPRECATION")
class DateDialog(
private val editText: EditText,
dateSelected:(String?) -> Unit

View File

@@ -1,24 +0,0 @@
package h_mal.appttude.com.espresso
import androidx.test.espresso.idling.CountingIdlingResource
object IdlingResourceClass {
private val CLASS_NAME = "IdlingResourceClass"
private const val RESOURCE = "GLOBAL"
@JvmField val countingIdlingResource = CountingIdlingResource(RESOURCE)
fun increment() {
if (!countingIdlingResource.isIdleNow) {
countingIdlingResource.increment()
}
}
fun decrement() {
if (countingIdlingResource.isIdleNow) {
countingIdlingResource.decrement()
}
}
}

View File

@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

View File

@@ -5,9 +5,9 @@ import android.content.Intent
import android.os.Bundle
import com.google.firebase.auth.AuthResult
import com.google.firebase.auth.FirebaseUser
import h_mal.appttude.com.ui.MainActivity
import h_mal.appttude.com.R
import h_mal.appttude.com.base.BaseActivity
import h_mal.appttude.com.ui.MainActivity
import h_mal.appttude.com.viewmodels.UserViewModel

View File

@@ -0,0 +1,32 @@
package h_mal.appttude.com.utils
import androidx.test.espresso.IdlingResource
import androidx.test.espresso.IdlingResource.ResourceCallback
import java.util.concurrent.atomic.AtomicBoolean
class BasicIdlingResource : IdlingResource {
private lateinit var mCallback: ResourceCallback
// Idleness is controlled with this boolean.
private val mIsIdleNow: AtomicBoolean = AtomicBoolean(true)
override fun getName(): String = this.javaClass.name
override fun isIdleNow(): Boolean = mIsIdleNow.get()
override fun registerIdleTransitionCallback(callback: ResourceCallback) {
mCallback = callback
}
/**
* Sets the new idle state, if isIdleNow is true, it pings the [ResourceCallback].
* @param isIdleNow false if there are pending operations, true if idle.
*/
fun setIdleState(isIdleNow: Boolean) {
mIsIdleNow.set(isIdleNow)
if (isIdleNow) {
mCallback.onTransitionToIdle()
}
}
}

View File

@@ -3,8 +3,7 @@ package h_mal.appttude.com.utils
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.PackageManager.*
import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.net.Uri
import android.provider.Settings
import androidx.appcompat.app.AlertDialog

View File

@@ -1,5 +1,6 @@
package h_mal.appttude.com.utils
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.Intent
@@ -20,13 +21,9 @@ import androidx.annotation.DrawableRes
import androidx.appcompat.widget.SearchView
import androidx.fragment.app.Fragment
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.squareup.picasso.Picasso
import com.squareup.picasso.Target
import h_mal.appttude.com.R
import com.bumptech.glide.request.target.Target as GlideRequestTarget
fun View.show() {
this.visibility = View.VISIBLE
@@ -54,6 +51,7 @@ fun EditText.setEnterPressedListener(action: () -> Unit) {
})
}
@SuppressLint("CheckResult")
fun ImageView.setGlideImage(
url: String?,
@DrawableRes placeholderRes: Int = R.drawable.choice_img_round
@@ -70,6 +68,7 @@ fun ImageView.setGlideImage(
.into(this)
}
@SuppressLint("CheckResult")
fun ImageView.setGlideImage(
url: Uri?,
@DrawableRes placeholderRes: Int = R.drawable.choice_img_round

View File

@@ -3,11 +3,11 @@ package h_mal.appttude.com.viewmodels
import android.net.Uri
import com.google.firebase.database.DatabaseReference
import com.google.firebase.storage.StorageReference
import h_mal.appttude.com.model.InsuranceObject
import h_mal.appttude.com.base.DataSubmissionBaseViewModel
import h_mal.appttude.com.data.FirebaseAuthentication
import h_mal.appttude.com.data.FirebaseDatabaseSource
import h_mal.appttude.com.data.FirebaseStorageSource
import h_mal.appttude.com.model.InsuranceObject
import h_mal.appttude.com.utils.Coroutines.io
class InsuranceViewModel (
@@ -17,7 +17,7 @@ class InsuranceViewModel (
) : DataSubmissionBaseViewModel<InsuranceObject>(auth, database, storage) {
override val databaseRef: DatabaseReference = database.getInsuranceDetailsRef(uid)
override val storageRef: StorageReference? = storage.insuranceStorageRef(uid)
override val storageRef: StorageReference = storage.insuranceStorageRef(uid)
override val objectName: String = "insurance"
override fun getDataFromDatabase() = getDataClass<InsuranceObject>()

View File

@@ -3,11 +3,11 @@ package h_mal.appttude.com.viewmodels
import android.net.Uri
import com.google.firebase.database.DatabaseReference
import com.google.firebase.storage.StorageReference
import h_mal.appttude.com.model.LogbookObject
import h_mal.appttude.com.base.DataSubmissionBaseViewModel
import h_mal.appttude.com.data.FirebaseAuthentication
import h_mal.appttude.com.data.FirebaseDatabaseSource
import h_mal.appttude.com.data.FirebaseStorageSource
import h_mal.appttude.com.model.LogbookObject
import h_mal.appttude.com.utils.Coroutines.io
class LogbookViewModel (

View File

@@ -3,11 +3,11 @@ package h_mal.appttude.com.viewmodels
import android.net.Uri
import com.google.firebase.database.DatabaseReference
import com.google.firebase.storage.StorageReference
import h_mal.appttude.com.model.PrivateHireVehicleObject
import h_mal.appttude.com.base.DataSubmissionBaseViewModel
import h_mal.appttude.com.data.FirebaseAuthentication
import h_mal.appttude.com.data.FirebaseDatabaseSource
import h_mal.appttude.com.data.FirebaseStorageSource
import h_mal.appttude.com.model.PrivateHireVehicleObject
import h_mal.appttude.com.utils.Coroutines.io
class PrivateHireVehicleViewModel (
@@ -17,7 +17,7 @@ class PrivateHireVehicleViewModel (
) : DataSubmissionBaseViewModel<PrivateHireVehicleObject>(auth, database, storage) {
override val databaseRef: DatabaseReference = database.getPrivateHireVehicleRef(uid)
override val storageRef: StorageReference? = storage.privateHireVehicleStorageRef(uid)
override val storageRef: StorageReference = storage.privateHireVehicleStorageRef(uid)
override val objectName: String = "private hire vehicle license"
override fun getDataFromDatabase() = getDataClass<PrivateHireVehicleObject>()

View File

@@ -7,8 +7,6 @@ import h_mal.appttude.com.data.FirebaseCompletion
import h_mal.appttude.com.data.FirebaseStorageSource
import h_mal.appttude.com.utils.Coroutines.io
import kotlinx.coroutines.tasks.await
import java.io.IOException
import java.lang.Exception
class UpdateUserViewModel(
private val auth: FirebaseAuthentication,

View File

@@ -7,8 +7,6 @@ import h_mal.appttude.com.data.FirebaseCompletion
import h_mal.appttude.com.utils.Coroutines.io
import kotlinx.coroutines.delay
import kotlinx.coroutines.tasks.await
import java.io.IOException
import java.lang.Exception
class UserViewModel(
val auth: FirebaseAuthentication

View File

@@ -1 +0,0 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"></resources>

10
database.rules.json Normal file
View File

@@ -0,0 +1,10 @@
{
"rules": {
"user": {
".read": true,
"$user_id": {
".write": "$user_id === auth.uid"
}
}
}
}

23
firebase.json Normal file
View File

@@ -0,0 +1,23 @@
{
"emulators": {
"auth": {
"port": 9099
},
"database": {
"port": 9000
},
"storage": {
"port": 9199
},
"ui": {
"enabled": true
},
"singleProjectMode": true
},
"database": {
"rules": "database.rules.json"
},
"storage": {
"rules": "storage.rules"
}
}

8
storage.rules Normal file
View File

@@ -0,0 +1,8 @@
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if false;
}
}
}