[
  {
    "path": ".gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n/.idea/libraries\n/.idea/modules.xml\n/.idea/workspace.xml\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n"
  },
  {
    "path": "README.md",
    "content": "### Please Note:\n- Since I see people are still looking at this repo, I want to be clear that I no longer recommend multi-module (i.e. multiple gradle subprojects) architecture unless you actually have a good reason for it. In this case, it really just adds extra complexity for no real benefit. However, multi-platform projects would be a great example of a situation where you'd want to use multi-module.\n\n\n# SpaceNotes\n\nNew to Kotlin? Whether you are a seasoned Java veteran, or you're just starting\nout with Kotlin for Android, consider checking out [Application Programming\nFundamentals w/ Kotlin](https://www.udemy.com/application-programming-fundamentals-with-kotlin/learn/v4/overview). If you like my videos and repositories, I think\nyou'll really enjoy a more polished course experience from wiseAss!\n\n## What is SpaceNotes?\n\nSpaceNotes is a Kotlin based Android Application, which was built with\nbest practices an innovation in mind. The app uses Coroutines for\nconcurrency and cross-module/boundary communication, a Clean Domain\nLayer to allow the application to work properly across multiple platforms,\nand a few of my favourite APIs from Android Architecture Component\nand Firebase.\n\n## Software Architecture\n\n### Feature Specific (Front End Android):\n\n#### NoteList Feature\n<img src=\"notelistfeature.gif\" alt=\"Note List\" width=\"270\" height=\"480\"/>\nThis feature displays whatever Notes are currently available based on the user's status, such as: Anonymous, Registered Private, Registered Public\n\n* INoteListContract specifies the different interactions between classes and the events which may occur in this particular feature\n* NoteListActivity is a feature level “container”, within which these different things are deployed in to (it is also the entry point of the feature)\n* NoteListLogic is the “decision maker” of the feature, which handles the events and interactions specified in the contract (this kind of class is the most important to test)\n* NoteListView contains logic and bindings to the user interface\n* NoteListAdapter contains a decoupled RecyclerView.ListAdapter w/ DiffUtil\n* NoteListViewModel contains the most recent data which has been returned from the “backend” of the application (or data which is passed into the feature via navigation), and persists this data so that the logic class or view does not need to (if they did, it would break the separation of concerns)\n* NoteListInjector: Build logic (Dpependency Injection Implementation) for this feature.\n\n\n#### NoteDetail Feature\n<img src=\"notedetail.png\" alt=\"Note Detail\" width=\"270\" height=\"480\"/>\n\nThis feature allows the User to view, update, create, and delete a Note. Data is stored in various local/remote datasources based on whether the user is or isn't logged in, and if they are in public or private mode.\n\n* INoteDetailContract specifies the different interactions between classes and the events which may occur in this particular feature\n* NoteDetailActivity is a feature level “container”, within which these different things are deployed in to (it is also the entry point of the feature)\n* NoteDetailLogic is the “decision maker” of the feature, which handles the events and interactions specified in the contract (this kind of class is the most important to test)\n* NoteDetailView contains logic and bindings to the user interface\n* NoteDetailViewModel contains the most recent data which has been returned from the “backend” of the application (or data which is passed into the feature via navigation), and persists this data so that the logic class or view does not need to (if they did, it would break the separation of concerns)\n* NoteDetailInjector: Build logic (Dpependency Injection Implementation) for this feature.\n\n\n#### Login Feature\n<img src=\"login.png\" alt=\"Login\" width=\"270\" height=\"480\"/>\n\nThis feature allows the User to authenticate with GoogleSignIn; which\nis currently the only supported sign in function. No passwords or in-app\nSign up is required.\n\n**Note:** I normally advocate against using Activities as Views, but I\nran in to a tight-coupling problem with GoogleSignIn API (which requires you\nto override Activity.onActivityResult(...). Given this tight coupling,\nand the simplicity of this feature (it only has two buttons including the toolbar), I decided to just use the Activity as a pragmatic decision.\n\n* ILoginContract specifies the different interactions between classes and the events which may occur in this particular feature\n* LoginActivity acts as the View and Container in this feature (for reasons mentioned above)\n* LoginLogic is the “decision maker” of the feature, which handles the events and interactions specified in the contract (this kind of class is the most important to test)\n* LoginResult Wrapper for when GoogleSignInProviders does it's thing (logging a User In)\n* LoginInjector: Build logic (Dpependency Injection Implementation) for this feature.\n\n\n#### Common:\n* Navigation.kt: Contains Top-level functions for starting each feature with the appropriate arguments.\n* Constants.kt: Contains messages and keys for front end Android\n* BaseLogic.kt: Abstract class for Logic classes. Could be optimized, currently just contains a DispatcherProvider (for Coroutines) as a property, and a Job object for keeping track and disposing in-flight coroutines.\n* AndroidExt.kt: Some handy Extensions functions for front end Android\n\n### Domain:\nThe Domain Layer of this application has three primary purposes:\n* Abstraction of the Data Layer of the application\n* Providing a common, 3rd party library free set of Models (such as Note.kt) for different platforms of the App\n* Providing a High-Level description of the applications primary functions based on problem domain analysis (such as User Stories)\n\n#### Packages:\n* domainmodel: POKOs (Plain Old Kotlin Objects) to be shared as a common model between different modules\n* error: Sealed Class which contains application specific errors\n* interactor: Interactors exist to coordinate the back end data sources. This is generally only necessary when there is more than one back data source, otherwise it ends up being\nanother unnecessary layer of abstraction over the repository.\n* repository: Repository Interfaces which dictate the contractual obligations of each part of the back end. This allows the domain layer to coordinate the different back end components without needing to know their real implementations/libraries/dependencies.\n* servicelocator: Service Locator promotes Functional Purity of the Interactor's functions, and acts as a method of Dependency Injection (by providing the dependencies as arguments to functions)\n* DispatcherProvider: Used for Coroutines Implementation\n\n### Data (Android Back End):\nThe Data Layer of this application contains implementations of the data sources which are described in the repository package of the domain layer.\n\n#### Auth:\n\nCurrently implemented with FirebaseAuth; manages user authentication.\n\n#### Data Models:\n\nAPI specific data models, which are mapped from/to domain models. Each data model is created for a particular API, such as Firestore and Room.\n\n#### Note:\n\nImplementations for the anonymous, registered private, and registered public data sources which persist Note objects.\n\n#### Transaction:\n\nTransaction is only used for registered private users. It's purpose is to store offline transactions that the user makes to their registered repository, and attempts to push those transactions when the user reconnects to the remote firestore database.\n\n#### DataExt.kt:\n\nContains all of the obnoxious but necessary Data Model Mapping functions, and some Coroutine wrappers over Firebase/GMS Tasks API\n\n## Can I use code from this Repo?\nAbsolutely, pursuant to the project's [LICENSE](LICENSE.md). That being said, the logo and name are my intellectual creations, so don't use them unless you are linking/reffering to this Repo.\n\nFollow the rules in the license, and you're good.\n\n## Architecture Style:\nThis project uses Model-View-Whatever. It is the software architecture that has no particular style, yet accommodates all situations. In a less Zen way of speaking, I don't follow MVP, MVC, or MVVM strictly. I use parts of all styles of architectures based on whatever feature I'm creating, and that is what dictates the ultimate architecture of a given feature.\n\nIf you want me to explain in slightly more familiar terms, I basically apply MVP + VM as a front-end session datastore (such as arguments passed in from Activity Intents, current user states, current note to be displayed). I don't use ViewModels as Decision Maker Classes, hence the Logic class.\n\n## Contact/Support me:\n\nFollow the wiseAss Community:\nhttps://www.instagram.com/wiseassbrand/\nhttps://www.facebook.com/wiseassblog/\nhttps://twitter.com/wiseass301\nhttp://wiseassblog.com/\nhttps://www.linkedin.com/in/ryan-kay-808388114\n\nSupport wiseAss here:\nhttps://www.paypal.me/ryanmkay\n\n## License\n * Copyright 2016, The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n"
  },
  {
    "path": "app/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "app/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\napply plugin: 'kotlin-android'\n\napply plugin: 'kotlin-android-extensions'\n\napply plugin: 'kotlin-kapt'\n\nandroid {\n    compileSdkVersion build_versions.target_sdk\n\n    defaultConfig {\n        applicationId \"com.wiseassblog.spacenotes\"\n        minSdkVersion build_versions.min_sdk\n        targetSdkVersion build_versions.target_sdk\n        versionCode 1\n        versionName \"1.0\"\n        testInstrumentationRunner \"android.support.test.runner.AndroidJUnitRunner\"\n    }\n\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_8\n        targetCompatibility JavaVersion.VERSION_1_8\n    }\n\n    buildTypes {\n        debug {\n            testCoverageEnabled = true\n            minifyEnabled false\n        }\n\n        release {\n            testCoverageEnabled = false\n            minifyEnabled true\n        }\n    }\n\n    testOptions.unitTests.all {\n        useJUnitPlatform()\n\n        testLogging {\n            events 'passed', 'skipped', 'failed', 'standardOut', 'standardError'\n        }\n    }\n\n}\n\ndependencies {\n    implementation project(\":domain\")\n    implementation project(\":data\")\n\n    //Dependencies\n    implementation deps.android.constraint_layout\n    implementation deps.android.lifecycle_extensions\n    implementation deps.android.ktx_fragment\n    implementation deps.android.fragment\n    implementation deps.android.appcompat\n    implementation deps.android.recyclerview\n    implementation deps.android.design\n    implementation deps.room.runtime\n\n    implementation deps.firebase.auth\n    implementation deps.firebase.firestore\n    implementation deps.play_services.auth\n\n    implementation deps.kotlin.kotlin_jre\n    implementation deps.kotlin.coroutines_core\n    implementation deps.kotlin.coroutines_android\n\n\n    kapt deps.room.compiler\n\n    testImplementation deps.test.junit\n    testRuntimeOnly deps.test.jupiter_engine\n    testRuntimeOnly deps.test.vintage_engine\n    testImplementation deps.test.mockk\n    testImplementation deps.test.kotlin_junit\n\n}\n\napply plugin: 'com.google.gms.google-services'\n"
  },
  {
    "path": "app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.wiseassblog.spacenotes\">\n\n    <application\n        android:name=\".SpaceNotes\"\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:roundIcon=\"@mipmap/ic_launcher_round\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\">\n        <activity android:name=\".notedetail.NoteDetailActivity\"></activity>\n        <activity android:name=\".notelist.NoteListActivity\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n        <activity android:name=\".login.LoginActivity\"></activity>\n    </application>\n\n</manifest>"
  },
  {
    "path": "app/src/main/java/com/wiseassblog/spacenotes/SpaceNotes.kt",
    "content": "package com.wiseassblog.spacenotes\n\nimport android.app.Application\n\n\nclass SpaceNotes: Application() {\n\n    override fun onCreate() {\n        super.onCreate()\n\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/wiseassblog/spacenotes/common/AndroidExt.kt",
    "content": "package com.wiseassblog.spacenotes.common\n\nimport android.app.Activity\nimport android.content.Intent\n\nimport android.text.Editable\nimport android.widget.Toast\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.FragmentManager\nimport com.wiseassblog.spacenotes.notedetail.NoteDetailActivity\nimport com.wiseassblog.spacenotes.notedetail.NoteDetailView\nimport com.wiseassblog.spacenotes.notelist.NoteListActivity\nimport java.text.SimpleDateFormat\nimport java.util.*\n\ninternal fun String.toEditable(): Editable = Editable.Factory.getInstance().newEditable(this)\n\n\ninternal fun Activity.attachFragment(manager: FragmentManager, containerId: Int, view: Fragment, tag: String) {\n    manager.beginTransaction()\n            .replace(containerId, view, tag)\n            .commitNowAllowingStateLoss()\n}\n\ninternal fun Fragment.getCalendarTime(): String {\n    val cal = Calendar.getInstance(TimeZone.getDefault())\n    val format = SimpleDateFormat(\"d MMM yyyy HH:mm:ss Z\")\n    format.timeZone = cal.timeZone\n    return format.format(cal.time)\n}\n\ninternal fun Fragment.makeToast(value: String) {\n    Toast.makeText(activity, value, Toast.LENGTH_SHORT).show()\n}\n\ninternal fun Fragment.restartCurrentFeature() {\n    val i: Intent\n    when (this) {\n        is NoteDetailView -> {\n            i = Intent(this.activity, NoteDetailActivity::class.java)\n        }\n\n        //To Be Added\n\n        else -> {\n            i = Intent(this.activity, NoteListActivity::class.java)\n        }\n    }\n\n    this.activity?.finish()\n    startActivity(i)\n}"
  },
  {
    "path": "app/src/main/java/com/wiseassblog/spacenotes/common/BaseLogic.kt",
    "content": "package com.wiseassblog.spacenotes.common\n\nimport com.wiseassblog.domain.DispatcherProvider\nimport kotlinx.coroutines.Job\n\n/**\n * Why use a base class? To both share implementation (properties and functions), and enforce a contract (interface) for all listener classes\n */\nabstract class BaseLogic(val dispatcher: DispatcherProvider) {\n\n    protected lateinit var jobTracker: Job\n}"
  },
  {
    "path": "app/src/main/java/com/wiseassblog/spacenotes/common/Constants.kt",
    "content": "package com.wiseassblog.spacenotes.common\n\ninternal const val MESSAGE_DELETE_SUCCESSFUL = \"Note successfully deleted.\"\ninternal const val MESSAGE_DELETE = \"DELETE\"\ninternal const val MESSAGE_DELETE_CONFIRMATION = \"Delete note permanently?\"\ninternal const val MESSAGE_GENERIC_ERROR = \"An error has occured.\"\ninternal const val MESSAGE_LOGIN = \"Log in to use public mode.\"\ninternal const val BOOLEAN_EXTRA_IS_PRIVATE = \"BOOLEAN_EXTRA_IS_PRIVATE\"\ninternal const val STRING_EXTRA_NOTE_ID = \"STRING_EXTRA_NOTE_ID\"\ninternal const val MODE_PRIVATE = \"Private Notes\"\ninternal const val MODE_PUBLIC = \"Public Notes\"\n"
  },
  {
    "path": "app/src/main/java/com/wiseassblog/spacenotes/common/Navigation.kt",
    "content": "package com.wiseassblog.spacenotes.common\n\nimport android.app.Activity\nimport android.content.Intent\nimport com.wiseassblog.spacenotes.login.LoginActivity\nimport com.wiseassblog.spacenotes.notedetail.NoteDetailActivity\nimport com.wiseassblog.spacenotes.notelist.NoteListActivity\n\ninternal fun startListFeature(activity: Activity?) {\n    activity?.startActivity(\n            Intent(\n                    activity,\n                    NoteListActivity::class.java\n            )\n    ).also { activity?.finish() }\n}\n\ninternal fun startNoteDetailFeatureWithExtras(activity: Activity?, noteId: String, isPrivate: Boolean) {\n    val i = Intent(activity, NoteDetailActivity::class.java)\n    i.putExtra(STRING_EXTRA_NOTE_ID, noteId)\n    i.putExtra(BOOLEAN_EXTRA_IS_PRIVATE, isPrivate)\n    activity?.startActivity(i)\n}\n\ninternal fun startLoginFeature(activity: Activity?) {\n    val i = Intent(activity, LoginActivity::class.java)\n    activity?.startActivity(i)\n            .also { activity?.finish() }\n}\n\n"
  },
  {
    "path": "app/src/main/java/com/wiseassblog/spacenotes/login/ILoginContract.kt",
    "content": "package com.wiseassblog.spacenotes.login\n\nimport androidx.lifecycle.Observer\n\ninterface ILoginContract {\n\n    interface View {\n        fun setLoginStatus(text: String)\n        fun setAuthButton(text: String)\n        fun showLoopAnimation()\n        fun setStatusDrawable(imageURL: String)\n        fun startSignInFlow()\n        fun setObserver(observer: Observer<LoginEvent<LoginResult>>)\n        fun startListFeature()\n    }\n\n    interface Logic {\n        fun event(event: LoginEvent<LoginResult>)\n    }\n}\n\ninternal const val SIGN_OUT = \"SIGN OUT\"\ninternal const val SIGN_IN = \"SIGN IN\"\ninternal const val SIGNED_IN = \"Signed In\"\ninternal const val SIGNED_OUT = \"Signed Out\"\ninternal const val ERROR_NETWORK_UNAVAILABLE = \"Network Unavailable\"\ninternal const val ERROR_AUTH = \"An Error Has Occured\"\ninternal const val RETRY = \"RETRY\"\ninternal const val ANTENNA_EMPTY = \"antenna_empty\"\ninternal const val ANTENNA_FULL = \"antenna_full\"\n\n/**\n * This value is just a constant to denote our sign in request; It can be any int.\n * Would have been great if that was explained in the docs, I assumed at first that it had to\n * be a specific value.\n */\ninternal const val RC_SIGN_IN = 1337\n\nsealed class LoginEvent<out T> {\n    object OnAuthButtonClick : LoginEvent<Nothing>()\n    object OnBackClick : LoginEvent<Nothing>()\n    object OnStart : LoginEvent<Nothing>()\n    data class OnGoogleSignInResult<out LoginResult>(val result: LoginResult) : LoginEvent<LoginResult>()\n    object OnDestroy : LoginEvent<Nothing>()\n}"
  },
  {
    "path": "app/src/main/java/com/wiseassblog/spacenotes/login/LoginActivity.kt",
    "content": "package com.wiseassblog.spacenotes.login\n\nimport android.content.Intent\nimport android.graphics.drawable.AnimationDrawable\nimport android.os.Bundle\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.lifecycle.MutableLiveData\nimport androidx.lifecycle.Observer\nimport androidx.lifecycle.ViewModelProviders\nimport com.google.android.gms.auth.api.signin.GoogleSignIn\nimport com.google.android.gms.auth.api.signin.GoogleSignInAccount\nimport com.google.android.gms.auth.api.signin.GoogleSignInOptions\nimport com.google.android.gms.common.api.ApiException\nimport com.wiseassblog.spacenotes.R\nimport com.wiseassblog.spacenotes.login.buildlogic.LoginInjector\nimport kotlinx.android.synthetic.main.activity_login.*\n\n\n/**\n * Q: Why did I decide to use an Activity as the View in this feature?\n * A: Since I want to be able to use the GoogleSignIn API, there is necessary tight coupling\n * with Activity in this feature. Further, since this feature is quite simple to begin with,\n * I didn't mind breaking SoC a little bit in exchange for the GoogleSignIn functionality.\n *\n */\nclass LoginActivity : AppCompatActivity(), ILoginContract.View {\n    override fun setObserver(observer: Observer<LoginEvent<LoginResult>>) = event.observeForever(observer)\n\n    override fun startListFeature() = com.wiseassblog.spacenotes.common.startListFeature(this)\n\n    val event = MutableLiveData<LoginEvent<LoginResult>>()\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_login)\n\n        //Note: I cal setObserver within the LoginInjector function\n        ViewModelProviders.of(this)\n                .get(LoginInjector::class.java)\n                .buildLoginLogic(this)\n\n        btn_auth_attempt.setOnClickListener { event.value = LoginEvent.OnAuthButtonClick }\n        imb_toolbar_back.setOnClickListener { event.value = LoginEvent.OnBackClick }\n    }\n\n\n    override fun onResume() {\n        super.onResume()\n        event.value = LoginEvent.OnStart\n    }\n\n    override fun setLoginStatus(text: String) {\n        lbl_login_status_display.text = text\n    }\n\n    override fun setAuthButton(text: String) {\n        btn_auth_attempt.text = text\n    }\n\n    override fun showLoopAnimation() {\n        imv_antenna_animation.setImageResource(\n                resources.getIdentifier(\"antenna_loop_fast\", \"drawable\", this.packageName)\n        )\n\n        val satelliteLoop = imv_antenna_animation.drawable as AnimationDrawable\n        satelliteLoop.start()\n    }\n\n    override fun setStatusDrawable(imageURL: String) {\n        imv_antenna_animation.setImageResource(\n                resources.getIdentifier(imageURL, \"drawable\", this.packageName)\n        )\n\n    }\n\n    override fun startSignInFlow() {\n        val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)\n                .requestIdToken(getString(R.string.default_web_client_id))\n                .build()\n\n        val googleSignInClient = GoogleSignIn.getClient(this, gso)\n\n        val signInIntent = googleSignInClient.signInIntent\n        startActivityForResult(signInIntent, RC_SIGN_IN)\n    }\n\n    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {\n        super.onActivityResult(requestCode, resultCode, data)\n\n        if (requestCode == RC_SIGN_IN) {\n\n            val task = GoogleSignIn.getSignedInAccountFromIntent(data)\n\n            try {\n                val account: GoogleSignInAccount? = task.getResult(ApiException::class.java)\n\n                event.value = LoginEvent.OnGoogleSignInResult(\n                        LoginResult(\n                                requestCode,\n                                account\n                        )\n                )\n\n            } catch (exception: Exception) {\n                event.value = LoginEvent.OnGoogleSignInResult(\n                        LoginResult(\n                                0,\n                                null\n                        )\n                )\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/wiseassblog/spacenotes/login/LoginLogic.kt",
    "content": "package com.wiseassblog.spacenotes.login\n\nimport androidx.lifecycle.Observer\nimport com.wiseassblog.domain.DispatcherProvider\nimport com.wiseassblog.domain.servicelocator.UserServiceLocator\nimport com.wiseassblog.domain.domainmodel.Result\nimport com.wiseassblog.domain.error.SpaceNotesError\nimport com.wiseassblog.domain.interactor.AuthSource\nimport com.wiseassblog.spacenotes.common.BaseLogic\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.launch\nimport kotlin.coroutines.CoroutineContext\n\nclass LoginLogic(dispatcher: DispatcherProvider,\n                 val userLocator: UserServiceLocator,\n                 val view: ILoginContract.View,\n                 val authSource: AuthSource) : BaseLogic(dispatcher), CoroutineScope, Observer<LoginEvent<LoginResult>> {\n\n\n    init {\n        jobTracker = Job()\n    }\n\n    override val coroutineContext: CoroutineContext\n        get() = dispatcher.provideUIContext() + jobTracker\n\n\n    override fun onChanged(event: LoginEvent<LoginResult>) {\n        when (event) {\n            is LoginEvent.OnStart -> onStart()\n            is LoginEvent.OnDestroy -> jobTracker.cancel()\n            is LoginEvent.OnBackClick -> onBackClick()\n            is LoginEvent.OnAuthButtonClick -> onAuthButtonClick()\n            is LoginEvent.OnGoogleSignInResult -> onSignInResult(event.result)\n        }\n    }\n\n    private fun onSignInResult(result: LoginResult) = launch {\n        if (result.requestCode == RC_SIGN_IN && result.account != null) {\n            view.showLoopAnimation()\n\n            val createGoogleUserResult = authSource.createGoogleUser(\n                    result.account.idToken!!,\n                    userLocator\n            )\n\n            when (createGoogleUserResult) {\n                is Result.Value -> onStart()\n                is Result.Error -> handleError(createGoogleUserResult.error)\n            }\n        } else {\n            renderErrorState(ERROR_AUTH)\n        }\n    }\n\n    private fun onAuthButtonClick() = launch {\n        view.showLoopAnimation()\n\n        val authResult = authSource.getCurrentUser(userLocator)\n\n        when (authResult) {\n            is Result.Value -> {\n                if (authResult.value == null) view.startSignInFlow()\n                else signUserOut()\n            }\n\n            is Result.Error -> handleError(authResult.error)\n        }\n\n    }\n\n    private fun handleError(error: Exception) {\n        when (error) {\n            is SpaceNotesError.NetworkUnavailableException -> renderErrorState(\n                    ERROR_NETWORK_UNAVAILABLE\n            )\n\n            else -> renderErrorState(ERROR_AUTH)\n        }\n    }\n\n    private suspend fun signUserOut() {\n        val signOutResult = authSource.signOutCurrentUser(userLocator)\n\n        when (signOutResult) {\n            is Result.Value -> renderNullUser()\n            is Result.Error -> renderErrorState(ERROR_AUTH)\n        }\n\n    }\n\n    private fun onBackClick() {\n        view.startListFeature()\n    }\n\n    private fun onStart() = launch {\n        view.showLoopAnimation()\n\n        val authResult = authSource.getCurrentUser(userLocator)\n\n        when (authResult) {\n            is Result.Value -> {\n                if (authResult.value == null) renderNullUser()\n                else renderActiveUser()\n            }\n\n            is Result.Error -> handleError(authResult.error)\n        }\n    }\n\n    private fun renderActiveUser() {\n        view.setStatusDrawable(ANTENNA_FULL)\n        view.setAuthButton(SIGN_OUT)\n        view.setLoginStatus(SIGNED_IN)\n    }\n\n    private fun renderNullUser() {\n        view.setStatusDrawable(ANTENNA_EMPTY)\n        view.setAuthButton(SIGN_IN)\n        view.setLoginStatus(SIGNED_OUT)\n    }\n\n    private fun renderErrorState(message: String) {\n        view.setStatusDrawable(ANTENNA_EMPTY)\n        view.setAuthButton(RETRY)\n        view.setLoginStatus(message)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/wiseassblog/spacenotes/login/LoginResult.kt",
    "content": "package com.wiseassblog.spacenotes.login\n\nimport com.google.android.gms.auth.api.signin.GoogleSignInAccount\nimport com.google.android.gms.tasks.Task\n\n/**\n * Wrapper class for data recieved in LoginActivity's onActivityResult()\n * function\n */\ndata class LoginResult(val requestCode: Int, val account: GoogleSignInAccount?)"
  },
  {
    "path": "app/src/main/java/com/wiseassblog/spacenotes/login/buildlogic/LoginInjector.kt",
    "content": "package com.wiseassblog.spacenotes.login.buildlogic\n\nimport android.app.Application\nimport androidx.lifecycle.AndroidViewModel\nimport com.google.firebase.FirebaseApp\nimport com.wiseassblog.data.auth.FirebaseAuthRepositoryImpl\nimport com.wiseassblog.domain.DispatcherProvider\nimport com.wiseassblog.domain.servicelocator.UserServiceLocator\nimport com.wiseassblog.domain.interactor.AuthSource\nimport com.wiseassblog.domain.repository.IAuthRepository\nimport com.wiseassblog.spacenotes.login.LoginActivity\nimport com.wiseassblog.spacenotes.login.LoginLogic\n\nclass LoginInjector(application: Application) : AndroidViewModel(application) {\n    init {\n        FirebaseApp.initializeApp(application)\n    }\n\n    //For user management\n    private val auth: IAuthRepository by lazy {\n        //by using lazy, I don't load this resource until I need it\n        FirebaseAuthRepositoryImpl()\n    }\n\n\n    fun buildLoginLogic(view: LoginActivity): LoginLogic = LoginLogic(\n            DispatcherProvider,\n            UserServiceLocator(auth),\n            view,\n            AuthSource()\n    ).also { view.setObserver(it) }\n}"
  },
  {
    "path": "app/src/main/java/com/wiseassblog/spacenotes/notedetail/INoteDetailContract.kt",
    "content": "package com.wiseassblog.spacenotes.notedetail\n\nimport androidx.lifecycle.Observer\nimport com.wiseassblog.domain.domainmodel.Note\n\n\n/**\n * Created by R_KAY on 10/8/2017.\n */\ninterface INoteDetailContract {\n\n    interface View {\n        fun setBackgroundImage(imageUrl: String)\n        fun setDateLabel(date: String)\n        fun setNoteBody(content: String)\n        fun setObserver(observer: Observer<NoteDetailEvent>)\n        fun hideBackButton()\n        fun getNoteBody(): String\n        fun getTime(): String\n        fun restartFeature()\n        fun showMessage(message: String)\n        fun showConfirmDeleteSnackbar()\n        fun startListFeature()\n    }\n\n    interface ViewModel {\n        fun setIsPrivateMode(isPrivateMode: Boolean)\n\n        fun getIsPrivateMode(): Boolean\n\n        fun setNoteState(note: Note)\n\n        fun getNoteState(): Note?\n\n        fun setId(id: String)\n\n        fun getId(): String?\n    }\n\n}\n\nsealed class NoteDetailEvent {\n    object OnDoneClick : NoteDetailEvent()\n    object OnDeleteClick : NoteDetailEvent()\n    object OnDeleteConfirmed : NoteDetailEvent()\n    object OnBackClick : NoteDetailEvent()\n    object OnStart : NoteDetailEvent()\n    object OnBind : NoteDetailEvent()\n    object OnDestroy : NoteDetailEvent()\n}\n\n"
  },
  {
    "path": "app/src/main/java/com/wiseassblog/spacenotes/notedetail/NoteDetailActivity.kt",
    "content": "package com.wiseassblog.spacenotes.notedetail\n\nimport android.content.Intent\nimport android.os.Bundle\nimport android.widget.Toast\nimport androidx.appcompat.app.AppCompatActivity\nimport com.wiseassblog.spacenotes.R\nimport com.wiseassblog.spacenotes.common.BOOLEAN_EXTRA_IS_PRIVATE\nimport com.wiseassblog.spacenotes.common.STRING_EXTRA_NOTE_ID\nimport com.wiseassblog.spacenotes.common.attachFragment\nimport com.wiseassblog.spacenotes.notedetail.buildlogic.NoteDetailInjector\nimport com.wiseassblog.spacenotes.notelist.NoteListActivity\n\nprivate const val VIEW = \"NOTE_DETAIL\"\n\n\nclass NoteDetailActivity : AppCompatActivity() {\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_note_detail)\n\n        //Elvis Operator val i:Intent = if intent is null,\n        // assign i to Intent(this, NoteListActivity::class.java)\n        val i: Intent = intent ?: Intent(this, NoteListActivity::class.java)\n\n        //if intent is null, then it's time to gtfo\n        if (intent == null) {\n            Toast.makeText(this, \"Application Restarted.\", Toast.LENGTH_SHORT).show()\n            startActivity(i)\n        }\n\n        val noteId = i.getStringExtra(STRING_EXTRA_NOTE_ID)\n        val isPrivate = i.getBooleanExtra(BOOLEAN_EXTRA_IS_PRIVATE, true)\n\n        val view = this.supportFragmentManager.findFragmentByTag(VIEW) as NoteDetailView?\n                ?: NoteDetailView.newInstance()\n\n        attachFragment(supportFragmentManager, R.id.root_activity_detail, view, VIEW)\n\n        NoteDetailInjector(application)\n                .buildNoteDetailLogic(view as NoteDetailView, noteId, isPrivate)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/wiseassblog/spacenotes/notedetail/NoteDetailLogic.kt",
    "content": "package com.wiseassblog.spacenotes.notedetail\n\nimport androidx.lifecycle.Observer\nimport com.wiseassblog.domain.DispatcherProvider\nimport com.wiseassblog.domain.servicelocator.NoteServiceLocator\nimport com.wiseassblog.domain.servicelocator.UserServiceLocator\nimport com.wiseassblog.domain.domainmodel.Note\nimport com.wiseassblog.domain.domainmodel.Result\nimport com.wiseassblog.domain.domainmodel.User\nimport com.wiseassblog.domain.interactor.AnonymousNoteSource\nimport com.wiseassblog.domain.interactor.AuthSource\nimport com.wiseassblog.domain.interactor.PublicNoteSource\nimport com.wiseassblog.domain.interactor.RegisteredNoteSource\nimport com.wiseassblog.spacenotes.common.BaseLogic\nimport com.wiseassblog.spacenotes.common.MESSAGE_DELETE_SUCCESSFUL\nimport com.wiseassblog.spacenotes.common.MESSAGE_GENERIC_ERROR\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.launch\nimport kotlin.coroutines.CoroutineContext\n\n\nclass NoteDetailLogic(dispatcher: DispatcherProvider,\n                      val noteLocator: NoteServiceLocator,\n                      val userLocator: UserServiceLocator,\n                      val vModel: INoteDetailContract.ViewModel,\n                      val view: INoteDetailContract.View,\n                      val anonymousNoteSource: AnonymousNoteSource,\n                      val registeredNoteSource: RegisteredNoteSource,\n                      val publicNoteSource: PublicNoteSource,\n                      val authSource: AuthSource,\n                      id: String,\n                      isPrivate: Boolean)\n    : BaseLogic(dispatcher), CoroutineScope, Observer<NoteDetailEvent> {\n\n    init {\n        vModel.setId(id)\n        vModel.setIsPrivateMode(isPrivate)\n        jobTracker = Job()\n    }\n\n    fun clear() {\n        jobTracker.cancel()\n    }\n\n    override val coroutineContext: CoroutineContext\n        get() = dispatcher.provideUIContext() + jobTracker\n\n    override fun onChanged(event: NoteDetailEvent) {\n        when (event) {\n            is NoteDetailEvent.OnDoneClick -> onDoneClick()\n            is NoteDetailEvent.OnDeleteClick -> onDeleteClick()\n            is NoteDetailEvent.OnBackClick -> onBackClick()\n            is NoteDetailEvent.OnDeleteConfirmed -> onDeleteConfirmed()\n            is NoteDetailEvent.OnStart -> onStart()\n            is NoteDetailEvent.OnBind -> bind()\n            is NoteDetailEvent.OnDestroy -> clear()\n        }\n    }\n\n    fun onDoneClick() = launch {\n\n        val userResult = authSource.getCurrentUser(userLocator)\n\n        when (userResult) {\n            is Result.Value -> {\n                //if null, user is anonymous\n                if (userResult.value == null) prepareAnonymousRepoUpdate()\n                else if (vModel.getIsPrivateMode()) prepareRegisteredRepoUpdate()\n                else preparePublicRepoUpdate()\n            }\n        }\n\n    }\n\n    private suspend fun prepareAnonymousRepoUpdate() {\n        val updatedNote = vModel.getNoteState()!!.copy(contents = view.getNoteBody())\n\n        val result = anonymousNoteSource.updateNote(updatedNote, noteLocator)\n\n        when (result) {\n            is Result.Value -> view.startListFeature()\n            is Result.Error -> view.showMessage(result.error.toString())\n        }\n    }\n\n    suspend fun prepareRegisteredRepoUpdate() {\n\n        val updatedNote = vModel.getNoteState()!!.copy(contents = view.getNoteBody())\n\n        val result = registeredNoteSource.updateNote(updatedNote, noteLocator)\n\n        when (result) {\n            is Result.Value -> view.startListFeature()\n            is Result.Error -> view.showMessage(result.error.toString())\n        }\n    }\n\n    suspend fun preparePublicRepoUpdate() {\n\n        val updatedNote = vModel.getNoteState()!!\n                .copy(contents = view.getNoteBody())\n\n        val result = publicNoteSource.updateNote(updatedNote, noteLocator)\n\n        when (result) {\n            is Result.Value -> view.startListFeature()\n            is Result.Error -> view.showMessage(result.error.toString())\n        }\n    }\n\n    fun bind() = launch {\n\n        val userResult = authSource.getCurrentUser(userLocator)\n\n        when (userResult) {\n            is Result.Value -> {\n                val id = vModel.getId()\n                if (id == \"\" || id == null) createNewNote(userResult.value)\n                else getNoteFromSource(id, userResult.value)\n            }\n\n            is Result.Error -> view.showMessage(userResult.error.toString())\n        }\n    }\n\n    fun createNewNote(user: User?) {\n\n        vModel.setNoteState(\n                Note(\n                        view.getTime(),\n                        \"\",\n                        0,\n                        \"satellite_beam\",\n                        user\n                )\n        )\n\n        //only save or delete with new note\n        view.hideBackButton()\n\n        onStart()\n    }\n\n    fun getNoteFromSource(id: String, user: User?) = launch {\n        val noteResult: Result<Exception, Note?>\n\n        //private anonymous\n        if (user == null) noteResult = anonymousNoteSource.getNoteById(id, noteLocator)\n        //private registered\n        else if (vModel.getIsPrivateMode()) noteResult = registeredNoteSource.getNoteById(id, noteLocator)\n        //public registered\n        else noteResult = publicNoteSource.getNoteById(id, noteLocator)\n\n        when (noteResult) {\n            is Result.Value -> {\n                vModel.setNoteState(noteResult.value!!)\n                onStart()\n            }\n\n            is Result.Error -> {\n                val message = noteResult.error.message ?: \"An error has occured.\"\n                view.showMessage(message)\n            }\n        }\n    }\n\n    fun onStart() {\n        val state = vModel.getNoteState()\n\n        //LiveData requires null checks due to nullable return types\n        if (state != null) {\n            renderView(state)\n        } else {\n            view.showMessage(MESSAGE_GENERIC_ERROR)\n            view.startListFeature()\n        }\n    }\n\n    private fun renderView(state: Note) {\n        view.setBackgroundImage(state.imageUrl)\n        view.setDateLabel(state.creationDate)\n        view.setNoteBody(state.contents)\n    }\n\n    fun onBackClick() {\n        view.startListFeature()\n    }\n\n    fun onDeleteClick() {\n        view.showConfirmDeleteSnackbar()\n    }\n\n    fun onDeleteConfirmed() = launch {\n\n        val currentNote = vModel.getNoteState()\n\n        //if VM data is null, we're in a bad spot\n        if (currentNote == null) {\n            view.showMessage(MESSAGE_GENERIC_ERROR)\n            view.restartFeature()\n        } else {\n            val userResult = authSource.getCurrentUser(userLocator)\n\n            when (userResult) {\n                is Result.Value -> {\n                    if (userResult.value == null) prepareAnonymousRepoDelete(currentNote)\n                    else if (vModel.getIsPrivateMode()) prepareRegisteredRepoDelete(currentNote)\n                    else preparePublicRepoDelete(currentNote)\n                }\n\n                is Result.Error -> view.showMessage(userResult.error.toString())\n            }\n        }\n    }\n\n    private fun preparePublicRepoDelete(note: Note) = launch {\n        val result = publicNoteSource.deleteNote(note, noteLocator)\n\n        when (result) {\n            is Result.Value -> {\n                view.showMessage(MESSAGE_DELETE_SUCCESSFUL)\n                view.startListFeature()\n            }\n            is Result.Error -> view.showMessage(result.error.toString())\n        }\n    }\n\n    private fun prepareRegisteredRepoDelete(note: Note) = launch {\n        val result = registeredNoteSource.deleteNote(note, noteLocator)\n\n        when (result) {\n            is Result.Value -> {\n                view.showMessage(MESSAGE_DELETE_SUCCESSFUL)\n                view.startListFeature()\n            }\n            is Result.Error -> view.showMessage(result.error.toString())\n        }\n    }\n\n    private fun prepareAnonymousRepoDelete(note: Note) = launch {\n        val result = anonymousNoteSource.deleteNote(note, noteLocator)\n\n        when (result) {\n            is Result.Value -> {\n                view.showMessage(MESSAGE_DELETE_SUCCESSFUL)\n                view.startListFeature()\n            }\n            is Result.Error -> view.showMessage(result.error.toString())\n        }\n    }\n\n\n}"
  },
  {
    "path": "app/src/main/java/com/wiseassblog/spacenotes/notedetail/NoteDetailView.kt",
    "content": "package com.wiseassblog.spacenotes.notedetail\n\n\nimport android.graphics.drawable.AnimationDrawable\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.fragment.app.Fragment\nimport androidx.lifecycle.MutableLiveData\nimport androidx.lifecycle.Observer\nimport com.google.android.material.snackbar.Snackbar\nimport com.wiseassblog.spacenotes.R\nimport com.wiseassblog.spacenotes.R.id.*\nimport com.wiseassblog.spacenotes.common.*\nimport com.wiseassblog.spacenotes.notedetail.buildlogic.NoteDetailInjector\nimport com.wiseassblog.spacenotes.notelist.NoteListEvent\nimport kotlinx.android.synthetic.main.fragment_note_detail.*\n\n\nclass NoteDetailView : Fragment(), INoteDetailContract.View {\n\n    val event = MutableLiveData<NoteDetailEvent>()\n\n    override fun setObserver(observer: Observer<NoteDetailEvent>) = event.observeForever(observer)\n\n    override fun startListFeature() = com.wiseassblog.spacenotes.common.startListFeature(this.activity)\n\n    override fun hideBackButton() {\n        imb_toolbar_back.visibility = View.INVISIBLE\n        imb_toolbar_back.isEnabled = false\n    }\n\n    override fun getTime(): String = getCalendarTime()\n\n    override fun showConfirmDeleteSnackbar() {\n        if (activity != null) {\n            Snackbar.make(frag_note_detail, MESSAGE_DELETE_CONFIRMATION, Snackbar.LENGTH_LONG)\n                    .setAction(MESSAGE_DELETE) { event.value = NoteDetailEvent.OnDeleteConfirmed }\n                    .show()\n        }\n    }\n\n    override fun showMessage(message: String) = makeToast(message)\n\n\n    override fun restartFeature() = restartCurrentFeature()\n\n    override fun getNoteBody(): String {\n        return edt_note_detail_text.text.toString()\n    }\n\n    override fun setBackgroundImage(imageUrl: String) {\n        imv_note_detail_satellite.setImageResource(\n                resources.getIdentifier(imageUrl, \"drawable\", context?.packageName)\n        )\n\n        val satelliteLoop = imv_note_detail_satellite.drawable as AnimationDrawable\n        satelliteLoop.start()\n    }\n\n\n    override fun setDateLabel(date: String) {\n        lbl_note_detail_date.text = date\n    }\n\n    override fun setNoteBody(content: String) {\n        edt_note_detail_text.text = content.toEditable()\n    }\n\n    override fun onStart() {\n        super.onStart()\n        event.value = NoteDetailEvent.OnBind\n    }\n\n    override fun onDestroy() {\n        event.value = NoteDetailEvent.OnDestroy\n        super.onDestroy()\n    }\n\n\n    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,\n                              savedInstanceState: Bundle?): View? {\n        // Inflate the layout for this fragment\n        return inflater.inflate(R.layout.fragment_note_detail, container, false)\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n\n        imb_toolbar_done.setOnClickListener { event.value = NoteDetailEvent.OnDoneClick }\n        imb_toolbar_back.setOnClickListener { event.value = NoteDetailEvent.OnBackClick }\n        imb_toolbar_delete.setOnClickListener { event.value = NoteDetailEvent.OnDeleteClick }\n\n        val spaceLoop = frag_note_detail.background as AnimationDrawable\n        spaceLoop.setEnterFadeDuration(1000)\n        spaceLoop.setExitFadeDuration(1000)\n        spaceLoop.start()\n\n        super.onViewCreated(view, savedInstanceState)\n    }\n\n\n    companion object {\n        @JvmStatic\n        fun newInstance() =\n                NoteDetailView()\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/wiseassblog/spacenotes/notedetail/NoteDetailViewModel.kt",
    "content": "package com.wiseassblog.spacenotes.notedetail\n\nimport androidx.lifecycle.LiveData\nimport androidx.lifecycle.MutableLiveData\nimport androidx.lifecycle.Observer\nimport androidx.lifecycle.ViewModel\nimport com.wiseassblog.domain.domainmodel.Note\nimport com.wiseassblog.spacenotes.notedetail.INoteDetailContract\n\nclass NoteDetailViewModel(private var displayState: MutableLiveData<Note> = MutableLiveData(),\n                          private var id: MutableLiveData<String> = MutableLiveData(),\n                          private var isPrivateMode: MutableLiveData<Boolean> = MutableLiveData()) : ViewModel(),\n        INoteDetailContract.ViewModel {\n\n    override fun getIsPrivateMode(): Boolean {\n        return isPrivateMode.value!!\n    }\n\n    override fun setIsPrivateMode(isPrivateMode: Boolean) {\n        this.isPrivateMode.value = isPrivateMode\n    }\n\n    override fun setId(id: String) {\n        this.id.value = id\n    }\n\n    override fun getId(): String? {\n        return this.id.value\n    }\n\n    override fun getNoteState(): Note? {\n        return displayState.value\n    }\n\n    override fun setNoteState(note: Note) {\n        displayState.value = note\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/java/com/wiseassblog/spacenotes/notedetail/buildlogic/NoteDetailInjector.kt",
    "content": "package com.wiseassblog.spacenotes.notedetail.buildlogic\n\nimport android.app.Application\nimport androidx.lifecycle.AndroidViewModel\nimport androidx.lifecycle.ViewModelProviders\nimport com.google.firebase.FirebaseApp\nimport com.wiseassblog.data.auth.FirebaseAuthRepositoryImpl\nimport com.wiseassblog.data.note.anonymous.AnonymousNoteDao\nimport com.wiseassblog.data.note.anonymous.AnonymousNoteDatabase\nimport com.wiseassblog.data.note.anonymous.RoomLocalAnonymousRepositoryImpl\nimport com.wiseassblog.data.note.public.FirestoreRemoteNoteImpl\nimport com.wiseassblog.data.note.registered.*\nimport com.wiseassblog.data.transaction.RoomRegisteredTransactionDatabase\nimport com.wiseassblog.data.transaction.RoomTransactionRepositoryImpl\nimport com.wiseassblog.domain.DispatcherProvider\nimport com.wiseassblog.domain.servicelocator.NoteServiceLocator\nimport com.wiseassblog.domain.servicelocator.UserServiceLocator\nimport com.wiseassblog.domain.interactor.AnonymousNoteSource\nimport com.wiseassblog.domain.interactor.AuthSource\nimport com.wiseassblog.domain.interactor.PublicNoteSource\nimport com.wiseassblog.domain.interactor.RegisteredNoteSource\nimport com.wiseassblog.domain.repository.*\nimport com.wiseassblog.spacenotes.notedetail.*\n\n/**\n *\n */\nclass NoteDetailInjector(application: Application) : AndroidViewModel(application) {\n    init {\n        FirebaseApp.initializeApp(application)\n    }\n\n    private val anonNoteDao: AnonymousNoteDao by lazy {\n        AnonymousNoteDatabase.getInstance(getApplication()).roomNoteDao()\n    }\n\n    private val regNoteDao: RegisteredNoteDao by lazy {\n        RegisteredNoteDatabase.getInstance(getApplication()).roomNoteDao()\n    }\n\n    private val transactionDao: RegisteredTransactionDao by lazy {\n        RoomRegisteredTransactionDatabase.getInstance(getApplication()).roomTransactionDao()\n    }\n\n    //For non-registered user persistence\n    private val localAnon: ILocalNoteRepository by lazy {\n        RoomLocalAnonymousRepositoryImpl(anonNoteDao)\n    }\n\n    //For registered user remote persistence (Source of Truth)\n    private val remotePrivate: IRemoteNoteRepository by lazy {\n        FirestorePrivateRemoteNoteImpl()\n    }\n\n    //For registered user remote persistence (Source of Truth)\n    private val remotePublic: IPublicNoteRepository by lazy {\n        FirestoreRemoteNoteImpl\n    }\n\n    //For registered user local persistience (cache)\n    private val cacheReg: ILocalNoteRepository by lazy {\n        RoomLocalCacheImpl(regNoteDao)\n    }\n\n    //For registered user remote persistence (Source of Truth)\n    private val remoteRepo: IRemoteNoteRepository by lazy {\n        RegisteredNoteRepositoryImpl(remotePrivate, cacheReg)\n    }\n\n\n    //For registered user local persistience (cache)\n    private val transactionReg: ITransactionRepository by lazy {\n        RoomTransactionRepositoryImpl(transactionDao)\n    }\n\n    //For user management\n    private val auth: IAuthRepository by lazy {\n        FirebaseAuthRepositoryImpl()\n    }\n\n    private lateinit var logic: NoteDetailLogic\n\n    fun buildNoteDetailLogic(view: NoteDetailView,\n                             id: String,\n                             isPrivate: Boolean): NoteDetailLogic {\n        logic = NoteDetailLogic(\n                DispatcherProvider,\n                NoteServiceLocator(localAnon, remoteRepo, transactionReg, remotePublic),\n                UserServiceLocator(auth),\n                ViewModelProviders.of(view)\n                        .get(NoteDetailViewModel::class.java),\n                view,\n                AnonymousNoteSource(),\n                RegisteredNoteSource(),\n                PublicNoteSource(),\n                AuthSource(),\n                id,\n                isPrivate\n        )\n\n        view.setObserver(logic)\n\n        return logic\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/wiseassblog/spacenotes/notelist/INoteListContract.kt",
    "content": "package com.wiseassblog.spacenotes.notelist\n\nimport androidx.lifecycle.Observer\nimport androidx.recyclerview.widget.ListAdapter\nimport com.wiseassblog.domain.domainmodel.Note\nimport com.wiseassblog.domain.domainmodel.User\n\n\n/**\n * Created by R_KAY on 10/8/2017.\n */\ninterface INoteListContract {\n\n    interface View {\n        fun setAdapter(adapter: ListAdapter<Note, NoteListAdapter.NoteViewHolder>)\n        fun setPrivateIcon(isPrivate: Boolean)\n        fun showList()\n        fun showEmptyState()\n        fun showErrorState(message:String)\n        fun showLoadingView()\n        fun setToolbarTitle(title: String)\n        fun startLoginFeature()\n        fun setObserver(observer: Observer<NoteListEvent<Int>>)\n        fun startNoteDetailFeatureWithExtras(noteId: String, isPrivate: Boolean)\n    }\n\n    interface ViewModel {\n\n        fun setAdapterState(result: List<Note>)\n\n        fun getAdapterState(): List<Note>\n\n        fun getUserState(): User?\n\n        fun setUserState(userResult: User?)\n\n        fun getIsPrivateMode(): Boolean\n\n        fun setIsPrivateMode(isPrivateMode: Boolean)\n\n    }\n}\n\nsealed class NoteListEvent<out T> {\n    data class OnNoteItemClick<out Int>(val position: Int) : NoteListEvent<Int>()\n    object OnNewNoteClick : NoteListEvent<Nothing>()\n    object OnLoginClick : NoteListEvent<Nothing>()\n    object OnTogglePublicMode : NoteListEvent<Nothing>()\n    object OnStart : NoteListEvent<Nothing>()\n    object OnBind : NoteListEvent<Nothing>()\n    object OnDestroy : NoteListEvent<Nothing>()\n}"
  },
  {
    "path": "app/src/main/java/com/wiseassblog/spacenotes/notelist/NoteDiffUtilCallback.kt",
    "content": "package com.wiseassblog.spacenotes.notelist\n\n\nimport androidx.recyclerview.widget.DiffUtil\nimport com.wiseassblog.domain.domainmodel.Note\n\nclass NoteDiffUtilCallback : DiffUtil.ItemCallback<Note>(){\n    override fun areItemsTheSame(oldItem: Note, newItem: Note): Boolean {\n        return oldItem.creationDate == newItem.creationDate\n    }\n\n    override fun areContentsTheSame(oldItem: Note, newItem: Note): Boolean {\n        return oldItem.creationDate == newItem.creationDate\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/wiseassblog/spacenotes/notelist/NoteListActivity.kt",
    "content": "package com.wiseassblog.spacenotes.notelist\n\nimport android.os.Bundle\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.fragment.app.Fragment\nimport androidx.lifecycle.ViewModelProviders\nimport com.wiseassblog.spacenotes.R\nimport com.wiseassblog.spacenotes.common.attachFragment\nimport com.wiseassblog.spacenotes.notelist.buildlogic.NoteListInjector\n\nprivate const val VIEW = \"NOTE_LIST\"\n\n\nclass NoteListActivity : AppCompatActivity() {\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_note_list)\n        //A container basically just builds things and sets the feature in motion\n        val view = this.supportFragmentManager.findFragmentByTag(VIEW)\n                ?: NoteListView.newInstance()\n\n        attachFragment(supportFragmentManager, R.id.root_activity_list, view, VIEW)\n\n        NoteListInjector(application)\n                .buildNoteListLogic(view as NoteListView)\n\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/wiseassblog/spacenotes/notelist/NoteListAdapter.kt",
    "content": "package com.wiseassblog.spacenotes.notelist\n\n\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.ImageView\nimport android.widget.ProgressBar\nimport android.widget.TextView\nimport androidx.lifecycle.MutableLiveData\nimport androidx.lifecycle.Observer\nimport androidx.recyclerview.widget.ListAdapter\nimport androidx.recyclerview.widget.RecyclerView\nimport com.wiseassblog.domain.domainmodel.Note\nimport com.wiseassblog.spacenotes.R\nimport kotlinx.android.synthetic.main.item_note.view.*\n\n\nclass NoteListAdapter(var event: MutableLiveData<NoteListEvent<Int>> = MutableLiveData()  ) : ListAdapter<Note, NoteListAdapter.NoteViewHolder>(NoteDiffUtilCallback()) {\n\n    internal fun setObserver(observer: Observer<NoteListEvent<Int>>) = event.observeForever(observer)\n\n    override fun onBindViewHolder(holder: NoteListAdapter.NoteViewHolder, position: Int) {\n        getItem(position).let { note ->\n            with(holder) {\n                holder.content.text = note.contents\n                holder.date.text = note.creationDate\n                holder.square.setImageResource(R.drawable.gps_icon)\n                holder.content.text = note.contents\n                holder.itemView.setOnClickListener {\n                    event.value = NoteListEvent.OnNoteItemClick(position)\n\n                }\n            }\n        }\n        holder.apply {\n\n        }\n    }\n\n    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NoteViewHolder {\n        val inflater = LayoutInflater.from(parent.context)\n        return NoteViewHolder(\n                inflater.inflate(R.layout.item_note, parent, false)\n        )\n    }\n\n    class NoteViewHolder(private val root: View) : RecyclerView.ViewHolder(root) {\n        var square: ImageView = root.imv_list_item_icon\n        var dateIcon: ImageView = root.imv_date_and_time\n        var content: TextView = root.lbl_message\n        var date: TextView = root.lbl_date_and_time\n        var loading: ProgressBar = root.pro_item_data\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/java/com/wiseassblog/spacenotes/notelist/NoteListLogic.kt",
    "content": "package com.wiseassblog.spacenotes.notelist\n\nimport androidx.lifecycle.Observer\nimport com.wiseassblog.domain.DispatcherProvider\nimport com.wiseassblog.domain.servicelocator.NoteServiceLocator\nimport com.wiseassblog.domain.servicelocator.UserServiceLocator\nimport com.wiseassblog.domain.domainmodel.Note\nimport com.wiseassblog.domain.domainmodel.Result\nimport com.wiseassblog.domain.error.SpaceNotesError\nimport com.wiseassblog.domain.interactor.AnonymousNoteSource\nimport com.wiseassblog.domain.interactor.AuthSource\nimport com.wiseassblog.domain.interactor.PublicNoteSource\nimport com.wiseassblog.domain.interactor.RegisteredNoteSource\nimport com.wiseassblog.spacenotes.common.*\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.launch\nimport kotlin.coroutines.CoroutineContext\n\nclass NoteListLogic(dispatcher: DispatcherProvider,\n                    val noteLocator: NoteServiceLocator,\n                    val userLocator: UserServiceLocator,\n                    val vModel: INoteListContract.ViewModel,\n                    var adapter: NoteListAdapter,\n                    val view: INoteListContract.View,\n                    val anonymousNoteSource: AnonymousNoteSource,\n                    val registeredNoteSource: RegisteredNoteSource,\n                    val publicNoteSource: PublicNoteSource,\n                    val authSource: AuthSource)\n    : BaseLogic(dispatcher), CoroutineScope, Observer<NoteListEvent<Int>> {\n    override fun onChanged(event: NoteListEvent<Int>?) {\n        when (event) {\n            is NoteListEvent.OnNoteItemClick -> onNoteItemClick(event.position)\n            is NoteListEvent.OnNewNoteClick -> onNewNoteClick()\n            is NoteListEvent.OnLoginClick -> onLoginClick()\n            is NoteListEvent.OnTogglePublicMode -> onTogglePublicMode()\n            is NoteListEvent.OnStart -> onStart()\n            is NoteListEvent.OnBind -> bind()\n            is NoteListEvent.OnDestroy -> clear()\n        }\n    }\n\n    init {\n        //This is directly analogous to CompositeDisposable\n        jobTracker = Job()\n    }\n\n    //dispatcher.provideUIContext is very analogous to observeOn(Dispatchers.UI)\n    override val coroutineContext: CoroutineContext\n        get() = dispatcher.provideUIContext() + jobTracker\n\n    private fun onNewNoteClick() = view.startNoteDetailFeatureWithExtras(\n            \"\",\n            vModel.getIsPrivateMode()\n    )\n\n    private fun onStart() {\n        getListData(vModel.getIsPrivateMode())\n    }\n\n    fun getListData(isPrivateMode: Boolean) = launch {\n        val dataResult: Result<Exception, List<Note>>\n\n        when (isPrivateMode) {\n            true -> dataResult = getPrivateListData()\n            false -> dataResult = getPublicListData()\n        }\n\n        when (dataResult) {\n            is Result.Value -> {\n                vModel.setAdapterState(dataResult.value)\n                renderView(dataResult.value)\n            }\n            is Result.Error -> {\n                view.showEmptyState()\n                view.showErrorState(MESSAGE_GENERIC_ERROR)\n            }\n        }\n    }\n\n    suspend fun getPublicListData(): Result<Exception, List<Note>> {\n        return if (vModel.getUserState() != null) publicNoteSource.getNotes(noteLocator)\n        else Result.build { throw SpaceNotesError.LocalIOException }\n    }\n\n    suspend fun getPrivateListData(): Result<Exception, List<Note>> {\n        return if (vModel.getUserState() == null) anonymousNoteSource.getNotes(noteLocator)\n        else registeredNoteSource.getNotes(noteLocator)\n    }\n\n    fun renderView(list: List<Note>) {\n        view.setPrivateIcon(vModel.getIsPrivateMode())\n        if (vModel.getIsPrivateMode()) view.setToolbarTitle(MODE_PRIVATE)\n        else view.setToolbarTitle(MODE_PUBLIC)\n\n        if (list.isEmpty()) view.showEmptyState()\n        else view.showList()\n\n        adapter.submitList(list)\n    }\n\n    private fun onTogglePublicMode() {\n        if (vModel.getUserState() != null) {\n            if (vModel.getIsPrivateMode()) {\n                vModel.setIsPrivateMode(false)\n                getListData(false)\n            } else {\n                vModel.setIsPrivateMode(true)\n                getListData(true)\n            }\n        } else {\n            view.showErrorState(MESSAGE_LOGIN)\n        }\n\n    }\n\n    private fun onLoginClick() {\n        view.startLoginFeature()\n    }\n\n    private fun onNoteItemClick(position: Int) {\n        val listData = vModel.getAdapterState()\n\n        view.startNoteDetailFeatureWithExtras(\n                listData[position].creationDate, vModel.getIsPrivateMode())\n    }\n\n\n    fun bind() {\n        view.setToolbarTitle(MODE_PRIVATE)\n        view.showLoadingView()\n        adapter.setObserver(this)\n        view.setAdapter(adapter)\n        view.setObserver(this)\n\n        launch {\n            val result = authSource.getCurrentUser(userLocator)\n            if (result is Result.Value) vModel.setUserState(result.value)\n            //otherwise defaults to null\n        }\n    }\n\n    //Single Expression Syntax\n    fun clear() = jobTracker.cancel()\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/wiseassblog/spacenotes/notelist/NoteListView.kt",
    "content": "package com.wiseassblog.spacenotes.notelist\n\n\nimport android.graphics.drawable.AnimationDrawable\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.fragment.app.Fragment\nimport androidx.lifecycle.MutableLiveData\nimport androidx.lifecycle.Observer\nimport androidx.recyclerview.widget.ListAdapter\nimport com.wiseassblog.domain.domainmodel.Note\nimport com.wiseassblog.spacenotes.R\nimport com.wiseassblog.spacenotes.R.id.*\nimport com.wiseassblog.spacenotes.common.makeToast\nimport com.wiseassblog.spacenotes.notelist.buildlogic.NoteListInjector\nimport kotlinx.android.synthetic.main.fragment_note_list.*\n\n\nclass NoteListView : Fragment(), INoteListContract.View {\n    override fun setPrivateIcon(isPrivate: Boolean) {\n        //private mode\n        if (isPrivate) imv_toolbar_private_toggle.setImageResource(R.drawable.design_ic_visibility_off)\n\n        //public mode\n        else imv_toolbar_private_toggle.setImageResource(R.drawable.design_ic_visibility)\n    }\n\n    val event = MutableLiveData<NoteListEvent<Int>>()\n\n    //Event listener\n    override fun setObserver(observer: Observer<NoteListEvent<Int>>) = event.observeForever(observer)\n\n    override fun showErrorState(message: String) = this.makeToast(message)\n\n    override fun startLoginFeature() = com.wiseassblog.spacenotes.common.startLoginFeature(this.activity)\n\n    override fun startNoteDetailFeatureWithExtras(noteId: String, isPrivate: Boolean) = com.wiseassblog.spacenotes.common.startNoteDetailFeatureWithExtras(this.activity, noteId, isPrivate)\n\n    override fun setToolbarTitle(title: String) {\n        lbl_toolbar_title.text = title\n    }\n\n    override fun setAdapter(adapter: ListAdapter<Note, NoteListAdapter.NoteViewHolder>) {\n        rec_list_activity.adapter = adapter\n    }\n\n\n    override fun showLoadingView() {\n        rec_list_activity.visibility = View.INVISIBLE\n        fab_create_new_item.hide()\n        imv_satellite_animation.visibility = View.VISIBLE\n\n        //set loading animation\n        val satelliteLoop = imv_satellite_animation.drawable as AnimationDrawable\n        satelliteLoop.start()\n    }\n\n    override fun showEmptyState() {\n        rec_list_activity.visibility = View.INVISIBLE\n        fab_create_new_item.show()\n        imv_satellite_animation.visibility = View.VISIBLE\n\n        val satelliteLoop = imv_satellite_animation.drawable as AnimationDrawable\n        satelliteLoop.start()\n    }\n\n\n    override fun showList() {\n        rec_list_activity.visibility = View.VISIBLE\n        fab_create_new_item.show()\n        imv_satellite_animation.visibility = View.INVISIBLE\n\n        val satelliteLoop = imv_satellite_animation.drawable as AnimationDrawable\n        satelliteLoop.stop()\n    }\n\n\n    override fun onStart() {\n        super.onStart()\n        event.value = NoteListEvent.OnBind\n    }\n\n    override fun onResume() {\n        super.onResume()\n        event.value = NoteListEvent.OnStart\n    }\n\n    override fun onDestroy() {\n        event.value = NoteListEvent.OnDestroy\n        super.onDestroy()\n    }\n\n    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,\n                              savedInstanceState: Bundle?): View? {\n        // Inflate the layout for this fragment\n\n        return inflater.inflate(R.layout.fragment_note_list, container, false)\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        event.value = NoteListEvent.OnBind\n\n        imv_toolbar_auth.setOnClickListener { event.value = NoteListEvent.OnLoginClick }\n        imv_toolbar_private_toggle.setOnClickListener { event.value = NoteListEvent.OnTogglePublicMode }\n        fab_create_new_item.setOnClickListener { event.value = NoteListEvent.OnNewNoteClick }\n\n        val spaceLoop = imv_space_background.drawable as AnimationDrawable\n        spaceLoop.setEnterFadeDuration(1000)\n        spaceLoop.setExitFadeDuration(1000)\n        spaceLoop.start()\n        super.onViewCreated(view, savedInstanceState)\n    }\n\n    companion object {\n        @JvmStatic\n        fun newInstance(): Fragment = NoteListView()\n    }\n\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/wiseassblog/spacenotes/notelist/NoteListViewModel.kt",
    "content": "package com.wiseassblog.spacenotes.notelist\n\n\nimport androidx.lifecycle.MutableLiveData\nimport androidx.lifecycle.ViewModel\nimport com.wiseassblog.domain.domainmodel.Note\nimport com.wiseassblog.domain.domainmodel.User\n\n/**\n * isPrivateMode refers to whether the User wants to post to and read from a shared repo, or they\n * would like to store their note in private storage.\n */\nclass NoteListViewModel(private var adapterData: MutableLiveData<List<Note>> = MutableLiveData(),\n                        private var user: MutableLiveData<User?> = MutableLiveData(),\n                        private var isPrivateMode: MutableLiveData<Boolean> = MutableLiveData()) : ViewModel(),\n        INoteListContract.ViewModel {\n\n    init {\n        isPrivateMode.value = true\n    }\n\n    override fun getIsPrivateMode(): Boolean {\n        return isPrivateMode.value!!\n    }\n\n    override fun setIsPrivateMode(isPrivateMode: Boolean) {\n        this.isPrivateMode.value = isPrivateMode\n    }\n\n    override fun setAdapterState(result: List<Note>) {\n        adapterData.value = result\n    }\n\n    override fun setUserState(userResult: User?) {\n        user.value = userResult\n    }\n\n    override fun getUserState(): User? {\n        return user.value\n    }\n\n    override fun getAdapterState(): List<Note> {\n        //return current display state or empty string if value is null\n        //see \"Elvis Operator\"\n        return adapterData.value ?: emptyList()\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/wiseassblog/spacenotes/notelist/buildlogic/NoteListInjector.kt",
    "content": "package com.wiseassblog.spacenotes.notelist.buildlogic\n\nimport android.app.Application\nimport androidx.lifecycle.AndroidViewModel\nimport androidx.lifecycle.ViewModelProviders\nimport com.google.firebase.FirebaseApp\nimport com.wiseassblog.data.auth.FirebaseAuthRepositoryImpl\nimport com.wiseassblog.data.note.anonymous.AnonymousNoteDao\nimport com.wiseassblog.data.note.anonymous.AnonymousNoteDatabase\nimport com.wiseassblog.data.note.anonymous.RoomLocalAnonymousRepositoryImpl\nimport com.wiseassblog.data.note.public.FirestoreRemoteNoteImpl\nimport com.wiseassblog.data.note.registered.*\nimport com.wiseassblog.data.transaction.RoomRegisteredTransactionDatabase\nimport com.wiseassblog.data.transaction.RoomTransactionRepositoryImpl\nimport com.wiseassblog.domain.DispatcherProvider\nimport com.wiseassblog.domain.servicelocator.NoteServiceLocator\nimport com.wiseassblog.domain.servicelocator.UserServiceLocator\nimport com.wiseassblog.domain.interactor.AnonymousNoteSource\nimport com.wiseassblog.domain.interactor.AuthSource\nimport com.wiseassblog.domain.interactor.PublicNoteSource\nimport com.wiseassblog.domain.interactor.RegisteredNoteSource\nimport com.wiseassblog.domain.repository.*\nimport com.wiseassblog.spacenotes.notelist.NoteListAdapter\nimport com.wiseassblog.spacenotes.notelist.NoteListLogic\nimport com.wiseassblog.spacenotes.notelist.NoteListView\nimport com.wiseassblog.spacenotes.notelist.NoteListViewModel\n\nclass NoteListInjector(application: Application) : AndroidViewModel(application) {\n    init {\n        FirebaseApp.initializeApp(application)\n    }\n\n    private val anonNoteDao: AnonymousNoteDao by lazy {\n        AnonymousNoteDatabase.getInstance(application).roomNoteDao()\n    }\n\n    private val regNoteDao: RegisteredNoteDao by lazy {\n        RegisteredNoteDatabase.getInstance(application).roomNoteDao()\n    }\n\n    private val transactionDao: RegisteredTransactionDao by lazy {\n        RoomRegisteredTransactionDatabase.getInstance(application).roomTransactionDao()\n    }\n\n    //For non-registered user persistence\n    private val localAnon: ILocalNoteRepository by lazy {\n        RoomLocalAnonymousRepositoryImpl(anonNoteDao)\n    }\n\n    //For registered user remote persistence (Source of Truth)\n    private val remotePrivate: IRemoteNoteRepository by lazy {\n        FirestorePrivateRemoteNoteImpl()\n    }\n\n    //For registered user remote persistence (Source of Truth)\n    private val remotePublicRepo: IPublicNoteRepository by lazy {\n        FirestoreRemoteNoteImpl\n    }\n\n    //For registered user local persistience (cache)\n    private val cacheReg: ILocalNoteRepository by lazy {\n        RoomLocalCacheImpl(regNoteDao)\n    }\n\n    //For registered user remote persistence (Source of Truth)\n    private val remotePrivateRepo: IRemoteNoteRepository by lazy {\n        RegisteredNoteRepositoryImpl(remotePrivate, cacheReg)\n    }\n\n    //For registered user local persistience (cache)\n    private val transactionReg: ITransactionRepository by lazy {\n        RoomTransactionRepositoryImpl(transactionDao)\n    }\n\n    //For user management\n    private val auth: IAuthRepository by lazy {\n        FirebaseAuthRepositoryImpl()\n    }\n\n\n    private lateinit var logic: NoteListLogic\n\n    fun buildNoteListLogic(view: NoteListView): NoteListLogic {\n         logic = NoteListLogic(\n                DispatcherProvider,\n                 NoteServiceLocator(localAnon, remotePrivateRepo, transactionReg, remotePublicRepo),\n                 UserServiceLocator(auth),\n                ViewModelProviders.of(view)\n                        .get(NoteListViewModel::class.java),\n                NoteListAdapter(),\n                view,\n                AnonymousNoteSource(),\n                RegisteredNoteSource(),\n                PublicNoteSource(),\n                AuthSource()\n        )\n\n        view.setObserver(logic)\n\n        return logic\n    }\n}"
  },
  {
    "path": "app/src/main/res/drawable/antenna_loop.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animation-list xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:oneshot=\"false\"\n    >\n    <item android:drawable=\"@drawable/antenna_empty\" android:duration=\"1500\" />\n    <item android:drawable=\"@drawable/antenna_half\" android:duration=\"1500\" />\n    <item android:drawable=\"@drawable/antenna_full\" android:duration=\"1500\" />\n\n</animation-list>"
  },
  {
    "path": "app/src/main/res/drawable/antenna_loop_fast.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animation-list xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:oneshot=\"false\"\n    >\n    <item android:drawable=\"@drawable/antenna_empty\" android:duration=\"100\" />\n    <item android:drawable=\"@drawable/antenna_half\" android:duration=\"100\" />\n    <item android:drawable=\"@drawable/antenna_full\" android:duration=\"100\" />\n\n</animation-list>"
  },
  {
    "path": "app/src/main/res/drawable/ic_access_time_black_24dp.xml",
    "content": "<vector android:alpha=\"0.86\" android:height=\"24dp\"\n    android:tint=\"#FFFFFF\" android:viewportHeight=\"24.0\"\n    android:viewportWidth=\"24.0\" android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"#FF000000\" android:pathData=\"M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_arrow_back_black_24dp.xml",
    "content": "<vector android:alpha=\"0.86\" android:height=\"24dp\"\n    android:tint=\"#FFFFFF\" android:viewportHeight=\"24.0\"\n    android:viewportWidth=\"24.0\" android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"#FF000000\" android:pathData=\"M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_add_24px.xml",
    "content": "<vector android:alpha=\"0.86\" android:height=\"24dp\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"#FF000000\" android:pathData=\"M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_event_24px.xml",
    "content": "<vector android:alpha=\"0.86\" android:height=\"24dp\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"#FF000000\" android:pathData=\"M17,12h-5v5h5v-5zM16,1v2L8,3L8,1L6,1v2L5,3c-1.11,0 -1.99,0.9 -1.99,2L3,19c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2h-1L18,1h-2zM19,19L5,19L5,8h14v11z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_delete_forever_black_24dp.xml",
    "content": "<vector android:alpha=\"0.86\" android:height=\"24dp\"\n    android:tint=\"#FFFFFF\" android:viewportHeight=\"24.0\"\n    android:viewportWidth=\"24.0\" android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"#FF000000\" android:pathData=\"M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2L18,7L6,7v12zM8.46,11.88l1.41,-1.41L12,12.59l2.12,-2.12 1.41,1.41L13.41,14l2.12,2.12 -1.41,1.41L12,15.41l-2.12,2.12 -1.41,-1.41L10.59,14l-2.13,-2.12zM15.5,4l-1,-1h-5l-1,1L5,4v2h14L19,4z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_done_black_24dp.xml",
    "content": "<vector android:alpha=\"0.86\" android:height=\"24dp\"\n    android:tint=\"#FFFFFF\" android:viewportHeight=\"24.0\"\n    android:viewportWidth=\"24.0\" android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"#FF000000\" android:pathData=\"M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportHeight=\"108\"\n    android:viewportWidth=\"108\">\n    <path\n        android:fillColor=\"#26A69A\"\n        android:pathData=\"M0,0h108v108h-108z\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M9,0L9,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,0L19,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,0L29,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,0L39,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,0L49,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,0L59,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,0L69,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,0L79,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M89,0L89,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M99,0L99,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,9L108,9\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,19L108,19\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,29L108,29\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,39L108,39\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,49L108,49\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,59L108,59\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,69L108,69\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,79L108,79\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,89L108,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,99L108,99\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,29L89,29\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,39L89,39\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,49L89,49\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,59L89,59\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,69L89,69\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,79L89,79\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,19L29,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,19L39,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,19L49,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,19L59,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,19L69,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,19L79,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_visibility_off_black_24dp.xml",
    "content": "<vector android:alpha=\"0.86\" android:height=\"24dp\"\n    android:tint=\"#FFFFFF\" android:viewportHeight=\"24.0\"\n    android:viewportWidth=\"24.0\" android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"#FF000000\" android:pathData=\"M12,7c2.76,0 5,2.24 5,5 0,0.65 -0.13,1.26 -0.36,1.83l2.92,2.92c1.51,-1.26 2.7,-2.89 3.43,-4.75 -1.73,-4.39 -6,-7.5 -11,-7.5 -1.4,0 -2.74,0.25 -3.98,0.7l2.16,2.16C10.74,7.13 11.35,7 12,7zM2,4.27l2.28,2.28 0.46,0.46C3.08,8.3 1.78,10.02 1,12c1.73,4.39 6,7.5 11,7.5 1.55,0 3.03,-0.3 4.38,-0.84l0.42,0.42L19.73,22 21,20.73 3.27,3 2,4.27zM7.53,9.8l1.55,1.55c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.66 1.34,3 3,3 0.22,0 0.44,-0.03 0.65,-0.08l1.55,1.55c-0.67,0.33 -1.41,0.53 -2.2,0.53 -2.76,0 -5,-2.24 -5,-5 0,-0.79 0.2,-1.53 0.53,-2.2zM11.84,9.02l3.15,3.15 0.02,-0.16c0,-1.66 -1.34,-3 -3,-3l-0.17,0.01z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_vpn_key_black_24dp.xml",
    "content": "<vector android:alpha=\"0.86\" android:height=\"24dp\"\n    android:tint=\"#FFFFFF\" android:viewportHeight=\"24.0\"\n    android:viewportWidth=\"24.0\" android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"#FF000000\" android:pathData=\"M12.65,10C11.83,7.67 9.61,6 7,6c-3.31,0 -6,2.69 -6,6s2.69,6 6,6c2.61,0 4.83,-1.67 5.65,-4H17v4h4v-4h2v-4H12.65zM7,14c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/satellite_beam.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animation-list xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:oneshot=\"false\"\n    >\n    <item android:drawable=\"@drawable/gps_icon\" android:duration=\"500\" />\n    <item android:drawable=\"@drawable/gps_icon_2\" android:duration=\"500\" />\n    <item android:drawable=\"@drawable/gps_icon_3\" android:duration=\"500\" />\n\n</animation-list>"
  },
  {
    "path": "app/src/main/res/drawable/space_loop.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animation-list xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:oneshot=\"false\"\n    >\n    <item android:drawable=\"@drawable/space_bg_one\" android:duration=\"2000\" />\n    <item android:drawable=\"@drawable/space_bg_two\" android:duration=\"2000\" />\n    <item android:drawable=\"@drawable/space_bg_three\" android:duration=\"2000\" />\n\n</animation-list>"
  },
  {
    "path": "app/src/main/res/drawable-v24/ic_launcher_foreground.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportHeight=\"108\"\n    android:viewportWidth=\"108\">\n    <path\n        android:fillType=\"evenOdd\"\n        android:pathData=\"M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z\"\n        android:strokeColor=\"#00000000\"\n        android:strokeWidth=\"1\">\n        <aapt:attr name=\"android:fillColor\">\n            <gradient\n                android:endX=\"78.5885\"\n                android:endY=\"90.9159\"\n                android:startX=\"48.7653\"\n                android:startY=\"61.0927\"\n                android:type=\"linear\">\n                <item\n                    android:color=\"#44000000\"\n                    android:offset=\"0.0\" />\n                <item\n                    android:color=\"#00000000\"\n                    android:offset=\"1.0\" />\n            </gradient>\n        </aapt:attr>\n    </path>\n    <path\n        android:fillColor=\"#FFFFFF\"\n        android:fillType=\"nonZero\"\n        android:pathData=\"M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z\"\n        android:strokeColor=\"#00000000\"\n        android:strokeWidth=\"1\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_login.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/root_activity_login\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@drawable/space_loop\"\n    tools:context=\"com.wiseassblog.spacenotes.login.LoginActivity\"\n    tools:layout_editor_absoluteY=\"25dp\">\n\n    <ImageButton\n        android:id=\"@+id/imb_toolbar_back\"\n        style=\"@style/Widget.AppCompat.ActionButton\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"8dp\"\n        android:contentDescription=\"Back Button\"\n        android:src=\"@drawable/ic_arrow_back_black_24dp\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <TextView\n        android:id=\"@+id/lbl_login_status_header\"\n        style=\"@style/Text.Primary.LoginHeader\"\n        android:layout_width=\"352dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"8dp\"\n        android:layout_marginBottom=\"8dp\"\n        android:text=\"Login Status:\"\n        app:layout_constraintBottom_toTopOf=\"@+id/imv_antenna_animation\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintHorizontal_bias=\"0.5\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintVertical_bias=\"1.0\" />\n\n\n    <ImageView\n        android:id=\"@+id/imv_antenna_animation\"\n        android:layout_width=\"128dp\"\n        android:layout_height=\"128dp\"\n        android:layout_marginBottom=\"8dp\"\n        android:alpha=\".86\"\n        android:src=\"@drawable/antenna_loop\"\n        android:visibility=\"visible\"\n        app:layout_constraintBottom_toTopOf=\"@+id/lbl_login_status_display\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintHorizontal_bias=\"0.5\"\n        app:layout_constraintStart_toStartOf=\"parent\" />\n\n    <TextView\n        android:id=\"@+id/lbl_login_status_display\"\n        style=\"@style/Text.Primary.LoginHeader.Sub\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintHorizontal_bias=\"0.5\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:text=\"Signed In\" />\n\n    <Button\n        android:id=\"@+id/btn_auth_attempt\"\n        style=\"@style/Widget.AppCompat.Button.Borderless.Colored\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"71dp\"\n        android:layout_marginTop=\"8dp\"\n        android:layout_marginBottom=\"8dp\"\n        android:textSize=\"36sp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintHorizontal_bias=\"0.5\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/lbl_login_status_display\"\n        app:layout_constraintVertical_bias=\"0.0\"\n        tools:text=\"Sign Out\" />\n\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_note_detail.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:id=\"@+id/root_activity_detail\"\n    tools:context=\"com.wiseassblog.spacenotes.notedetail.NoteDetailActivity\"/>\n\n"
  },
  {
    "path": "app/src/main/res/layout/activity_note_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:id=\"@+id/root_activity_list\"\n    tools:context=\"com.wiseassblog.spacenotes.notelist.NoteListActivity\"/>\n\n"
  },
  {
    "path": "app/src/main/res/layout/activity_user_auth.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\"com.wiseassblog.spacenotes.userauth.UserAuthActivity\">\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/fragment_note_detail.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/frag_note_detail\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@drawable/space_loop\"\n    tools:context=\"com.wiseassblog.spacenotes.notedetail.NoteDetailActivity\">\n\n    <FrameLayout\n        android:id=\"@+id/tlb_detail_activity\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"56dp\"\n        android:layout_marginLeft=\"0dp\"\n        android:layout_marginTop=\"0dp\"\n        android:layout_marginRight=\"0dp\"\n        android:paddingStart=\"16dp\"\n        android:paddingEnd=\"16dp\"\n        android:elevation=\"4dp\"\n        android:background=\"@color/colorTransparentBlack\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" >\n\n        <ImageButton\n            android:contentDescription=\"Back Button\"\n            android:id=\"@+id/imb_toolbar_back\"\n            style=\"@style/Widget.AppCompat.ActionButton\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"match_parent\"\n            android:src=\"@drawable/ic_arrow_back_black_24dp\"\n            android:layout_gravity=\"start\"\n            />\n\n        <ImageButton\n            android:contentDescription=\"Delete Button\"\n            android:id=\"@+id/imb_toolbar_delete\"\n            style=\"@style/Widget.AppCompat.ActionButton\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"match_parent\"\n            android:src=\"@drawable/ic_delete_forever_black_24dp\"\n            android:layout_marginEnd=\"74dp\"\n            android:layout_gravity=\"end\"\n            />\n\n        <ImageButton\n            android:contentDescription=\"Finish Editing Note Button\"\n            android:id=\"@+id/imb_toolbar_done\"\n            style=\"@style/Widget.AppCompat.ActionButton\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"match_parent\"\n            android:src=\"@drawable/ic_done_black_24dp\"\n            android:layout_gravity=\"end\"\n            />\n\n    </FrameLayout>\n\n    <ImageView\n        android:id=\"@+id/imv_note_detail_satellite\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:src=\"@drawable/satellite_beam\"\n        android:tint=\"@android:color/white\"\n        android:alpha=\".86\"\n        android:layout_marginStart=\"16dp\"\n        android:layout_marginTop=\"16dp\"\n        android:layout_marginEnd=\"16dp\"\n        android:layout_marginBottom=\"16dp\"\n        app:layout_constraintBottom_toTopOf=\"@+id/gdl_detail_middle\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/tlb_detail_activity\"\n\n        />\n\n    <TextView\n        android:id=\"@+id/lbl_note_detail_date\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_above=\"@+id/edt_note_detail_text\"\n        android:background=\"@color/colorTransparentBlack\"\n        android:padding=\"16dp\"\n        android:gravity=\"center\"\n        android:textSize=\"16sp\"\n        android:fontFamily=\"sans-serif-medium\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintHorizontal_bias=\"1.0\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"@+id/gdl_detail_middle\"\n        tools:text=\"2:43AM 09/7/2018\" />\n\n    <EditText\n        android:inputType=\"textMultiLine\"\n        android:id=\"@+id/edt_note_detail_text\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:padding=\"16dp\"\n        android:background=\"@color/colorTransparentBlack\"\n        android:gravity=\"top|start\"\n        android:maxLines=\"10\"\n        android:scrollbars=\"vertical\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/lbl_note_detail_date\"\n        tools:text=\"\" />\n\n    <androidx.constraintlayout.widget.Guideline\n        android:id=\"@+id/gdl_detail_middle\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\"\n        app:layout_constraintGuide_percent=\"0.5\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_note_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/frag_note_list\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\"com.wiseassblog.spacenotes.notelist.NoteListActivity\">\n\n\n    <TextView\n        android:id=\"@+id/lbl_toolbar_title\"\n        style=\"@style/ToolbarTitle\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"56dp\"\n        android:layout_marginLeft=\"16dp\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:text=\"Private Mode\" />\n\n    <ImageButton\n        android:id=\"@+id/imv_toolbar_private_toggle\"\n        style=\"@style/Widget.AppCompat.ActionButton\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"56dp\"\n        android:layout_marginRight=\"8dp\"\n        android:src=\"@drawable/ic_visibility_off_black_24dp\"\n        app:layout_constraintRight_toLeftOf=\"@+id/imv_toolbar_auth\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <ImageButton\n        android:id=\"@+id/imv_toolbar_auth\"\n        style=\"@style/Widget.AppCompat.ActionButton\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"56dp\"\n        android:layout_gravity=\"right\"\n        android:layout_marginRight=\"8dp\"\n        android:src=\"@drawable/ic_vpn_key_black_24dp\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n\n    <ImageView\n        android:id=\"@+id/imv_space_background\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:layout_marginLeft=\"0dp\"\n        android:layout_marginTop=\"0dp\"\n        android:layout_marginRight=\"0dp\"\n        android:layout_marginBottom=\"0dp\"\n        android:elevation=\"0dp\"\n        android:scaleType=\"centerCrop\"\n        android:src=\"@drawable/space_loop\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintHorizontal_bias=\"0.0\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/lbl_toolbar_title\">\n\n    </ImageView>\n\n    <com.google.android.material.floatingactionbutton.FloatingActionButton\n        android:id=\"@+id/fab_create_new_item\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginEnd=\"16dp\"\n        android:layout_marginRight=\"16dp\"\n        android:layout_marginBottom=\"64dp\"\n        android:src=\"@drawable/ic_baseline_add_24px\"\n        android:visibility=\"invisible\"\n        app:elevation=\"6dp\"\n        app:fabSize=\"normal\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:rippleColor=\"@color/colorTransparentBlack\" />\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/rec_list_activity\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:layout_marginTop=\"0dp\"\n        android:background=\"@color/colorTransparentBlack\"\n        android:visibility=\"invisible\"\n        app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintHorizontal_bias=\"0.0\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/lbl_toolbar_title\" />\n\n    <ImageView\n        android:id=\"@+id/imv_satellite_animation\"\n        android:layout_width=\"128dp\"\n        android:layout_height=\"128dp\"\n        android:alpha=\".86\"\n        android:src=\"@drawable/satellite_beam\"\n        android:tint=\"@android:color/white\"\n        android:visibility=\"visible\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_note.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"88dp\"\n    android:id=\"@+id/root_list_item\"\n    android:background=\"?android:attr/selectableItemBackground\"\n    >\n\n    <ImageView\n        android:id=\"@+id/imv_list_item_icon\"\n        android:layout_width=\"48dp\"\n        android:layout_height=\"48dp\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        android:layout_margin=\"16dp\"\n        android:src=\"@drawable/gps_icon\"\n        android:tint=\"@android:color/white\"\n        android:alpha=\".86\"\n        android:scaleType=\"fitCenter\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        android:layout_marginBottom=\"16dp\"\n        android:layout_marginStart=\"16dp\" />\n\n    <ImageView\n        android:id=\"@+id/imv_date_and_time\"\n        android:layout_width=\"24dp\"\n        android:layout_height=\"24dp\"\n        android:layout_marginStart=\"8dp\"\n        android:alpha=\".86\"\n        android:padding=\"4dp\"\n        android:src=\"@drawable/ic_access_time_black_24dp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintStart_toEndOf=\"@+id/imv_list_item_icon\"\n        app:layout_constraintTop_toBottomOf=\"@+id/lbl_message\"\n        app:layout_constraintVertical_bias=\"0.0\" />\n\n    <TextView\n        android:id=\"@+id/lbl_message\"\n        style=\"@style/Text.Primary\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"24dp\"\n        android:layout_marginStart=\"8dp\"\n        android:layout_marginTop=\"24dp\"\n        android:ellipsize=\"end\"\n        android:gravity=\"center_vertical\"\n        android:singleLine=\"true\"\n        app:layout_constraintHorizontal_bias=\"0.0\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toEndOf=\"@+id/imv_list_item_icon\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:text=\"If at first something doesn't make any sense, find another explanation.\" />\n\n    <TextView\n        android:id=\"@+id/lbl_date_and_time\"\n        style=\"@style/Text\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"24dp\"\n        android:gravity=\"center_vertical\"\n        android:textColor=\"@color/colorAccent\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintStart_toEndOf=\"@+id/imv_date_and_time\"\n        app:layout_constraintTop_toBottomOf=\"@+id/lbl_message\"\n        app:layout_constraintVertical_bias=\"0.0\"\n        tools:text=\"6:30AM 06/01/2017\" />\n\n    <ProgressBar\n        android:id=\"@+id/pro_item_data\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:visibility=\"invisible\"\n        android:background=\"@color/colorPrimaryDark\"\n        android:layout_marginRight=\"0dp\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        android:layout_marginLeft=\"0dp\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        android:layout_marginBottom=\"0dp\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        android:layout_marginTop=\"0dp\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout >"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@drawable/ic_launcher_background\" />\n    <foreground android:drawable=\"@drawable/ic_launcher_foreground\" />\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@drawable/ic_launcher_background\" />\n    <foreground android:drawable=\"@drawable/ic_launcher_foreground\" />\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#121212</color>\n    <color name=\"colorPrimaryDark\">#000000</color>\n    <color name=\"colorAccent\">#80D8FF</color>\n    <color name=\"colorWindowBackground\">#121212</color>\n\n    <!-- 52 = 32%, hex to alpha -->\n    <color name=\"colorTransparentBlack\">#52000000</color>\n    <color name=\"colorTransparentWhite\">#52FFFFFF</color>\n\n    <color name=\"RED\">#FF0000</color>\n    <color name=\"BLUE\">#0000FF</color>\n    <color name=\"GREEN\">#00FF00</color>\n    <color name=\"YELLOW\">#FFEB3B</color>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">SpaceNotes</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.NoActionBar\">\n        <!-- Customize your theme here. -->\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n        <item name=\"android:windowBackground\">@color/colorWindowBackground</item>\n\n    </style>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/view_styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <style name=\"Text\">\n        <item name=\"android:fontFamily\">sans-serif</item>\n        <item name=\"android:textSize\">14sp</item>\n    </style>\n\n    <style name=\"Text.Primary\">\n        <item name=\"android:fontFamily\">sans-serif-medium</item>\n        <item name=\"android:textSize\">16sp</item>\n    </style>\n\n    <style name=\"Text.Primary.LoginHeader\">\n        <item name=\"android:fontFamily\">sans-serif-light</item>\n        <item name=\"android:textSize\">24sp</item>\n        <item name=\"android:gravity\">center</item>\n    </style>\n\n    <style name=\"Text.Primary.LoginHeader.Sub\">\n        <item name=\"android:fontFamily\">sans-serif-medium</item>\n        <item name=\"android:textSize\">18sp</item>\n    </style>\n\n\n    <style name=\"ToolbarTitle\" parent=\"TextAppearance.Widget.AppCompat.Toolbar.Title\">\n        <item name=\"android:gravity\">center_vertical</item>\n        <item name=\"android:fontFamily\">sans-serif-light</item>\n        <item name=\"android:textSize\">18dp</item>\n    </style>\n\n\n</resources>"
  },
  {
    "path": "app/src/test/java/com/wiseassblog/spacenotes/LoginLogicTest.kt",
    "content": "package com.wiseassblog.spacenotes\n\nimport com.google.android.gms.auth.api.signin.GoogleSignInAccount\nimport com.wiseassblog.domain.DispatcherProvider\nimport com.wiseassblog.domain.servicelocator.UserServiceLocator\nimport com.wiseassblog.domain.domainmodel.Result\nimport com.wiseassblog.domain.domainmodel.User\nimport com.wiseassblog.domain.error.SpaceNotesError\nimport com.wiseassblog.domain.interactor.AuthSource\nimport com.wiseassblog.spacenotes.login.*\nimport io.mockk.*\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.runBlocking\nimport org.junit.After\nimport org.junit.Before\nimport org.junit.Test\n\n\nclass LoginLogicTest {\n\n\n    private val dispatcher: DispatcherProvider = mockk()\n\n    private val userLocator: UserServiceLocator = mockk()\n\n    private val view: ILoginContract.View = mockk(relaxed = true)\n\n    private val auth: AuthSource = mockk()\n\n    private val testAccount: GoogleSignInAccount = mockk()\n\n\n    private lateinit var logic: LoginLogic\n\n    val testIdToken: String = \"8675309\"\n\n\n    fun getUser(uid: String = \"8675309\",\n                name: String = \"Ajahn Chah\",\n                profilePicUrl: String = \"\"\n    ) = User(uid,\n            name,\n            profilePicUrl)\n\n    @Before\n    fun clear() {\n        clearAllMocks()\n\n        logic = LoginLogic(dispatcher, userLocator, view, auth)\n\n    }\n\n    /**\n     * In onstart, we give a channel to the firebaseauth backend which it can use to push the latest\n     * user state to listener.\n     * the ui appropriately.\n     * a. User is retrieved successfully\n     * b. User is null\n     * c. Exception: no network connectivity\n     *\n     * a:\n     * 1. Check Network status: available\n     * 2. Ask auth source for current user: User\n     * 3. Start antenna loop\n     * 4. set login status: \"Signed In\"\n     * 5. set login button: \"SIGN OUT\"\n     *\n     */\n    @Test\n    fun `On Start retrieve User`() = runBlocking {\n\n        every {\n            dispatcher.provideUIContext()\n        } returns Dispatchers.Unconfined\n\n        coEvery {\n            auth.getCurrentUser(userLocator)\n        } returns Result.build { getUser() }\n\n        logic.onChanged(LoginEvent.OnStart)\n\n\n        coVerify { auth.getCurrentUser(userLocator) }\n        verify { view.setLoginStatus(SIGNED_IN) }\n        verify { view.showLoopAnimation() }\n        verify { view.setStatusDrawable(ANTENNA_FULL) }\n        verify { view.setAuthButton(SIGN_OUT) }\n    }\n\n    /**\n     *b:\n     * 1. Check Network status: available\n     * 2. Ask auth source for current user: null\n     * 3. Set animation to antenna_full\n     * 4. set login status: \"Signed Out\"\n     * 5. set login button: \"SIGN IN\"\n     */\n    @Test\n    fun `On Start retrieve null`() = runBlocking {\n\n        every {\n            dispatcher.provideUIContext()\n        } returns Dispatchers.Unconfined\n\n        coEvery {\n            auth.getCurrentUser(userLocator)\n        } returns Result.build { null }\n\n        logic.onChanged(LoginEvent.OnStart)\n\n\n        coVerify { auth.getCurrentUser(userLocator) }\n        verify { view.setLoginStatus(SIGNED_OUT) }\n        verify { view.showLoopAnimation() }\n        verify { view.setStatusDrawable(ANTENNA_EMPTY) }\n        verify { view.setAuthButton(SIGN_IN) }\n    }\n\n    /**\n     *c:\n     * 1. Check network status: unavailable\n     * 2. set animatin to drawable antenna_empty:\n     * 3. set login status: \"Network Unavailable\"\n     * 4. set login button: \"RETRY\"\n     */\n    @Test\n    fun `On Start retrieve network error`() = runBlocking {\n        every {\n            dispatcher.provideUIContext()\n        } returns Dispatchers.Unconfined\n\n        coEvery {\n            auth.getCurrentUser(userLocator)\n        } returns Result.build { throw SpaceNotesError.NetworkUnavailableException }\n\n        logic.onChanged(LoginEvent.OnStart)\n\n\n        coVerify { auth.getCurrentUser(userLocator) }\n        verify { view.setLoginStatus(ERROR_NETWORK_UNAVAILABLE) }\n        verify { view.showLoopAnimation() }\n        verify { view.setStatusDrawable(ANTENNA_EMPTY) }\n        verify { view.setAuthButton(RETRY) }\n    }\n\n    /**\n     *In OnAuthButtonClick, the user wishes to sign in to the application. Instruct View to\n     *  create and launch GoogleSignInClient for result, and fire the intent\n     * a. User is currently signed out\n     * b. User is currently signed in\n     * c. network is unavailable\n     *\n     * a.\n     * 1. Check network status: available\n     * 2. User result: null\n     * 3. start sign in flow\n     */\n    @Test\n    fun `On Auth Button Click signed out`() = runBlocking {\n\n        every {\n            dispatcher.provideUIContext()\n        } returns Dispatchers.Unconfined\n\n        coEvery {\n            auth.getCurrentUser(userLocator)\n        } returns Result.build { null }\n\n        logic.onChanged(LoginEvent.OnAuthButtonClick)\n\n\n        coVerify { auth.getCurrentUser(userLocator) }\n        verify { view.showLoopAnimation() }\n        verify { view.startSignInFlow() }\n\n\n    }\n\n    /**\n     * b.\n     * 1. Check network status: available\n     * 2. User result: User\n     * 3. tell auth to sign user out\n     * 4. render user signed out view\n     */\n    @Test\n    fun `On Auth Button Click signed in`() = runBlocking {\n\n        every {\n            dispatcher.provideUIContext()\n        } returns Dispatchers.Unconfined\n\n        coEvery {\n            auth.getCurrentUser(userLocator)\n        } returns Result.build { getUser() }\n\n        coEvery {\n            auth.signOutCurrentUser(userLocator)\n        } returns Result.build { Unit }\n\n\n        logic.onChanged(LoginEvent.OnAuthButtonClick)\n\n        verify { view.showLoopAnimation() }\n        coVerify { auth.getCurrentUser(userLocator) }\n        coVerify { auth.signOutCurrentUser(userLocator) }\n        verify { view.setLoginStatus(SIGNED_OUT) }\n        verify { view.setStatusDrawable(ANTENNA_EMPTY) }\n        verify { view.setAuthButton(SIGN_IN) }\n    }\n\n    /**\n     * c.\n     * 1. Check network status: unavailable\n     * 2. render error view\n     */\n    @Test\n    fun `On Auth Button Click network unavailable`() = runBlocking {\n        every {\n            dispatcher.provideUIContext()\n        } returns Dispatchers.Unconfined\n\n        coEvery {\n            auth.getCurrentUser(userLocator)\n        } returns Result.build { throw SpaceNotesError.NetworkUnavailableException }\n\n\n\n        logic.onChanged(LoginEvent.OnAuthButtonClick)\n\n        verify { view.showLoopAnimation() }\n        coVerify { auth.getCurrentUser(userLocator) }\n        verify { view.setLoginStatus(ERROR_NETWORK_UNAVAILABLE) }\n        verify { view.setStatusDrawable(ANTENNA_EMPTY) }\n        verify { view.setAuthButton(RETRY) }\n    }\n\n\n    @Test\n    fun `On Back Click`() = runBlocking {\n        logic.onChanged(LoginEvent.OnBackClick)\n\n        verify { view.startListFeature() }\n    }\n\n    /**\n     * When the user wishes to create Sign In or create a new account, the result of this\n     * action will pop up in onActivityResult(), which is called prior to onResume(). Since\n     * we're already preferring pragmatism over separation of concerns in this feature due to tight\n     * coupling with Activities, I've chosen to attempt to retrieve the user account from in the\n     * activity. After that, it's up to the Logic class and backend to figure things out.\n     *\n     * a. GoogleSignInAccount succesfully retrieved\n     * b. GoogleSignInAcccount was null, or the task threw an exception\n     *\n     * 1. Pass LoginResult to Logic:\n     * 2. Check request code. If RC_SIGN_IN, we know that the result has to do with\n     * Signing In.\n     * 3. Pass token to backend\n     * 4. Attempt to await response for auth sign in result. This may timeout.\n     * 5. Either way ask firebase for the current user\n     *\n     */\n    @Test\n    fun `On Sign In Result RC_SIGN_IN account idToken acquired`() = runBlocking {\n\n        val loginResult = LoginResult(RC_SIGN_IN, testAccount)\n\n        every {\n            testAccount.idToken\n        } returns testIdToken\n\n        every {\n            dispatcher.provideUIContext()\n        } returns Dispatchers.Unconfined\n\n        coEvery {\n            auth.getCurrentUser(userLocator)\n        } returns Result.build { getUser() }\n\n        coEvery {\n            auth.createGoogleUser(testIdToken, userLocator)\n        } returns Result.build { Unit }\n\n\n        logic.onChanged(LoginEvent.OnGoogleSignInResult(loginResult))\n\n        coVerify { auth.createGoogleUser(testIdToken, userLocator) }\n        coVerify { auth.getCurrentUser(userLocator) }\n        verify { view.setLoginStatus(SIGNED_IN) }\n        verify { view.showLoopAnimation() }\n        verify { view.setStatusDrawable(ANTENNA_FULL) }\n        verify { view.setAuthButton(SIGN_OUT) }\n    }\n\n    /**\n     * b.\n     */\n    @Test\n    fun `On Sign In Result RC_SIGN_IN account null`() = runBlocking {\n\n        val loginResult = LoginResult(RC_SIGN_IN, null)\n\n        every {\n            dispatcher.provideUIContext()\n        } returns Dispatchers.Unconfined\n\n        logic.onChanged(LoginEvent.OnGoogleSignInResult(loginResult))\n\n        verify { view.setLoginStatus(ERROR_AUTH) }\n        verify { view.setStatusDrawable(ANTENNA_EMPTY) }\n        verify { view.setAuthButton(RETRY) }\n    }\n\n    @After\n    fun confirm() {\n        excludeRecords {\n            dispatcher.provideUIContext()\n            testAccount.idToken\n        }\n        confirmVerified(dispatcher, userLocator, view, auth, testAccount)\n    }\n\n}\n"
  },
  {
    "path": "app/src/test/java/com/wiseassblog/spacenotes/NoteDetailLogicTest.kt",
    "content": "package com.wiseassblog.spacenotes\n\nimport com.wiseassblog.domain.DispatcherProvider\nimport com.wiseassblog.domain.domainmodel.Note\nimport com.wiseassblog.domain.domainmodel.Result\nimport com.wiseassblog.domain.domainmodel.User\nimport com.wiseassblog.domain.interactor.AnonymousNoteSource\nimport com.wiseassblog.domain.interactor.AuthSource\nimport com.wiseassblog.domain.interactor.PublicNoteSource\nimport com.wiseassblog.domain.interactor.RegisteredNoteSource\nimport com.wiseassblog.domain.servicelocator.NoteServiceLocator\nimport com.wiseassblog.domain.servicelocator.UserServiceLocator\nimport com.wiseassblog.spacenotes.common.MESSAGE_DELETE_SUCCESSFUL\nimport com.wiseassblog.spacenotes.notedetail.INoteDetailContract\nimport com.wiseassblog.spacenotes.notedetail.NoteDetailEvent\nimport com.wiseassblog.spacenotes.notedetail.NoteDetailLogic\nimport io.mockk.*\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.runBlocking\nimport org.junit.jupiter.api.AfterEach\nimport org.junit.jupiter.api.BeforeEach\nimport org.junit.jupiter.api.Test\n\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * Philipp Hauer\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\nclass NoteDetailLogicTest {\n\n    private val dispatcher: DispatcherProvider = mockk()\n\n    private val noteLocator: NoteServiceLocator = mockk()\n\n    private val userLocator: UserServiceLocator = mockk()\n\n    private val vModel: INoteDetailContract.ViewModel = mockk(relaxed = true)\n\n    private val view: INoteDetailContract.View = mockk(relaxed = true)\n\n    private val anonymous: AnonymousNoteSource = mockk()\n\n    private val registered: RegisteredNoteSource = mockk()\n\n    private val public: PublicNoteSource = mockk()\n\n    private val auth: AuthSource = mockk()\n\n\n    private lateinit var logic: NoteDetailLogic\n\n\n    //Shout out to Philipp Hauer @philipp_hauer for the snippet below (creating test data) with\n    //a default argument wrapper function:\n    fun getNote(creationDate: String = \"12:30:30, November 3rd, 2018\",\n                contents: String = \"When I understand that this glass is already broken, every moment with it becomes precious.\",\n                upVotes: Int = 0,\n                imageUrl: String = \"satellite_beam\",\n                creator: User? = User(\n                        \"8675309\",\n                        \"Ajahn Chah\",\n                        \"satellite_beam\"\n                )\n    ) = Note(\n            creationDate = creationDate,\n            contents = contents,\n            upVotes = upVotes,\n            imageUrl = imageUrl,\n            creator = creator\n    )\n\n    fun getUser(uid: String = \"8675309\",\n                name: String = \"Ajahn Chah\",\n                profilePicUrl: String = \"satellite_beam\"\n    ) = User(uid,\n            name,\n            profilePicUrl)\n\n    fun getLogic(id: String = getNote().creationDate,\n                 isPrivate: Boolean = true) = NoteDetailLogic(\n            dispatcher,\n            noteLocator,\n            userLocator,\n            vModel,\n            view,\n            anonymous,\n            registered,\n            public,\n            auth,\n            id,\n            isPrivate\n    )\n\n\n    @BeforeEach\n    fun clear() {\n        clearAllMocks()\n\n        every {\n            dispatcher.provideUIContext()\n        } returns Dispatchers.Unconfined\n    }\n\n    /**\n     * When auth presses done, they are finished editing their note. They will be returned to a list\n     * view of all notes. Depending on if the note isPrivate, and whether or not the user is\n     * anonymous, will dictate where the note is written to.\n     *\n     * a. isPrivate: true, user: null\n     * b. isPrivate: true, user: not null\n     * c. isPrivate: false, user: not null\n     *\n     * 1. Check current user status: null (anonymous), isPrivate is beside the point if null user\n     * 2. Create a copy of the note in vM, with updated \"content\" value\n     * 3. exit to list activity upon completion\n     */\n    @Test\n    fun `On Done Click private, not logged in`() = runBlocking {\n\n        logic = getLogic()\n\n        every {\n            view.getNoteBody()\n        } returns getNote().contents\n\n        every {\n            vModel.getNoteState()\n        } returns getNote()\n\n        coEvery {\n            anonymous.updateNote(getNote(), noteLocator)\n        } returns Result.build { Unit }\n\n        coEvery {\n            auth.getCurrentUser(userLocator)\n        } returns Result.build { null }\n\n        //call the unit to be tested\n        logic.onChanged(NoteDetailEvent.OnDoneClick)\n\n        //verify interactions and state if necessary\n\n        verify { view.getNoteBody() }\n        verify { vModel.getNoteState() }\n        coVerify { auth.getCurrentUser(userLocator) }\n        coVerify { anonymous.updateNote(getNote(), noteLocator) }\n        verify { view.startListFeature() }\n    }\n\n    /**\n     *b:\n     * 1. get current value of noteBody\n     * 2. write updated note to repositories\n     * 3. exit to list activity\n     */\n    @Test\n    fun `On Done Click private, logged in`() = runBlocking {\n        logic = getLogic()\n\n        every {\n            view.getNoteBody()\n        } returns getNote().contents\n\n        every {\n            vModel.getNoteState()\n        } returns getNote()\n\n        every {\n            vModel.getIsPrivateMode()\n        } returns true\n\n        coEvery {\n            registered.updateNote(getNote(), noteLocator)\n        } returns Result.build { Unit }\n\n        coEvery {\n            auth.getCurrentUser(userLocator)\n        } returns Result.build { getUser() }\n\n        //call the unit to be tested\n        logic.onChanged(NoteDetailEvent.OnDoneClick)\n\n        //verify interactions and state if necessary\n\n        verify { view.getNoteBody() }\n        verify { vModel.getNoteState() }\n        coVerify { auth.getCurrentUser(userLocator) }\n        coVerify { registered.updateNote(getNote(), noteLocator) }\n        verify { view.startListFeature() }\n    }\n\n    /**\n     *c:\n     * 1. get current value of noteBody\n     * 2. write updated note to public\n     * 3. exit to list activity\n     */\n    @Test\n    fun `On Done Click public, logged in`() = runBlocking {\n        logic = getLogic()\n\n        every {\n            view.getNoteBody()\n        } returns getNote().contents\n\n        every {\n            vModel.getNoteState()\n        } returns getNote()\n\n        every {\n            vModel.getIsPrivateMode()\n        } returns false\n\n        coEvery {\n            public.updateNote(getNote(), noteLocator)\n        } returns Result.build { Unit }\n\n        coEvery {\n            auth.getCurrentUser(userLocator)\n        } returns Result.build { getUser() }\n\n        //call the unit to be tested\n        logic.onChanged(NoteDetailEvent.OnDoneClick)\n\n        //verify interactions and state if necessary\n\n        verify { view.getNoteBody() }\n        verify { vModel.getNoteState() }\n        coVerify { auth.getCurrentUser(userLocator) }\n        coVerify { public.updateNote(getNote(), noteLocator) }\n        verify { view.startListFeature() }\n    }\n\n    /**\n     * When auth presses delete, they may wish to delete a note. Show confirmation.\n     */\n    @Test\n    fun `On Delete Click`() = runBlocking {\n        logic = getLogic()\n\n        every {\n            view.showConfirmDeleteSnackbar()\n        } returns Unit\n\n        logic.onChanged(NoteDetailEvent.OnDeleteClick)\n\n        verify { view.showConfirmDeleteSnackbar() }\n    }\n\n    /**\n     * When user confirms that they wish to delete a note, delete the note. There are three possible\n     * places to delete from:\n     * a. Private Anonymous Repo\n     * b. Private Registered Repo\n     * c. Public Repo\n     *\n     * a:\n     * 1. Check status of current user: null\n     * 2. delete Note from anonymous repo\n     * 3. show message to indicate if operation was successful\n     * 3. startListFeature\n     */\n    @Test\n    fun `On Delete Confirmation successful anonymous`() {\n        logic = getLogic()\n\n        every {\n            vModel.getNoteState()\n        } returns getNote()\n\n        coEvery {\n            auth.getCurrentUser(userLocator)\n        } returns Result.build { null }\n\n        coEvery {\n            anonymous.deleteNote(getNote(), noteLocator)\n        } returns Result.build { Unit }\n\n        logic.onChanged(NoteDetailEvent.OnDeleteConfirmed)\n\n        verify { vModel.getNoteState() }\n        verify { view.showMessage(MESSAGE_DELETE_SUCCESSFUL) }\n        verify { view.startListFeature() }\n        coVerify { anonymous.deleteNote(getNote(), noteLocator) }\n        coVerify { auth.getCurrentUser(userLocator) }\n    }\n\n    /**\n     * When user confirms that they wish to delete a note, delete the note. There are three possible\n     * places to delete from:\n     * a. Private Anonymous Repo\n     * b. Private Registered Repo\n     * c. Public Repo\n     *\n     * b:\n     * 1. Check status of current user: not null\n     * 2. check isPrivate: true\n     * 2. delete Note from registered repo\n     * 3. show message to indicate if operation was successful\n     * 3. startListFeature\n     */\n    @Test\n    fun `On Delete Confirmation successful registered`() {\n        logic = getLogic()\n\n        every {\n            vModel.getNoteState()\n        } returns getNote()\n\n        every {\n            vModel.getIsPrivateMode()\n        } returns true\n\n        coEvery {\n            auth.getCurrentUser(userLocator)\n        } returns Result.build { getUser() }\n\n        coEvery {\n            registered.deleteNote(getNote(), noteLocator)\n        } returns Result.build { Unit }\n\n        logic.onChanged(NoteDetailEvent.OnDeleteConfirmed)\n\n        verify { vModel.getNoteState() }\n        verify { view.showMessage(MESSAGE_DELETE_SUCCESSFUL) }\n        verify { view.startListFeature() }\n        coVerify { registered.deleteNote(getNote(), noteLocator) }\n        coVerify { auth.getCurrentUser(userLocator) }\n    }\n\n    /**\n     *\n     * c:\n     * 1. Check status of current user: not null\n     * 2. check isPrivate: false\n     * 2. delete Note from public repo\n     * 3. show message to indicate if operation was successful\n     * 3. startListFeature\n     */\n    @Test\n    fun `On Delete Confirmation successful public`() {\n        logic = getLogic()\n\n        every {\n            vModel.getNoteState()\n        } returns getNote()\n\n        every {\n            vModel.getIsPrivateMode()\n        } returns false\n\n        coEvery {\n            auth.getCurrentUser(userLocator)\n        } returns Result.build { getUser() }\n\n        coEvery {\n            public.deleteNote(getNote(), noteLocator)\n        } returns Result.build { Unit }\n\n        logic.onChanged(NoteDetailEvent.OnDeleteConfirmed)\n\n        verify { vModel.getNoteState() }\n        verify { view.showMessage(MESSAGE_DELETE_SUCCESSFUL) }\n        verify { view.startListFeature() }\n        coVerify { public.deleteNote(getNote(), noteLocator) }\n        coVerify { auth.getCurrentUser(userLocator) }\n    }\n\n    @Test\n    fun `On Back Click`() {\n        logic = getLogic()\n\n        logic.onChanged(NoteDetailEvent.OnBackClick)\n\n        verify { view.startListFeature() }\n    }\n\n\n    /**\n     * In on bind, we need to check the status of arguments sent in to the feature via intent,\n     * check the user status, and call onStart() to render the view.\n     *\n     * a. get id from vModel: \"\" or null (new note)\n     * b. get id from vModel: not null (not new note)\n     * c. get user from auth: null\n     * d. get user from auth: not null\n     * e. get isPrivate from vModel: true\n     * f. get isPrivate from vModel: false\n     *\n     * a/c:\n     * 1. Check User state: null\n     * 2. Check arguments from activity: note id = \"\", isPrivate = true\n     * 3. Create new note with date and null user, store in vModel\n     * 4. render view\n     * - back set to invisible (only delete or save allowed for new notes\n     * - start satellite animation\n     * - set creation date\n     */\n    @Test\n    fun `On bind a and c`() {\n        logic = getLogic(\"\", true)\n\n        every {\n            view.getTime()\n        } returns getNote().creationDate\n\n        every {\n            vModel.getId()\n        } returns \"\"\n\n        every {\n            vModel.getIsPrivateMode()\n        } returns true\n\n        coEvery {\n            auth.getCurrentUser(userLocator)\n        } returns Result.build { null }\n\n        logic.onChanged(NoteDetailEvent.OnBind)\n\n        //creatorId should be null for new note. It will be added if the user saves the note while\n        //logged in\n        verify { vModel.setNoteState(getNote(creator = null, contents = \"\", imageUrl = \"satellite_beam\")) }\n        verify { vModel.setIsPrivateMode(true) }\n        coVerify { auth.getCurrentUser(userLocator) }\n        verify { vModel.setId(\"\") }\n        verify { view.getTime() }\n        verify { view.hideBackButton() }\n        excludeRecords {\n            view.setBackgroundImage(any())\n            view.setDateLabel(any())\n            view.setNoteBody(any())\n        }\n    }\n\n    /**\n     *a: New Note\n     *d: Not null user\n     */\n    @Test\n    fun `On bind a and d`() {\n        logic = getLogic(\"\", true)\n\n        every {\n            view.getTime()\n        } returns getNote().creationDate\n\n        every {\n            vModel.getId()\n        } returns \"\"\n\n        every {\n            vModel.getIsPrivateMode()\n        } returns true\n\n        coEvery {\n            auth.getCurrentUser(userLocator)\n        } returns Result.build { getUser() }\n\n        logic.onChanged(NoteDetailEvent.OnBind)\n\n        //creatorId should be null for new note. It will be added if the user saves the note while\n        //logged in\n        verify { vModel.setNoteState(getNote(creator = getUser(), contents = \"\", imageUrl = \"satellite_beam\")) }\n        verify { vModel.setIsPrivateMode(true) }\n        coVerify { auth.getCurrentUser(userLocator) }\n        verify { vModel.setId(\"\") }\n        verify { view.getTime() }\n        verify { view.hideBackButton() }\n        excludeRecords {\n            view.setBackgroundImage(any())\n            view.setDateLabel(any())\n            view.setNoteBody(any())\n        }\n    }\n\n    /**\n     *b: Not new Note\n     *c: User is null\n     *\n     * 1. Get current user: null\n     * 2. Check id: not null\n     * 3. Query anonymous datasource based on id\n     */\n    @Test\n    fun `On bind b and c`() {\n        logic = getLogic(getNote().creationDate, true)\n\n        every {\n            vModel.getId()\n        } returns getNote().creationDate\n\n        every {\n            vModel.getIsPrivateMode()\n        } returns true\n\n        coEvery {\n            auth.getCurrentUser(userLocator)\n        } returns Result.build { null }\n\n        coEvery {\n            anonymous.getNoteById(getNote().creationDate, noteLocator)\n        } returns Result.build { getNote() }\n\n        logic.onChanged(NoteDetailEvent.OnBind)\n\n        //creatorId should be null for new note. It will be added if the user saves the note while\n        //logged in\n        verify { vModel.setNoteState(getNote()) }\n        verify { vModel.setIsPrivateMode(true) }\n        coVerify { auth.getCurrentUser(userLocator) }\n        verify { vModel.setId(getNote().creationDate) }\n        coExcludeRecords {\n            anonymous.getNoteById(any(), any())\n            view.setBackgroundImage(any())\n            view.setDateLabel(any())\n            view.setNoteBody(any())\n        }\n    }\n\n    /**\n     *b: Not new Note\n     *f: public mode\n     */\n    @Test\n    fun `On bind b and f`() {\n        logic = getLogic(getNote().creationDate, false)\n\n        every {\n            vModel.getId()\n        } returns getNote().creationDate\n\n        every {\n            vModel.getIsPrivateMode()\n        } returns false\n\n        coEvery {\n            auth.getCurrentUser(userLocator)\n        } returns Result.build { getUser() }\n\n        coEvery {\n            public.getNoteById(getNote().creationDate, noteLocator)\n        } returns Result.build { getNote() }\n\n        logic.onChanged(NoteDetailEvent.OnBind)\n\n        //creatorId should be null for new note. It will be added if the user saves the note while\n        //logged in\n        verify { vModel.setNoteState(getNote()) }\n        verify { vModel.setIsPrivateMode(false) }\n        coVerify { auth.getCurrentUser(userLocator) }\n        coVerify { public.getNoteById(getNote().creationDate, noteLocator) }\n        verify { vModel.setId(getNote().creationDate) }\n        coExcludeRecords {\n            anonymous.getNoteById(any(), any())\n            view.setBackgroundImage(any())\n            view.setDateLabel(any())\n            view.setNoteBody(any())\n        }\n    }\n\n    /**\n     *a: New Note\n     *f: public mode\n     */\n    @Test\n    fun `On bind a and f`() {\n        logic = getLogic(\"\", false)\n\n        every {\n            vModel.getId()\n        } returns \"\"\n\n        every {\n            view.getTime()\n        } returns getNote().creationDate\n\n        every {\n            vModel.getIsPrivateMode()\n        } returns false\n\n        coEvery {\n            auth.getCurrentUser(userLocator)\n        } returns Result.build { getUser() }\n\n        logic.onChanged(NoteDetailEvent.OnBind)\n\n        coVerify { auth.getCurrentUser(userLocator) }\n        verify { vModel.getId() }\n        verify { view.getTime() }\n        verify { view.hideBackButton() }\n        verify {\n            vModel.setNoteState(\n                    getNote(\n                            contents = \"\"\n                    )\n            )\n        }\n        coExcludeRecords {\n            anonymous.getNoteById(any(), any())\n            view.setBackgroundImage(any())\n            view.setDateLabel(any())\n            view.setNoteBody(any())\n        }\n    }\n\n    /**On start  can be considered as a generic event to represent the view telling the listener\n     * that it's time to rock'n'roll.\n     *\n     * 1. Get value of the Note from VM\n     * 2. Render View\n     */\n    @Test\n    fun `On start`() = runBlocking {\n        logic = getLogic(id = getNote().creationDate)\n\n        every {\n            vModel.getNoteState()\n        } returns getNote()\n\n        logic.onChanged(NoteDetailEvent.OnStart)\n\n        verify { vModel.getNoteState() }\n        verify { view.setBackgroundImage(getNote().imageUrl) }\n        verify { view.setDateLabel(getNote().creationDate) }\n        verify { view.setNoteBody(getNote().contents) }\n    }\n\n    @AfterEach\n    fun confirm() {\n        excludeRecords {\n            dispatcher.provideUIContext()\n\n            vModel.getNoteState()\n\n            vModel.setId(any())\n            vModel.getId()\n\n            vModel.setIsPrivateMode(any())\n            vModel.getIsPrivateMode()\n        }\n        confirmVerified(\n                dispatcher,\n                noteLocator,\n                userLocator,\n                vModel,\n                view,\n                anonymous,\n                registered,\n                public,\n                auth\n        )\n    }\n\n\n}\n"
  },
  {
    "path": "app/src/test/java/com/wiseassblog/spacenotes/NoteListLogicTest.kt",
    "content": "package com.wiseassblog.spacenotes\n\nimport com.wiseassblog.domain.DispatcherProvider\nimport com.wiseassblog.domain.servicelocator.NoteServiceLocator\nimport com.wiseassblog.domain.servicelocator.UserServiceLocator\nimport com.wiseassblog.domain.domainmodel.Note\nimport com.wiseassblog.domain.domainmodel.Result\nimport com.wiseassblog.domain.domainmodel.User\nimport com.wiseassblog.domain.error.SpaceNotesError\nimport com.wiseassblog.domain.interactor.AnonymousNoteSource\nimport com.wiseassblog.domain.interactor.AuthSource\nimport com.wiseassblog.domain.interactor.PublicNoteSource\nimport com.wiseassblog.domain.interactor.RegisteredNoteSource\nimport com.wiseassblog.spacenotes.common.*\nimport com.wiseassblog.spacenotes.notelist.INoteListContract\nimport com.wiseassblog.spacenotes.notelist.NoteListAdapter\nimport com.wiseassblog.spacenotes.notelist.NoteListEvent\nimport com.wiseassblog.spacenotes.notelist.NoteListLogic\nimport io.mockk.*\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.runBlocking\nimport org.junit.After\nimport org.junit.jupiter.api.BeforeEach\nimport org.junit.jupiter.api.Test\n\nclass NoteListLogicTest {\n\n    //This mocking framework is called Mockk\n    private val dispatcher: DispatcherProvider = mockk()\n\n    private val noteLocator: NoteServiceLocator = mockk()\n\n    private val userLocator: UserServiceLocator = mockk()\n\n    private val vModel: INoteListContract.ViewModel = mockk(relaxed = true)\n\n    private val adapter: NoteListAdapter = mockk(relaxed = true)\n\n    private val view: INoteListContract.View = mockk(relaxed = true)\n\n    private val anonymous: AnonymousNoteSource = mockk()\n\n    private val registered: RegisteredNoteSource = mockk()\n\n    private val public: PublicNoteSource = mockk()\n\n    private val auth: AuthSource = mockk()\n\n\n    private val logic = NoteListLogic(\n            dispatcher,\n            noteLocator,\n            userLocator,\n            vModel,\n            adapter,\n            view,\n            anonymous,\n            registered,\n            public,\n            auth\n    )\n\n    //Shout out to Philipp Hauer @philipp_hauer for the snippet below (creating test data) with\n    //a default argument wrapper function:\n    fun getNote(creationDate: String = \"28/10/2018\",\n                contents: String = \"When I understand that this glass is already broken, every moment with it becomes precious.\",\n                upVotes: Int = 0,\n                imageUrl: String = \"\",\n                creator: User? = User(\n                        \"8675309\",\n                        \"Ajahn Chah\",\n                        \"\"\n                )\n    ) = Note(\n            creationDate = creationDate,\n            contents = contents,\n            upVotes = upVotes,\n            imageUrl = imageUrl,\n            creator = creator\n    )\n\n    fun getUser(uid: String = \"8675309\",\n                name: String = \"Ajahn Chah\",\n                profilePicUrl: String = \"\"\n    ) = User(uid,\n            name,\n            profilePicUrl)\n\n    val getNoteList = listOf<Note>(\n            getNote(),\n            getNote(),\n            getNote()\n    )\n\n\n    @BeforeEach\n    fun build() {\n        clearAllMocks()\n        every { dispatcher.provideUIContext() } returns Dispatchers.Unconfined\n    }\n\n    /**\n     * New Note events will have two possible event streams, based on whether or not the user is\n     * in private or public mode\n     * a: User is in private mode (could be logged in or anonymous, either case is fine)\n     * c: User is in public mode (must be logged in, but we'll check in the other activity to avoid\n     * shared mutable state issues)\n     *\n     * a:\n     * 1. check isPrivate status from ViewModel: true\n     * 2. startNoteDetailFeatureWithExtras with empty string as extra\n     */\n    @Test\n    fun `On New Note Click Private`() {\n        //prepare mock interactions\n        every { vModel.getIsPrivateMode() } returns true\n\n        //call the unit to be tested\n        logic.onChanged(NoteListEvent.OnNewNoteClick)\n\n        //verify interactions and state if necessary\n        verify { view.startNoteDetailFeatureWithExtras(\"\", true) }\n    }\n\n    /**\n     * b:\n     * 1.\n     * 2. startNoteDetailFeatureWithExtras with empty string as extra\n     */\n    @Test\n    fun `On New Note Click Public`() {\n        //prepare mock interactions\n        every { vModel.getIsPrivateMode() } returns false\n\n        //call the unit to be tested\n        logic.onChanged(NoteListEvent.OnNewNoteClick)\n\n        //verify interactions and state if necessary\n        verify { view.startNoteDetailFeatureWithExtras(\"\", false) }\n    }\n\n    /**\n     * On bind process, called by view in onCreate. Check current user state, write that result to\n     * vModel, show loading graphic, perform some initialization\n     *\n     * a. User is Anonymous\n     * b. User is Registered\n     *\n     * a:\n     * 1. Display Loading View\n     * 2. Check for a logged in user from auth: null\n     * 3. write null to vModel user state\n     * 4. call On start process\n     */\n    @Test\n    fun `On bind User anonymous`() = runBlocking {\n\n        coEvery { auth.getCurrentUser(userLocator) } returns Result.build { null }\n\n        logic.onChanged(NoteListEvent.OnBind)\n\n        coVerify { auth.getCurrentUser(userLocator) }\n        verify { vModel.setUserState(null) }\n        verify { view.showLoadingView() }\n        verify { view.setToolbarTitle(MODE_PRIVATE) }\n        verify { view.setAdapter(adapter) }\n        verify { adapter.setObserver(logic) }\n\n    }\n\n    @Test\n    fun `On bind user registered`() = runBlocking {\n\n        coEvery { auth.getCurrentUser(userLocator) } returns Result.build { getUser() }\n\n        logic.onChanged(NoteListEvent.OnBind)\n\n        coVerify { auth.getCurrentUser(userLocator) }\n        verify { vModel.setUserState(getUser()) }\n        verify { view.showLoadingView() }\n        verify { view.setAdapter(adapter) }\n        verify { view.setToolbarTitle(MODE_PRIVATE) }\n        verify { adapter.setObserver(logic) }\n    }\n\n    @Test\n    fun `On bind error retrieving user`() = runBlocking {\n\n        coEvery { auth.getCurrentUser(userLocator) } returns Result.build { throw SpaceNotesError.AuthError }\n\n        logic.onChanged(NoteListEvent.OnBind)\n\n        coVerify { auth.getCurrentUser(userLocator) }\n        verify { view.showLoadingView() }\n        verify { view.setToolbarTitle(MODE_PRIVATE) }\n        verify { view.setAdapter(adapter) }\n        verify { adapter.setObserver(logic) }\n    }\n\n    /**\n     *\n     * On start basically means that we want to render the UI. This depends on whether the user is\n     * anonymous, or registered (logged out or in), and if they are in public or private mode\n     * a. User is anonymous (always private in that case)\n     * b. User is registered, private mode\n     * c. User is registered, public mode\n     *\n     * a:\n     *1. Check isPrivate status: true\n     *2. Check login status in backend if necessary\n     *3. parse datasources accordingly\n     *4. draw view accordingly\n     */\n    @Test\n    fun `On Start anonymous`() = runBlocking {\n        every { vModel.getIsPrivateMode() } returns true\n        every { vModel.getUserState() } returns null\n        coEvery { anonymous.getNotes(noteLocator) } returns Result.build { getNoteList }\n\n        logic.onChanged(NoteListEvent.OnStart)\n\n        verify { vModel.getIsPrivateMode() }\n        verify { vModel.getUserState() }\n        verify { view.showList() }\n        verify { adapter.submitList(getNoteList) }\n        coVerify { anonymous.getNotes(noteLocator) }\n    }\n\n    /**\n     * b:\n     *1. Check isPrivate status: false\n     *2. Check login status in backend if necessary\n     *3. parse datasources accordingly\n     *4. draw view accordingly\n     *\n     */\n    @Test\n    fun `On Start Registered Private`() = runBlocking {\n        every { vModel.getIsPrivateMode() } returns true\n        every { vModel.getUserState() } returns getUser()\n        coEvery { registered.getNotes(noteLocator) } returns Result.build { getNoteList }\n\n        logic.onChanged(NoteListEvent.OnStart)\n\n        verify { vModel.getIsPrivateMode() }\n        verify { vModel.getUserState() }\n        verify { view.showList() }\n        verify { adapter.submitList(getNoteList) }\n        coVerify { registered.getNotes(noteLocator) }\n    }\n\n    /**\n     *c:\n     *1. Check isPrivate status: false\n     *2. Check login status in backend if necessary\n     *3. parse datasources accordingly\n     *4. draw view accordingly\n     *\n     */\n    @Test\n    fun `On Start Registered Public`() = runBlocking {\n        every { vModel.getIsPrivateMode() } returns false\n        every { vModel.getUserState() } returns getUser()\n        coEvery { public.getNotes(noteLocator) } returns Result.build { getNoteList }\n\n        logic.onChanged(NoteListEvent.OnStart)\n\n        verify { vModel.getIsPrivateMode() }\n        verify { vModel.getUserState() }\n        verify { view.showList() }\n        verify { adapter.submitList(getNoteList) }\n        coVerify { public.getNotes(noteLocator) }\n    }\n\n    /**\n     * error:\n     *1. Check isPrivate status: false\n     *2. Check login status in backend if necessary\n     *3. parse datasources accordingly\n     *4. draw view accordingly\n     *\n     */\n    @Test\n    fun `On Start Error`() = runBlocking {\n        every { vModel.getIsPrivateMode() } returns true\n        every { vModel.getUserState() } returns getUser()\n        coEvery { registered.getNotes(noteLocator) } returns Result.build { throw SpaceNotesError.RemoteIOException }\n\n        logic.onChanged(NoteListEvent.OnStart)\n\n        verify { vModel.getIsPrivateMode() }\n        verify { vModel.getUserState() }\n        verify { view.showEmptyState() }\n        verify { view.showErrorState(MESSAGE_GENERIC_ERROR) }\n        coVerify { registered.getNotes(noteLocator) }\n    }\n\n    /**\n     * For empty list, leave the loading animation active.\n     */\n    @Test\n    fun `On Start a with empty list`() = runBlocking {\n        every { vModel.getIsPrivateMode() } returns true\n        every { vModel.getUserState() } returns getUser()\n        coEvery { registered.getNotes(noteLocator) } returns Result.build { emptyList<Note>() }\n\n        logic.onChanged(NoteListEvent.OnStart)\n\n        verify { vModel.getIsPrivateMode() }\n        verify { vModel.getUserState() }\n        verify { view.showEmptyState() }\n        verify { adapter.submitList(emptyList<Note>()) }\n        coVerify { registered.getNotes(noteLocator) }\n    }\n\n    /**\n     * c. auth is logged in and in public mode\n     *1. Check auth status\n     *2. Check isPrivate status\n     *3.  parse datasources accordingly\n     */\n    @Test\n    fun `On Start Public Mode`() = runBlocking {\n        every { vModel.getIsPrivateMode() } returns false\n        coEvery { public.getNotes(noteLocator) } returns Result.build { getNoteList }\n\n        logic.onChanged(NoteListEvent.OnStart)\n\n        verify { vModel.getIsPrivateMode() }\n        verify { view.showList() }\n        verify { adapter.submitList(getNoteList) }\n        coVerify { public.getNotes(noteLocator) }\n    }\n\n\n    /**\n     * On login click, send auth to Auth Activity in order to manage their login status\n     *\n     *1. start login activity\n     */\n    @Test\n    fun `On Login Click `() {\n\n        logic.onChanged(NoteListEvent.OnLoginClick)\n\n        verify { view.startLoginFeature() }\n    }\n\n    /**\n     * On Note Item Click, auth wishes to navigate to a detailed view of the selected item\n     *a: isPrivate = true\n     *1. Get appropriate Note from vModel\n     *2. Get isPrivate from vModel\n     *2. Start detail Activity with note id passed as extra, and isPrivate result\n     */\n    @Test\n    fun `On Note Item Click a`() = runBlocking {\n\n\n        every { vModel.getIsPrivateMode() } returns true\n        every { vModel.getAdapterState() } returns getNoteList\n\n        //auth selects first item in adapter\n        val clickEvent = NoteListEvent.OnNoteItemClick(0)\n\n        logic.onChanged(clickEvent)\n\n        verify { view.startNoteDetailFeatureWithExtras(getNote().creationDate, true) }\n        verify { vModel.getAdapterState() }\n        verify { vModel.getIsPrivateMode() }\n    }\n\n\n    /**\n     *b: isPrivate = false\n     *1. Get appropriate Note from vModel\n     *2. Get isPrivate from vModel\n     *2. Start detail Activity with note id passed as extra, and isPrivate result\n     */\n    @Test\n    fun `On Note Item Click b`() = runBlocking {\n\n        every { vModel.getIsPrivateMode() } returns false\n        every { vModel.getAdapterState() } returns getNoteList\n\n        //auth selects first item in adapter\n        val clickEvent = NoteListEvent.OnNoteItemClick(0)\n\n        logic.onChanged(clickEvent)\n\n        verify { view.startNoteDetailFeatureWithExtras(getNote().creationDate, false) }\n        verify { vModel.getAdapterState() }\n        verify { vModel.getIsPrivateMode() }\n    }\n\n    /**\n     * When the user wants to switch between private and public mode\n     * a: User is logged in, currently in private mode\n     * b: User is logged in, currently in public mode\n     * c: User is logged out, private only\n     *\n     *a:\n     *1. Check current user status: User\n     *2. Get isPrivate from vModel: true\n     *3. Request public notes from repo: Notes\n     *4. Update view/adapter appropriately\n     *  */\n    @Test\n    fun `On Toggle Public Mode`() = runBlocking {\n\n        every { vModel.getIsPrivateMode() } returns true andThen false\n        coEvery { public.getNotes(noteLocator) } returns Result.build { getNoteList }\n\n        logic.onChanged(NoteListEvent.OnTogglePublicMode)\n\n        verify { vModel.setAdapterState(getNoteList) }\n        verify { vModel.getIsPrivateMode() }\n        verify { adapter.submitList(getNoteList) }\n        coVerify { public.getNotes(noteLocator) }\n        //ought to be false and MODE_PUBLIC, but\n        verify { view.setPrivateIcon(false) }\n        verify { view.setToolbarTitle(MODE_PUBLIC) }\n\n    }\n\n    /**\n     * b:\n    *1. Check current user status: User\n    *2. Get isPrivate from vModel: false\n    *3. Request private notes from repo: Notes\n    *4. Update view/adapter appropriately\n    *  */\n    @Test\n    fun `On Toggle Private Mode`() = runBlocking {\n\n\n        every { vModel.getIsPrivateMode() } returns false andThen true\n        coEvery { registered.getNotes(noteLocator) } returns Result.build { getNoteList }\n\n        logic.onChanged(NoteListEvent.OnTogglePublicMode)\n\n        verify { vModel.setAdapterState(getNoteList) }\n        verify { vModel.getIsPrivateMode() }\n        verify { adapter.submitList(getNoteList) }\n        coVerify { registered.getNotes(noteLocator) }\n        verify { view.setPrivateIcon(true) }\n        verify { view.setToolbarTitle(MODE_PRIVATE) }\n    }\n\n    /**\n     * C:\n     *1. Check current user status: no user\n     *2. Tell user to log in if they want to use the public feature\n     *  */\n    @Test\n    fun `On Toggle Private Mode logged out`() = runBlocking {\n        every { vModel.getUserState() } returns null\n\n        logic.onChanged(NoteListEvent.OnTogglePublicMode)\n\n        verify { view.showErrorState(MESSAGE_LOGIN) }\n    }\n\n    @After\n    fun confirm() {\n        excludeRecords { dispatcher.provideUIContext() }\n        confirmVerified(\n                dispatcher,\n                noteLocator,\n                userLocator,\n                vModel,\n                adapter,\n                view,\n                anonymous,\n                registered,\n                public,\n                auth\n        )\n    }\n\n}"
  },
  {
    "path": "build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    apply from: 'versions.gradle'\n    addRepos(repositories)\n\n    dependencies {\n        classpath deps.android_gradle_plugin\n        classpath deps.kotlin.kotlin_gradle_plugin\n        classpath 'com.google.gms:google-services:4.2.0'\n\n        // NOTE: Do not place your application dependencies here; they belong\n        // in the individual module build.gradle files\n    }\n}\n\nallprojects {\n    addRepos(repositories)\n}\n\ntask clean(type: Delete) {\n    delete rootProject.buildDir\n}\n\n"
  },
  {
    "path": "data/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "data/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\napply plugin: 'kotlin-android'\n\napply plugin: 'kotlin-android-extensions'\n\napply plugin: 'kotlin-kapt'\n\nandroid {\n    compileSdkVersion build_versions.target_sdk\n\n    lintOptions {\n        abortOnError false\n    }\n\n    defaultConfig {\n        minSdkVersion build_versions.min_sdk\n        targetSdkVersion build_versions.target_sdk\n        versionCode 1\n        versionName \"1.0\"\n        testInstrumentationRunner \"android.support.test.runner.AndroidJUnitRunner\"\n    }\n\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_8\n        targetCompatibility JavaVersion.VERSION_1_8\n    }\n\n    buildTypes {\n        debug {\n            testCoverageEnabled = true\n            minifyEnabled false\n        }\n\n        release {\n            testCoverageEnabled = false\n            minifyEnabled true\n        }\n    }\n\n    testOptions.unitTests.all {\n        useJUnitPlatform()\n\n        testLogging {\n            events 'passed', 'skipped', 'failed', 'standardOut', 'standardError'\n        }\n    }\n\n}\n\ndependencies {\n    //to talk to another module within the project:\n    implementation project(\":domain\")\n\n    implementation deps.room.runtime\n\n    implementation deps.firebase.auth\n    implementation deps.firebase.firestore\n    implementation deps.play_services.auth\n\n    implementation deps.kotlin.kotlin_jre\n    implementation deps.kotlin.coroutines_core\n    implementation deps.kotlin.coroutines_android\n\n    kapt deps.room.compiler\n\n    testImplementation deps.test.junit\n    testRuntimeOnly deps.test.jupiter_engine\n    testRuntimeOnly deps.test.vintage_engine\n    testImplementation deps.test.mockk\n    testImplementation deps.test.kotlin_junit\n}\n\n"
  },
  {
    "path": "data/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "data/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.wiseassblog.data\" />\n"
  },
  {
    "path": "data/src/main/java/com/wiseassblog/data/DataExt.kt",
    "content": "package com.wiseassblog.data\n\nimport android.net.Uri\nimport com.google.android.gms.tasks.Task\nimport com.wiseassblog.data.datamodels.AnonymousRoomNote\nimport com.wiseassblog.data.datamodels.RegisteredRoomNote\nimport com.wiseassblog.data.datamodels.RegisteredRoomTransaction\nimport com.wiseassblog.data.datamodels.FirebaseNote\nimport com.wiseassblog.domain.domainmodel.Note\nimport com.wiseassblog.domain.domainmodel.NoteTransaction\nimport com.wiseassblog.domain.domainmodel.TransactionType\nimport com.wiseassblog.domain.domainmodel.User\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.resumeWithException\nimport kotlin.coroutines.suspendCoroutine\n\n\nsuspend fun <T> awaitTaskResult(task: Task<T>): T = suspendCoroutine { continuation ->\n    task.addOnCompleteListener { task ->\n        if (task.isSuccessful) {\n            continuation.resume(task.result!!)\n        } else {\n            continuation.resumeWithException(task.exception!!)\n        }\n    }\n}\n//Wraps Firebase/GMS calls\nsuspend fun <T> awaitTaskCompletable(task: Task<T>): Unit = suspendCoroutine { continuation ->\n    task.addOnCompleteListener { task ->\n        if (task.isSuccessful) {\n            continuation.resume(Unit)\n        } else {\n            continuation.resumeWithException(task.exception!!)\n        }\n    }\n}\n\n\n//Since this.creator is of type Note?, we must give it a default value in such cases.\ninternal val Note.safeGetUid: String\n    get() = this.creator?.uid ?: \"\"\n\ninternal val NoteTransaction.safeGetUid: String\n    get() = this.creator?.uid ?: \"\"\n\ninternal val Uri?.defaultIfEmpty: String\n    get() = if (this.toString() == \"\" || this == null) \"satellite_beam\"\n    else this.toString()\n\n\n//\"this\" refers to the object upon which this extension property is called\ninternal val Note.toAnonymousRoomNote: AnonymousRoomNote\n    get() = AnonymousRoomNote(\n            this.creationDate,\n            this.contents,\n            this.upVotes,\n            this.imageUrl,\n            this.safeGetUid\n    )\n\ninternal val AnonymousRoomNote.toNote: Note\n    get() = Note(\n            this.creationDate,\n            this.contents,\n            this.upVotes,\n            this.imageUrl,\n            User(this.creatorId)\n    )\n\ninternal val RegisteredRoomTransaction.toTransaction: NoteTransaction\n    get() = NoteTransaction(\n            this.creationDate,\n            this.contents,\n            this.upVotes,\n            this.imageUrl,\n            User(this.creatorId),\n            this.transactionType.toTransactionType()\n    )\n\ninternal val NoteTransaction.toRegisteredRoomTransaction: RegisteredRoomTransaction\n    get() = RegisteredRoomTransaction(\n            this.creationDate,\n            this.contents,\n            this.upVotes,\n            this.imageUrl,\n            this.safeGetUid,\n            this.transactionType.value\n    )\n\ninternal val NoteTransaction.toNote: Note\n    get() = Note(\n            this.creationDate,\n            this.contents,\n            this.upVotes,\n            this.imageUrl,\n            User(this.safeGetUid)\n    )\n\ninternal fun String.toTransactionType(): TransactionType {\n    return if (this.equals(TransactionType.DELETE)) TransactionType.DELETE\n    else TransactionType.UPDATE\n}\n\ninternal val Note.toRegisteredRoomNote: RegisteredRoomNote\n    get() = RegisteredRoomNote(\n            this.creationDate,\n            this.contents,\n            this.upVotes,\n            this.imageUrl,\n            this.safeGetUid\n    )\n\ninternal val RegisteredRoomNote.toNote: Note\n    get() = Note(\n            this.creationDate,\n            this.contents,\n            this.upVotes,\n            this.imageUrl,\n            User(this.creatorId)\n    )\n\ninternal val Note.toFirebaseNote: FirebaseNote\n    get() = FirebaseNote(\n            this.creationDate,\n            this.contents,\n            this.upVotes,\n            this.imageUrl,\n            this.safeGetUid\n    )\n\n\ninternal val FirebaseNote.toNote: Note\n    get() = Note(\n            this.creationDate ?: \"\",\n            this.contents ?: \"\",\n            this.upVotes ?: 0,\n            this.imageurl ?: \"\",\n            User(this.creator ?: \"\")\n    )\n\n//Maps from lists of different Data Model types\ninternal fun List<AnonymousRoomNote>.toNoteListFromAnonymous(): List<Note> = this.flatMap {\n    listOf(it.toNote)\n}\n\ninternal fun List<RegisteredRoomNote>.toNoteListFromRegistered(): List<Note> = this.flatMap {\n    listOf(it.toNote)\n}\n\ninternal fun List<RegisteredRoomTransaction>.toNoteTransactionListFromRegistered(): List<NoteTransaction> = this.flatMap {\n    listOf(it.toTransaction)\n}\n"
  },
  {
    "path": "data/src/main/java/com/wiseassblog/data/auth/FirebaseAuthRepositoryImpl.kt",
    "content": "package com.wiseassblog.data.auth\n\nimport com.google.android.gms.tasks.Tasks\nimport com.google.firebase.auth.FirebaseAuth\nimport com.google.firebase.auth.GoogleAuthProvider\nimport com.wiseassblog.data.awaitTaskCompletable\nimport com.wiseassblog.data.defaultIfEmpty\nimport com.wiseassblog.domain.domainmodel.Result\nimport com.wiseassblog.domain.domainmodel.User\nimport com.wiseassblog.domain.error.SpaceNotesError\nimport com.wiseassblog.domain.repository.IAuthRepository\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\n\nclass FirebaseAuthRepositoryImpl(val auth: FirebaseAuth = FirebaseAuth.getInstance()) : IAuthRepository {\n\n    override suspend fun createGoogleUser(idToken: String):\n            Result<Exception, Unit> = withContext(Dispatchers.IO) {\n        try {\n            val credential = GoogleAuthProvider.getCredential(idToken, null)\n            awaitTaskCompletable(auth.signInWithCredential(credential))\n\n            Tasks.await(auth.signInWithCredential(credential))\n\n            Result.build { Unit }\n        } catch (e: Exception) {\n            Result.build { throw e }\n        }\n    }\n\n\n    override suspend fun signOutCurrentUser(): Result<Exception, Unit> {\n        return Result.build {\n            auth.signOut()\n        }\n    }\n\n    override suspend fun deleteCurrentUser(): Result<Exception, Boolean> {\n        return try {\n            val user = auth.currentUser ?: throw SpaceNotesError.AuthError\n\n            awaitTaskCompletable(user.delete())\n            Result.build { true }\n        } catch (exception: Exception) {\n            Result.build { throw exception }\n        }\n    }\n\n    override suspend fun getCurrentUser(): Result<Exception, User?> {\n        val firebaseUser = auth.currentUser\n\n        if (firebaseUser == null) return Result.build { null }\n        else return Result.build {\n            User(\n                    firebaseUser.uid,\n                    firebaseUser.displayName ?: \"\",\n                    firebaseUser.photoUrl.defaultIfEmpty\n            )\n        }\n    }\n}"
  },
  {
    "path": "data/src/main/java/com/wiseassblog/data/datamodels/AnonymousRoomNote.kt",
    "content": "package com.wiseassblog.data.datamodels\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\n\n//If you're Data Models for a given API require API specific code, then create a separate Data\n//Model instead of polluting your domain with platform specific APIs.\n@Entity(\n        tableName = \"anonymous_notes\",\n        indices = [Index(\"creation_date\")]\n)\ndata class AnonymousRoomNote(\n\n        @PrimaryKey\n        @ColumnInfo(name = \"creation_date\")\n        val creationDate: String,\n\n        @ColumnInfo(name = \"contents\")\n        val contents: String,\n\n        @ColumnInfo(name = \"up_votes\")\n        val upVotes: Int,\n\n        @ColumnInfo(name = \"image_url\")\n        val imageUrl: String,\n\n        @ColumnInfo(name = \"creator_id\")\n        val creatorId: String\n)"
  },
  {
    "path": "data/src/main/java/com/wiseassblog/data/datamodels/FirebaseNote.kt",
    "content": "package com.wiseassblog.data.datamodels\n\n//var and default arguments used due to firestore requiring a no argument constructor to\n//deserialize\ndata class FirebaseNote(\n        var creationDate: String? = \"\",\n        var contents: String? = \"\",\n        var upVotes: Int? = 0,\n        var imageurl: String? = \"\",\n        var creator: String? = \"\"\n)"
  },
  {
    "path": "data/src/main/java/com/wiseassblog/data/datamodels/RegisteredRoomNote.kt",
    "content": "package com.wiseassblog.data.datamodels\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\n\n//If you're Data Models for a given API require API specific code, then create a separate Data\n//Model instead of polluting your domain with platform specific APIs.\n@Entity(\n        tableName = \"registered_notes\",\n        indices = [Index(\"creation_date\")]\n)\ndata class RegisteredRoomNote(\n\n        @PrimaryKey\n        @ColumnInfo(name = \"creation_date\")\n        val creationDate: String,\n\n        @ColumnInfo(name = \"contents\")\n        val contents: String,\n\n        @ColumnInfo(name = \"up_votes\")\n        val upVotes: Int,\n\n        @ColumnInfo(name = \"image_url\")\n        val imageUrl: String,\n\n        @ColumnInfo(name = \"creator_id\")\n        val creatorId: String\n)"
  },
  {
    "path": "data/src/main/java/com/wiseassblog/data/datamodels/RegisteredRoomTransaction.kt",
    "content": "package com.wiseassblog.data.datamodels\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\n\n//If you're Data Models for a given API require API specific code, then create a separate Data\n//Model instead of polluting your domain with platform specific APIs.\n@Entity(\n        tableName = \"transactions\",\n        indices = [Index(\"creation_date\")]\n)\ndata class RegisteredRoomTransaction(\n\n        @PrimaryKey\n        @ColumnInfo(name = \"creation_date\")\n        val creationDate: String,\n\n        @ColumnInfo(name = \"contents\")\n        val contents: String,\n\n        @ColumnInfo(name = \"up_votes\")\n        val upVotes: Int,\n\n        @ColumnInfo(name = \"image_url\")\n        val imageUrl: String,\n\n        @ColumnInfo(name = \"creator_id\")\n        val creatorId: String,\n\n        @ColumnInfo(name = \"transaction_type\")\n        val transactionType: String\n)"
  },
  {
    "path": "data/src/main/java/com/wiseassblog/data/note/anonymous/AnonymousNoteDao.kt",
    "content": "package com.wiseassblog.data.note.anonymous\n\nimport androidx.room.*\nimport com.wiseassblog.data.datamodels.AnonymousRoomNote\n\n@Dao\ninterface AnonymousNoteDao {\n    @Query(\"SELECT * FROM anonymous_notes ORDER BY creation_date\")\n    fun getNotes(): List<AnonymousRoomNote>\n\n    @Query(\"SELECT * FROM anonymous_notes WHERE creation_date = :creationDate ORDER BY creation_date\")\n    fun getNoteById(creationDate: String): AnonymousRoomNote\n\n    @Delete\n    fun deleteNote(noteAnonymous: AnonymousRoomNote)\n\n    @Query(\"DELETE FROM anonymous_notes\")\n    fun deleteAll()\n\n    //if update successful, will return number of rows effected, which should be 1\n    @Insert(onConflict = OnConflictStrategy.REPLACE)\n    fun insertOrUpdateNote(noteAnonymous: AnonymousRoomNote): Long\n}"
  },
  {
    "path": "data/src/main/java/com/wiseassblog/data/note/anonymous/RoomAnonymousNoteDatabase.kt",
    "content": "package com.wiseassblog.data.note.anonymous\n\nimport android.content.Context\nimport androidx.room.Database\nimport androidx.room.Room\nimport androidx.room.RoomDatabase\nimport com.wiseassblog.data.datamodels.AnonymousRoomNote\n\nprivate const val DATABASE_ANON = \"anonymous\"\n\n@Database(entities = [AnonymousRoomNote::class],\n        version = 1,\n        exportSchema = false)\nabstract class AnonymousNoteDatabase : RoomDatabase(){\n    \n    abstract fun roomNoteDao(): AnonymousNoteDao\n\n    //code below courtesy of https://github.com/googlesamples/android-sunflower; it is open\n    //source just like this application.\n    companion object {\n\n        // For Singleton instantiation\n        @Volatile private var instance: AnonymousNoteDatabase? = null\n\n        fun getInstance(context: Context): AnonymousNoteDatabase {\n            return instance ?: synchronized(this) {\n                instance\n                        ?: buildDatabase(context).also { instance = it }\n            }\n        }\n\n        private fun buildDatabase(context: Context): AnonymousNoteDatabase {\n            return Room.databaseBuilder(context, AnonymousNoteDatabase::class.java, DATABASE_ANON)\n                    .build()\n        }\n    }\n}"
  },
  {
    "path": "data/src/main/java/com/wiseassblog/data/note/anonymous/RoomLocalAnonymousRepositoryImpl.kt",
    "content": "package com.wiseassblog.data.note.anonymous\n\nimport com.wiseassblog.data.toAnonymousRoomNote\nimport com.wiseassblog.data.toNote\nimport com.wiseassblog.data.toNoteListFromAnonymous\nimport com.wiseassblog.domain.domainmodel.Note\nimport com.wiseassblog.domain.domainmodel.Result\nimport com.wiseassblog.domain.error.SpaceNotesError\nimport com.wiseassblog.domain.repository.ILocalNoteRepository\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\n\nclass RoomLocalAnonymousRepositoryImpl(private val noteDao: AnonymousNoteDao) : ILocalNoteRepository {\n    //Not to be used\n    override suspend fun deleteAll(): Result<Exception, Unit> = Result.build { throw SpaceNotesError.LocalIOException }\n\n    //Not to be used\n    override suspend fun updateAll(list: List<Note>): Result<Exception, Unit> = Result.build { throw SpaceNotesError.LocalIOException }\n\n    override suspend fun updateNote(note: Note): Result<Exception, Unit> = withContext(Dispatchers.IO) {\n        val updated = noteDao.insertOrUpdateNote(note.toAnonymousRoomNote)\n\n        when {\n            //TODO verify that if nothing is updated, the resulting value will be 0\n            updated == 0L -> Result.build { throw SpaceNotesError.LocalIOException }\n            else -> Result.build { Unit }\n        }\n    }\n\n    override suspend fun getNote(id: String): Result<Exception, Note?> = withContext(Dispatchers.IO) { Result.build { noteDao.getNoteById(id).toNote } }\n\n\n    override suspend fun getNotes(): Result<Exception, List<Note>> = withContext(Dispatchers.IO) { Result.build { noteDao.getNotes().toNoteListFromAnonymous() } }\n\n\n    override suspend fun deleteNote(note: Note): Result<Exception, Unit> = withContext(Dispatchers.IO) {\n        noteDao.deleteNote(note.toAnonymousRoomNote)\n        Result.build { Unit }\n    }\n}"
  },
  {
    "path": "data/src/main/java/com/wiseassblog/data/note/public/FirestorePublicNoteRepositoryImpl.kt",
    "content": "package com.wiseassblog.data.note.public\n\nimport com.google.firebase.firestore.FirebaseFirestore\nimport com.google.firebase.firestore.QuerySnapshot\nimport com.wiseassblog.data.awaitTaskCompletable\nimport com.wiseassblog.data.awaitTaskResult\nimport com.wiseassblog.data.datamodels.FirebaseNote\nimport com.wiseassblog.data.toFirebaseNote\nimport com.wiseassblog.data.toNote\nimport com.wiseassblog.domain.domainmodel.Note\nimport com.wiseassblog.domain.domainmodel.Result\nimport com.wiseassblog.domain.repository.IPublicNoteRepository\n\n\nconst val COLLECTION_PUBLIC = \"public_notes\"\nobject FirestoreRemoteNoteImpl : IPublicNoteRepository {\n    override suspend fun getNotes(): Result<Exception, List<Note>> {\n        val firestore = FirebaseFirestore.getInstance()\n\n        var reference = firestore.collection(COLLECTION_PUBLIC)\n\n        return try {\n            val task = awaitTaskResult(reference.get())\n\n            return resultToNoteList(task)\n        } catch (exception: Exception) {\n            Result.build { throw exception }\n        }\n    }\n\n    override suspend fun getNote(id: String): Result<Exception, Note?> {\n        val firestore = FirebaseFirestore.getInstance()\n\n        var reference = firestore.collection(COLLECTION_PUBLIC)\n                .document(id)\n\n        return try {\n            val task = awaitTaskResult(reference.get())\n\n            Result.build {\n                task.toObject(FirebaseNote::class.java)?.toNote\n            }\n        } catch (exception: Exception) {\n            Result.build { throw exception }\n        }\n    }\n\n    override suspend fun deleteNote(note: Note): Result<Exception, Unit> {\n        val firestore = FirebaseFirestore.getInstance()\n\n        return try {\n            awaitTaskCompletable(firestore.collection(COLLECTION_PUBLIC)\n                    .document(note.creationDate)\n                    .delete()\n            )\n\n            Result.build { Unit }\n\n        } catch (exception: Exception) {\n            Result.build { throw exception }\n        }\n    }\n\n    override suspend fun updateNote(note: Note): Result<Exception, Unit> {\n        val firestore = FirebaseFirestore.getInstance()\n\n        return try {\n            awaitTaskCompletable(firestore.collection(COLLECTION_PUBLIC)\n                    .document(note.creationDate)\n                    .set(note.toFirebaseNote)\n            )\n\n            Result.build { Unit }\n\n        } catch (exception: Exception) {\n            Result.build { throw exception }\n        }\n    }\n\n    private fun resultToNoteList(result: QuerySnapshot?): Result<Exception, List<Note>> {\n        val noteList = mutableListOf<Note>()\n\n        result?.forEach { documentSnapshop ->\n            noteList.add(documentSnapshop.toObject(FirebaseNote::class.java).toNote)\n        }\n\n        return Result.build {\n            noteList\n        }\n    }\n}"
  },
  {
    "path": "data/src/main/java/com/wiseassblog/data/note/registered/FirestorePrivateRemoteNoteImpl.kt",
    "content": "package com.wiseassblog.data.note.registered\n\nimport com.google.firebase.firestore.FirebaseFirestore\nimport com.google.firebase.firestore.QuerySnapshot\nimport com.wiseassblog.data.awaitTaskCompletable\nimport com.wiseassblog.data.awaitTaskResult\nimport com.wiseassblog.data.datamodels.FirebaseNote\nimport com.wiseassblog.data.toFirebaseNote\nimport com.wiseassblog.data.toNote\nimport com.wiseassblog.domain.domainmodel.Note\nimport com.wiseassblog.domain.domainmodel.NoteTransaction\nimport com.wiseassblog.domain.domainmodel.Result\nimport com.wiseassblog.domain.repository.IRemoteNoteRepository\n\nprivate const val COLLECTION_NAME = \"notes\"\n\nclass FirestorePrivateRemoteNoteImpl(\n        val firestore: FirebaseFirestore = FirebaseFirestore.getInstance()\n) : IRemoteNoteRepository {\n\n    //Currently handled in RegisteredNoteRepositoryImpl\n    override suspend fun synchronizeTransactions(transactions: List<NoteTransaction>): Result<Exception, Unit> = Result.build { Unit }\n\n\n    override suspend fun getNotes(): Result<Exception, List<Note>> {\n        var reference = firestore.collection(COLLECTION_NAME)\n\n        return try {\n            val task = awaitTaskResult(reference.get())\n\n            return resultToNoteList(task)\n        } catch (exception: Exception) {\n            Result.build { throw exception }\n        }\n    }\n\n    private fun resultToNoteList(result: QuerySnapshot?): Result<Exception, List<Note>> {\n        val noteList = mutableListOf<Note>()\n\n        result?.forEach { documentSnapshop ->\n            noteList.add(documentSnapshop.toObject(FirebaseNote::class.java).toNote)\n        }\n\n        return Result.build {\n            noteList\n        }\n    }\n\n\n    override suspend fun getNote(id: String): Result<Exception, Note?> {\n        var reference = firestore.collection(COLLECTION_NAME)\n                .document(id)\n\n        return try {\n            val task = awaitTaskResult(reference.get())\n\n            Result.build {\n                task.toObject(FirebaseNote::class.java)?.toNote\n            }\n        } catch (exception: Exception) {\n            Result.build { throw exception }\n        }\n\n    }\n\n    override suspend fun deleteNote(note: Note): Result<Exception, Unit> {\n        return try {\n            awaitTaskCompletable(firestore.collection(COLLECTION_NAME)\n                    .document(note.creationDate)\n                    .delete()\n            )\n\n            Result.build { Unit }\n\n        } catch (exception: Exception) {\n            Result.build { throw exception }\n        }\n    }\n\n    override suspend fun updateNote(note: Note): Result<Exception, Unit> {\n        return try {\n            awaitTaskCompletable(firestore.collection(COLLECTION_NAME)\n                    .document(note.creationDate)\n                    .set(note.toFirebaseNote)\n            )\n\n            Result.build { Unit }\n\n        } catch (exception: Exception) {\n            Result.build { throw exception }\n        }\n    }\n}"
  },
  {
    "path": "data/src/main/java/com/wiseassblog/data/note/registered/RegisteredNoteDao.kt",
    "content": "package com.wiseassblog.data.note.registered\n\nimport androidx.room.*\nimport com.wiseassblog.data.datamodels.RegisteredRoomNote\n\n@Dao\ninterface RegisteredNoteDao {\n    @Query(\"SELECT * FROM registered_notes ORDER BY creation_date\")\n    fun getNotes(): List<RegisteredRoomNote>\n\n    @Query(\"SELECT * FROM registered_notes WHERE creation_date = :creationDate ORDER BY creation_date\")\n    fun getNoteById(creationDate: String): RegisteredRoomNote\n\n    @Delete\n    fun deleteNote(note: RegisteredRoomNote)\n\n    @Query(\"DELETE FROM registered_notes\")\n    fun deleteAll()\n\n    //if update successful, will return number of rows effected, which should be 1\n    @Insert(onConflict = OnConflictStrategy.REPLACE)\n    fun insertOrUpdateNote(note: RegisteredRoomNote): Long\n}"
  },
  {
    "path": "data/src/main/java/com/wiseassblog/data/note/registered/RegisteredNoteRepositoryImpl.kt",
    "content": "package com.wiseassblog.data.note.registered\n\nimport com.wiseassblog.data.toNote\nimport com.wiseassblog.domain.domainmodel.Note\nimport com.wiseassblog.domain.domainmodel.NoteTransaction\nimport com.wiseassblog.domain.domainmodel.Result\nimport com.wiseassblog.domain.domainmodel.TransactionType\nimport com.wiseassblog.domain.error.SpaceNotesError\nimport com.wiseassblog.domain.repository.ILocalNoteRepository\nimport com.wiseassblog.domain.repository.IRemoteNoteRepository\n\nclass RegisteredNoteRepositoryImpl(val remote: IRemoteNoteRepository,\n                                   val cache: ILocalNoteRepository) : IRemoteNoteRepository {\n    /**\n     * Since n number of transactions may need to be pushed to the remote, and may not all\n     * be successful, it's rather tricky to return a specific result. I figured that the next\n     * best thing would be to return an error if any of them fail, to at least inform the\n     * user that something didn't go as planned.\n     */\n    override suspend fun synchronizeTransactions(transactions: List<NoteTransaction>): Result<Exception, Unit> {\n\n        //track results\n        val resultList = mutableListOf<Result<Exception, Unit>>()\n\n        transactions.forEach {\n            if (it.transactionType == TransactionType.UPDATE) remote.updateNote(it.toNote)\n                    .also { updateResult ->\n                        resultList.add(updateResult)\n                    }\n            else remote.deleteNote(it.toNote).also { deleteResult ->\n                resultList.add(deleteResult)\n            }\n        }\n\n        var successful = true\n\n        //if any result was an error, throw a generic error\n        resultList.forEach {\n            if (it is Result.Error) successful = false\n        }\n\n        if (successful) return Result.build { Unit }\n        else return Result.build { throw SpaceNotesError.RemoteIOException }\n\n    }\n\n\n    override suspend fun getNotes(): Result<Exception, List<Note>> {\n        val remoteResult = remote.getNotes()\n\n        when (remoteResult) {\n            is Result.Value -> {\n                cache.deleteAll()\n                cache.updateAll(remoteResult.value)\n            }\n\n            is Result.Error -> {\n                return cache.getNotes()\n            }\n        }\n\n        return remoteResult\n    }\n\n    override suspend fun getNote(id: String): Result<Exception, Note?> {\n        val remoteResult = remote.getNote(id)\n\n        return if (remoteResult is Result.Error) cache.getNote(id)\n        else remoteResult\n    }\n\n    override suspend fun deleteNote(note: Note): Result<Exception, Unit> = remote.deleteNote(\n            note\n    )\n\n\n    override suspend fun updateNote(note: Note): Result<Exception, Unit> = remote.updateNote(\n            note\n    )\n}"
  },
  {
    "path": "data/src/main/java/com/wiseassblog/data/note/registered/RegisteredTransactionDao.kt",
    "content": "package com.wiseassblog.data.note.registered\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport com.wiseassblog.data.datamodels.RegisteredRoomTransaction\n\n@Dao\ninterface RegisteredTransactionDao {\n    @Query(\"SELECT * FROM transactions ORDER BY creation_date\")\n    fun getTransactions(): List<RegisteredRoomTransaction>\n\n    @Query(\"DELETE FROM transactions\")\n    fun deleteAll()\n\n    //if update successful, will return number of rows effected, which should be 1\n    @Insert(onConflict = OnConflictStrategy.REPLACE)\n    fun insertOrUpdateTransaction(transaction: RegisteredRoomTransaction): Long\n}"
  },
  {
    "path": "data/src/main/java/com/wiseassblog/data/note/registered/RoomLocalCacheImpl.kt",
    "content": "package com.wiseassblog.data.note.registered\n\nimport com.wiseassblog.data.toNote\nimport com.wiseassblog.data.toNoteListFromRegistered\nimport com.wiseassblog.data.toRegisteredRoomNote\nimport com.wiseassblog.domain.domainmodel.Note\nimport com.wiseassblog.domain.domainmodel.Result\nimport com.wiseassblog.domain.repository.ILocalNoteRepository\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\n\n\n/**\n * This datasource is used by the RegisteredNoteRepository\n */\nclass RoomLocalCacheImpl(private val noteDao: RegisteredNoteDao) : ILocalNoteRepository {\n    override suspend fun deleteAll(): Result<Exception, Unit> = withContext(Dispatchers.IO) {\n        noteDao.deleteAll()\n\n        Result.build { Unit }\n    }\n\n    override suspend fun updateAll(list: List<Note>): Result<Exception, Unit> = withContext(Dispatchers.IO) {\n        list.forEach {\n            noteDao.insertOrUpdateNote(it.toRegisteredRoomNote)\n        }\n\n        Result.build { Unit }\n    }\n\n    override suspend fun updateNote(note: Note): Result<Exception, Unit> = withContext(Dispatchers.IO) {\n        noteDao.insertOrUpdateNote(note.toRegisteredRoomNote)\n\n        Result.build { Unit }\n    }\n\n    override suspend fun getNote(id: String): Result<Exception, Note?> = withContext(Dispatchers.IO) {\n        Result.build { noteDao.getNoteById(id).toNote }\n    }\n\n\n    override suspend fun getNotes(): Result<Exception, List<Note>> = withContext(Dispatchers.IO) {\n        Result.build { noteDao.getNotes().toNoteListFromRegistered() }\n    }\n\n\n    override suspend fun deleteNote(note: Note): Result<Exception, Unit> = withContext(Dispatchers.IO) {\n        noteDao.deleteNote(note.toRegisteredRoomNote)\n        Result.build { Unit }\n    }\n}\n\n\n"
  },
  {
    "path": "data/src/main/java/com/wiseassblog/data/note/registered/RoomRegisteredNoteDatabase.kt",
    "content": "package com.wiseassblog.data.note.registered\n\nimport android.content.Context\nimport androidx.room.Database\nimport androidx.room.Room\nimport androidx.room.RoomDatabase\nimport com.wiseassblog.data.datamodels.RegisteredRoomNote\n\nprivate const val DATABASE_REG = \"registered\"\n\n/**\n * This database is used as a \"cache\" for registered users.\n */\n@Database(entities = [RegisteredRoomNote::class],\n        version = 1,\n        exportSchema = false)\nabstract class RegisteredNoteDatabase : RoomDatabase(){\n    \n    abstract fun roomNoteDao(): RegisteredNoteDao\n\n    //code below courtesy of https://github.com/googlesamples/android-sunflower; it is open\n    //source just like this application.\n    companion object {\n\n        // For Singleton instantiation\n        @Volatile private var instance: RegisteredNoteDatabase? = null\n\n        fun getInstance(context: Context): RegisteredNoteDatabase {\n            return instance ?: synchronized(this) {\n                instance\n                        ?: buildDatabase(context).also { instance = it }\n            }\n        }\n\n        private fun buildDatabase(context: Context): RegisteredNoteDatabase {\n            return Room.databaseBuilder(context, RegisteredNoteDatabase::class.java, DATABASE_REG)\n                    .build()\n        }\n    }\n}"
  },
  {
    "path": "data/src/main/java/com/wiseassblog/data/transaction/RoomRegisteredTransactionDatabase.kt",
    "content": "package com.wiseassblog.data.transaction\n\nimport android.content.Context\nimport androidx.room.Database\nimport androidx.room.Room\nimport androidx.room.RoomDatabase\nimport com.wiseassblog.data.datamodels.RegisteredRoomTransaction\nimport com.wiseassblog.data.note.registered.RegisteredTransactionDao\n\nprivate const val DATABASE_TRANSACTION = \"transactions\"\n\n/**\n * This database is used as a \"cache\" for registered users.\n */\n@Database(entities = [RegisteredRoomTransaction::class],\n        version = 1,\n        exportSchema = false)\nabstract class RoomRegisteredTransactionDatabase : RoomDatabase() {\n\n    abstract fun roomTransactionDao(): RegisteredTransactionDao\n\n    //code below courtesy of https://github.com/googlesamples/android-sunflower; it is open\n    //source just like this application.\n    companion object {\n\n        // For Singleton instantiation\n        @Volatile\n        private var instance: RoomRegisteredTransactionDatabase? = null\n\n        fun getInstance(context: Context): RoomRegisteredTransactionDatabase {\n            return instance\n                    ?: synchronized(this) {\n                instance\n                        ?: buildDatabase(context).also { instance = it }\n            }\n        }\n\n        private fun buildDatabase(context: Context): RoomRegisteredTransactionDatabase {\n            return Room.databaseBuilder(context, RoomRegisteredTransactionDatabase::class.java, DATABASE_TRANSACTION)\n                    .build()\n        }\n    }\n}"
  },
  {
    "path": "data/src/main/java/com/wiseassblog/data/transaction/RoomTransactionRepositoryImpl.kt",
    "content": "package com.wiseassblog.data.transaction\n\nimport com.wiseassblog.data.note.registered.RegisteredTransactionDao\nimport com.wiseassblog.data.toNoteTransactionListFromRegistered\nimport com.wiseassblog.data.toRegisteredRoomTransaction\nimport com.wiseassblog.domain.domainmodel.NoteTransaction\nimport com.wiseassblog.domain.domainmodel.Result\nimport com.wiseassblog.domain.repository.ITransactionRepository\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.runBlocking\n\nclass RoomTransactionRepositoryImpl(val transactionDao: RegisteredTransactionDao) : ITransactionRepository {\n    override suspend fun getTransactions():\n            Result<Exception, List<NoteTransaction>> = runBlocking(IO) {\n        Result.build {\n            transactionDao.getTransactions().toNoteTransactionListFromRegistered()\n        }\n    }\n\n    override suspend fun deleteTransactions(): Result<Exception, Unit> = runBlocking(IO) {\n        Result.build {\n            transactionDao.deleteAll()\n        }\n    }\n\n    override suspend fun updateTransactions(transaction: NoteTransaction):\n            Result<Exception, Unit> = runBlocking(IO) {\n        Result.build {\n            transactionDao.insertOrUpdateTransaction(\n                    transaction.toRegisteredRoomTransaction\n            ).toUnit()\n        }\n    }\n\n    private fun Long.toUnit(): Unit = Unit\n}"
  },
  {
    "path": "data/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">data</string>\n</resources>\n"
  },
  {
    "path": "data/src/test/java/com/wiseassblog/data/ExtTest.kt",
    "content": "package com.wiseassblog.data\n\nimport com.wiseassblog.data.datamodels.AnonymousRoomNote\nimport com.wiseassblog.data.datamodels.RegisteredRoomNote\nimport com.wiseassblog.domain.domainmodel.Note\nimport com.wiseassblog.domain.domainmodel.User\nimport org.junit.Test\nimport kotlin.test.assertTrue\n\n\nclass ExtTest{\n    fun getNote(creationDate: String = \"28/10/2018\",\n                contents: String = \"When I understand that this glass is already broken, every moment with it becomes precious.\",\n                upVotes: Int = 0,\n                imageUrl: String = \"\",\n                creator: User? = User(\n                        \"8675309\",\n                        \"Ajahn Chah\",\n                        \"\"\n                )\n    ) = Note(\n            creationDate = creationDate,\n            contents = contents,\n            upVotes = upVotes,\n            imageUrl = imageUrl,\n            creator = creator\n    )\n\n\n    fun getAnonymousRoomNote(creationDate: String = \"28/10/2018\",\n                              contents: String = \"When I understand that this glass is already broken, every moment with it becomes precious.\",\n                              upVotes: Int = 0,\n                              imageUrl: String = \"\",\n                              creator: String = \"8675309\"\n    ) = AnonymousRoomNote(\n            creationDate = creationDate,\n            contents = contents,\n            upVotes = upVotes,\n            imageUrl = imageUrl,\n            creatorId = creator\n    )\n\n    fun getRegisteredRoomNote(creationDate: String = \"28/10/2018\",\n                             contents: String = \"When I understand that this glass is already broken, every moment with it becomes precious.\",\n                             upVotes: Int = 0,\n                             imageUrl: String = \"\",\n                             creator: String = \"8675309\"\n    ) = RegisteredRoomNote(\n            creationDate = creationDate,\n            contents = contents,\n            upVotes = upVotes,\n            imageUrl = imageUrl,\n            creatorId = creator\n    )\n\n\n    @Test\n    fun testExtensionFlatMap(){\n        val roomNoteList = listOf<AnonymousRoomNote>(getAnonymousRoomNote(), getAnonymousRoomNote(), getAnonymousRoomNote(contents = \"third\"))\n\n        val result = roomNoteList.toNoteListFromAnonymous()\n\n        assertTrue { result.contains(getAnonymousRoomNote().toNote) }\n\n    }\n}\n\n\n"
  },
  {
    "path": "data/src/test/java/com/wiseassblog/data/RegisteredNoteRepositoryTest.kt",
    "content": "package com.wiseassblog.data\n\nimport com.wiseassblog.data.note.registered.RegisteredNoteRepositoryImpl\nimport com.wiseassblog.domain.DispatcherProvider\nimport com.wiseassblog.domain.domainmodel.*\nimport com.wiseassblog.domain.error.SpaceNotesError\nimport com.wiseassblog.domain.repository.ILocalNoteRepository\nimport com.wiseassblog.domain.repository.IRemoteNoteRepository\nimport io.mockk.*\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.runBlocking\nimport org.junit.jupiter.api.AfterEach\nimport org.junit.jupiter.api.BeforeEach\nimport org.junit.jupiter.api.Test\nimport kotlin.test.assertEquals\nimport kotlin.test.assertTrue\n\nclass RegisteredNoteRepositoryTest {\n\n    val dispatcher: DispatcherProvider = mockk()\n\n    val cache: ILocalNoteRepository = mockk()\n\n    val remote: IRemoteNoteRepository = mockk()\n\n    val repo = RegisteredNoteRepositoryImpl(remote, cache)\n\n    fun getNote(creationDate: String = \"28/10/2018\",\n                contents: String = \"When I understand that this glass is already broken, every moment with it becomes precious.\",\n                upVotes: Int = 0,\n                imageUrl: String = \"\",\n                creator: User? = User(\n                        \"8675309\",\n                        \"Ajahn Chah\",\n                        \"\"\n                )\n    ) = Note(\n            creationDate = creationDate,\n            contents = contents,\n            upVotes = upVotes,\n            imageUrl = imageUrl,\n            creator = creator\n    )\n\n    fun getTransaction(\n            creationDate: String = \"28/10/2018\",\n            contents: String = \"When I understand that this glass is already broken, every moment with it becomes precious.\",\n            upVotes: Int = 0,\n            imageUrl: String = \"\",\n            creator: User? = User(\n                    \"8675309\",\n                    \"Ajahn Chah\",\n                    \"\"\n            ),\n            transactionType: TransactionType = TransactionType.UPDATE\n    ) = NoteTransaction(\n            creationDate = creationDate,\n            contents = contents,\n            upVotes = upVotes,\n            imageUrl = imageUrl,\n            creator = creator,\n            transactionType = transactionType\n    )\n\n    @BeforeEach\n    fun setUpRedundantMocks() {\n        clearAllMocks()\n        every { dispatcher.provideIOContext() } returns Dispatchers.Unconfined\n\n    }\n\n    /**\n     * On get notes, we first request Notes from the Remote. Data is returned either from the remote\n     * or local based on that result.\n     * a: Success\n     * b: Error\n     *\n     * a:\n     * 1. Request Data from Remote: Success\n     * 2. Update the Local repository\n     * 3. Return data from Remote\n     */\n    @Test\n    fun `Get Notes Success`() = runBlocking {\n        val testList = listOf(getNote(), getNote(), getNote())\n\n        coEvery { remote.getNotes() } returns Result.build { testList }\n\n        coEvery { cache.updateAll(testList) } returns Result.build { Unit }\n        coEvery { cache.deleteAll() } returns Result.build { Unit }\n\n        val result = repo.getNotes()\n\n        coVerify { remote.getNotes() }\n        coVerify { cache.deleteAll() }\n        coVerify { cache.updateAll(testList) }\n\n        if (result is Result.Value) assertEquals(result.value, testList)\n        else assertTrue { false }\n    }\n\n    /**\n     * b:\n     * 1. Request Data from Remote: Error\n     * 2. Return Data from Local\n     */\n    @Test\n    fun `Get Notes Fail`() = runBlocking {\n        val testNote = getNote()\n        val testList = listOf(getNote(), getNote(), getNote())\n\n        coEvery { remote.getNotes() } returns Result.build { throw SpaceNotesError.RemoteIOException }\n\n        coEvery { cache.getNotes() } returns Result.build { testList }\n\n        val result = repo.getNotes()\n\n        coVerify { remote.getNotes() }\n        coVerify { cache.getNotes() }\n\n        if (result is Result.Value) assertEquals(result.value, testList)\n        else assertTrue { false }\n    }\n\n    /**\n     * On get note, we first request Notes from the Remote. Data is returned either from the remote\n     * or local based on that result.\n     * a: Success\n     * b: Fail\n     *\n     * a:\n     * 1. Request Data from Remote: Success\n     * 2. Return data from Remote\n     */\n    @Test\n    fun `Get Note Success`() = runBlocking {\n        val testNote = getNote()\n\n        coEvery { remote.getNote(testNote.creationDate) } returns Result.build { testNote }\n\n        val result = repo.getNote(testNote.creationDate)\n\n        coVerify { remote.getNote(testNote.creationDate) }\n\n        if (result is Result.Value) assertEquals(result.value, testNote)\n        else assertTrue { false }\n    }\n\n    /**\n     * b:\n     * 1. Request Data from Remote: Fail\n     * 2. Return Data from Local\n     */\n    @Test\n    fun `Get Note Fail`() = runBlocking {\n        val testNote = getNote()\n\n        coEvery { remote.getNote(testNote.creationDate) } returns Result.build {\n            throw SpaceNotesError.RemoteIOException\n        }\n        coEvery { cache.getNote(testNote.creationDate) } returns Result.build { testNote }\n\n        val result = repo.getNote(testNote.creationDate)\n\n        coVerify { remote.getNote(testNote.creationDate) }\n        coVerify { cache.getNote(testNote.creationDate) }\n\n        if (result is Result.Value) assertEquals(result.value, testNote)\n        else assertTrue { false }\n    }\n\n    /**\n     * On delete note:\n     * a: Success\n     * b: Fail\n     *\n     * a:\n     * 1. Delete Data from Remote: Success\n     * 2. Return: Success\n     */\n    @Test\n    fun `Delete Note Success`() = runBlocking {\n        val testNote = getNote()\n\n        coEvery { remote.deleteNote(testNote) } returns Result.build {\n            Unit\n        }\n\n        val result = repo.deleteNote(testNote)\n\n        coVerify { remote.deleteNote(testNote) }\n\n        assertTrue { result is Result.Value }\n    }\n\n    /**\n     * b:\n     * 1. Delete Data from Remote: Fail\n     * 2. Return: Error\n     */\n    @Test\n    fun `Delete Note Fail`() = runBlocking {\n        val testNote = getNote()\n\n        coEvery { remote.deleteNote(testNote) } returns Result.build {\n            throw SpaceNotesError.RemoteIOException\n        }\n\n        val result = repo.deleteNote(testNote)\n\n        coVerify { remote.deleteNote(testNote) }\n\n        assertTrue { result is Result.Error }\n    }\n\n    /**\n     * On delete note:\n     * a: Success\n     * b: Fail\n     *\n     * a:\n     * 1. Update Data from Remote: Success\n     * 2. Return: Success\n     */\n    @Test\n    fun `Update Note Success`() = runBlocking {\n        val testNote = getNote()\n\n        coEvery { remote.updateNote(testNote) } returns Result.build {\n            Unit\n        }\n\n        val result = repo.updateNote(testNote)\n\n        coVerify { remote.updateNote(testNote) }\n\n        assertTrue { result is Result.Value }\n    }\n\n    /**\n     * b:\n     * 1. Update Data from Remote: Fail\n     * 2. Return: Error\n     */\n    @Test\n    fun `Update Note Fail`() = runBlocking {\n        val testNote = getNote()\n\n        coEvery { remote.updateNote(testNote) } returns Result.build {\n            throw SpaceNotesError.RemoteIOException\n        }\n\n        val result = repo.updateNote(testNote)\n\n        coVerify { remote.updateNote(testNote) }\n\n        assertTrue { result is Result.Error }\n    }\n\n    /**\n     * On Synchronize Transactions, we want to map transactions to Note objects, and push them\n     * all to the Remote Repo:\n     * a: Success\n     * b: Fail\n     *\n     * a:\n     * 1. Pass Data to Remote: Success\n     * 2. Return: Unit\n     */\n    @Test\n    fun `Synchronize Transactions Success`() = runBlocking {\n        val updateTransaction = getTransaction()\n        val deleteTransaction = getTransaction(transactionType = TransactionType.DELETE)\n        val testList = listOf(updateTransaction, deleteTransaction)\n\n        coEvery { remote.updateNote(updateTransaction.toNote) } returns Result.build {\n            Unit\n        }\n\n        coEvery { remote.deleteNote(deleteTransaction.toNote) } returns Result.build {\n            Unit\n        }\n\n        val result = repo.synchronizeTransactions(testList)\n\n        coVerify { remote.updateNote(updateTransaction.toNote) }\n        coVerify { remote.deleteNote(deleteTransaction.toNote) }\n\n        assertTrue { result is Result.Value }\n    }\n\n    /**\n     * b:\n     * 1. Pass Data from Remote: Success (once), Fail (once)\n     * 2. Return: Error\n     */\n    @Test\n    fun `Synchronize Transactions Fail`() = runBlocking {\n        val updateTransaction = getTransaction()\n        val deleteTransaction = getTransaction(transactionType = TransactionType.DELETE)\n        val testList = listOf(updateTransaction, deleteTransaction)\n\n        coEvery { remote.updateNote(updateTransaction.toNote) } returns Result.build {\n            Unit\n        }\n\n        coEvery { remote.deleteNote(deleteTransaction.toNote) } returns Result.build {\n            throw SpaceNotesError.RemoteIOException\n        }\n\n        val result = repo.synchronizeTransactions(testList)\n\n        coVerify { remote.updateNote(updateTransaction.toNote) }\n        coVerify { remote.deleteNote(deleteTransaction.toNote) }\n\n        assertTrue { result is Result.Error }\n    }\n\n    @AfterEach\n    fun confirm() {\n        confirmVerified(\n            dispatcher,\n            cache,\n            remote\n        )\n    }\n}"
  },
  {
    "path": "domain/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "domain/build.gradle",
    "content": "apply plugin: 'java-library'\napply plugin: 'kotlin'\n\ndependencies {\n    implementation deps.kotlin.coroutines_core\n\n    testImplementation deps.test.junit\n    testImplementation deps.test.mockk\n    testImplementation deps.test.kotlin_junit\n}\n\nsourceCompatibility = \"1.8\"\ntargetCompatibility = \"1.8\"\n"
  },
  {
    "path": "domain/src/main/java/com/wiseassblog/domain/DispatcherProvider.kt",
    "content": "package com.wiseassblog.domain\n\nimport kotlinx.coroutines.Dispatchers\nimport kotlin.coroutines.CoroutineContext\n\nobject DispatcherProvider {\n    fun provideUIContext(): CoroutineContext {\n        return Dispatchers.Main\n    }\n\n    fun provideIOContext(): CoroutineContext {\n        return Dispatchers.IO\n    }\n}"
  },
  {
    "path": "domain/src/main/java/com/wiseassblog/domain/domainmodel/Note.kt",
    "content": "package com.wiseassblog.domain.domainmodel\n\n\ndata class Note(val creationDate:String,\n                val contents:String,\n                val upVotes: Int,\n                //why String? some times it will be Int from Android Resources, or URL String\n                val imageUrl: String,\n                val creator: User?)\n\n"
  },
  {
    "path": "domain/src/main/java/com/wiseassblog/domain/domainmodel/NoteTransaction.kt",
    "content": "package com.wiseassblog.domain.domainmodel\n\ndata class NoteTransaction(\n        val creationDate:String,\n        val contents:String,\n        val upVotes: Int,\n        val imageUrl: String,\n        val creator: User?,\n        val transactionType: TransactionType\n)\n\nenum class TransactionType(val value: String) {\n    UPDATE( \"update\"),\n    DELETE( \"delete\"),\n}\n\nfun Note.toTransaction(type: TransactionType): NoteTransaction = NoteTransaction(\n        creationDate,\n        contents,\n        upVotes,\n        imageUrl,\n        creator,\n        type\n)\n"
  },
  {
    "path": "domain/src/main/java/com/wiseassblog/domain/domainmodel/Result.kt",
    "content": "package com.wiseassblog.domain.domainmodel\n\n/**\n * Result Wrapper <Left = Exception, Right = Value/Success>\n */\nsealed class Result<out E, out V> {\n\n    data class Value<out V>(val value: V) : Result<Nothing, V>()\n    object Loading : Result<Nothing, Nothing>()\n    data class Error<out E>(val error: E) : Result<E, Nothing>()\n\n    companion object Factory{\n        //higher order functions take functions as parameters or return a function\n        //Kotlin has function types name: () -> V\n        inline fun <V> build(function: () -> V): Result<Exception, V> =\n                try {\n                    Value(function.invoke())\n                } catch (e: java.lang.Exception) {\n                    Error(e)\n                }\n\n        fun buildLoading(): Result<Exception, Nothing> {\n            return Loading\n        }\n    }\n\n}"
  },
  {
    "path": "domain/src/main/java/com/wiseassblog/domain/domainmodel/User.kt",
    "content": "package com.wiseassblog.domain.domainmodel\n\ndata class User(val uid: String,\n                val name: String = \"\",\n                val profilePicUrl: String = \"satellite_beam\")"
  },
  {
    "path": "domain/src/main/java/com/wiseassblog/domain/error/SpaceNotesError.kt",
    "content": "package com.wiseassblog.domain.error\n\nimport java.lang.Exception\n\nsealed class SpaceNotesError: Exception() {\n\n    object LocalIOException: SpaceNotesError()\n    object RemoteIOException: SpaceNotesError()\n    object NetworkUnavailableException: SpaceNotesError()\n    object AuthError: SpaceNotesError()\n    object TransactionIOException : SpaceNotesError()\n\n\n\n}\n\nconst val ERROR_UPDATE_FAILED = \"Update operation unsuccessful.\"\nconst val ERROR_DELETE_FAILED = \"Delete operation unsuccessful.\"\n"
  },
  {
    "path": "domain/src/main/java/com/wiseassblog/domain/interactor/AnonymousNoteSource.kt",
    "content": "package com.wiseassblog.domain.interactor\n\nimport com.wiseassblog.domain.servicelocator.NoteServiceLocator\nimport com.wiseassblog.domain.domainmodel.Note\nimport com.wiseassblog.domain.domainmodel.Result\n\nclass AnonymousNoteSource {\n    suspend fun getNotes(locator: NoteServiceLocator):\n            Result<Exception, List<Note>> = locator.localAnon.getNotes()\n\n    suspend fun getNoteById(id: String, locator: NoteServiceLocator):\n            Result<Exception, Note?> = locator.localAnon.getNote(id)\n\n    suspend fun updateNote(note: Note, locator: NoteServiceLocator):\n            Result<Exception, Unit> = locator.localAnon.updateNote(note)\n\n\n    suspend fun deleteNote(note: Note, locator: NoteServiceLocator):\n            Result<Exception, Unit> = locator.localAnon.deleteNote(note)\n\n}"
  },
  {
    "path": "domain/src/main/java/com/wiseassblog/domain/interactor/AuthSource.kt",
    "content": "package com.wiseassblog.domain.interactor\n\nimport com.wiseassblog.domain.servicelocator.UserServiceLocator\nimport com.wiseassblog.domain.domainmodel.Result\nimport com.wiseassblog.domain.domainmodel.User\n\nclass AuthSource {\n\n    suspend fun getCurrentUser(locator: UserServiceLocator):\n            Result<Exception, User?> = locator.authRepository.getCurrentUser()\n\n    suspend fun deleteCurrentUser(locator: UserServiceLocator):\n            Result<Exception, Boolean> = locator.authRepository.deleteCurrentUser()\n\n    suspend fun signOutCurrentUser(locator: UserServiceLocator):\n            Result<Exception, Unit> = locator.authRepository.signOutCurrentUser()\n\n    suspend fun createGoogleUser(idToken: String, locator: UserServiceLocator):\n            Result<Exception, Unit> = locator.authRepository.createGoogleUser(idToken)\n\n}"
  },
  {
    "path": "domain/src/main/java/com/wiseassblog/domain/interactor/PublicNoteSource.kt",
    "content": "package com.wiseassblog.domain.interactor\n\nimport com.wiseassblog.domain.servicelocator.NoteServiceLocator\nimport com.wiseassblog.domain.domainmodel.Note\nimport com.wiseassblog.domain.domainmodel.Result\n\n\nclass PublicNoteSource {\n    suspend fun getNotes(locator: NoteServiceLocator): Result<Exception, List<Note>> = locator\n            .remotePublic\n            .getNotes()\n\n\n    suspend fun getNoteById(id: String,\n                            locator: NoteServiceLocator): Result<Exception, Note?> = locator\n            .remotePublic\n            .getNote(id)\n\n    suspend fun updateNote(note: Note,\n                           locator: NoteServiceLocator): Result<Exception, Unit> = locator\n            .remotePublic\n            .updateNote(note)\n\n    suspend fun deleteNote(note: Note,\n                           locator: NoteServiceLocator): Result<Exception, Unit> = locator\n            .remotePublic\n            .deleteNote(note)\n}\n"
  },
  {
    "path": "domain/src/main/java/com/wiseassblog/domain/interactor/RegisteredNoteSource.kt",
    "content": "package com.wiseassblog.domain.interactor\n\nimport com.wiseassblog.domain.servicelocator.NoteServiceLocator\nimport com.wiseassblog.domain.domainmodel.*\nimport com.wiseassblog.domain.repository.IRemoteNoteRepository\nimport com.wiseassblog.domain.repository.ITransactionRepository\n\n\nclass RegisteredNoteSource {\n    suspend fun getNotes(locator: NoteServiceLocator): Result<Exception, List<Note>> {\n\n        val transactionResult = locator.transactionReg.getTransactions()\n\n        when (transactionResult) {\n            is Result.Value -> {\n                //if items exist in transaction cache:\n                if (transactionResult.value.size != 0) synchronizeTransactionCache(\n                        transactionResult.value,\n                        locator.remoteReg,\n                        locator.transactionReg\n                )\n            }\n\n            is Result.Error -> {\n                //For now we'll just continue to ask remote for the latest data\n            }\n        }\n\n        return locator.remoteReg.getNotes()\n    }\n\n    private suspend fun synchronizeTransactionCache(\n            transactions: List<NoteTransaction>,\n            remoteReg: IRemoteNoteRepository,\n            transactionReg: ITransactionRepository) {\n\n        val synchronizationResult = remoteReg.synchronizeTransactions(transactions)\n\n        //if synchronization was successful, delete items from the transaction cache\n        when (synchronizationResult) {\n            is Result.Value -> transactionReg.deleteTransactions()\n            is Result.Error -> {\n                //\"Again, not necessarily a fatal error\"\n            }\n        }\n    }\n\n    suspend fun getNoteById(id: String,\n                            locator: NoteServiceLocator):\n            Result<Exception, Note?> = locator.remoteReg.getNote(id)\n\n    suspend fun updateNote(note: Note,\n                           locator: NoteServiceLocator): Result<Exception, Unit> {\n        val remoteResult = locator.remoteReg.updateNote(note)\n\n        if (remoteResult is Result.Value) return remoteResult\n        else return locator.transactionReg.updateTransactions(\n                note.toTransaction(TransactionType.UPDATE)\n        )\n    }\n\n    suspend fun deleteNote(note: Note,\n                           locator: NoteServiceLocator): Result<Exception, Unit> {\n        val remoteResult = locator.remoteReg.deleteNote(note)\n\n        if (remoteResult is Result.Value) return remoteResult\n        else return locator.transactionReg.updateTransactions(\n                note.toTransaction(TransactionType.DELETE)\n        )\n    }\n}\n"
  },
  {
    "path": "domain/src/main/java/com/wiseassblog/domain/repository/IAuthRepository.kt",
    "content": "package com.wiseassblog.domain.repository\n\nimport com.wiseassblog.domain.domainmodel.Result\nimport com.wiseassblog.domain.domainmodel.User\nimport kotlinx.coroutines.channels.SendChannel\n\n\ninterface IAuthRepository {\n\n\n//    suspend fun setAuthStateListener(channel: SendChannel<Result<Exception, User?>>): Result<Exception, Unit>\n\n    suspend fun getCurrentUser(): Result<Exception, User?>\n\n    suspend fun signOutCurrentUser(): Result<Exception, Unit>\n\n    suspend fun deleteCurrentUser(): Result<Exception, Boolean>\n\n    suspend fun createGoogleUser(idToken: String): Result<Exception, Unit>\n\n}"
  },
  {
    "path": "domain/src/main/java/com/wiseassblog/domain/repository/ILocalNoteRepository.kt",
    "content": "package com.wiseassblog.domain.repository\n\nimport com.wiseassblog.domain.domainmodel.Note\nimport com.wiseassblog.domain.domainmodel.Result\n\ninterface ILocalNoteRepository {\n    suspend fun getNotes(): Result<Exception, List<Note>>\n\n    suspend fun getNote(id: String): Result<Exception, Note?>\n\n    suspend fun deleteNote(note: Note): Result<Exception, Unit>\n\n    suspend fun deleteAll(): Result<Exception, Unit>\n\n    suspend fun updateAll(list: List<Note>): Result<Exception, Unit>\n\n    suspend fun updateNote(note: Note): Result<Exception, Unit>\n}"
  },
  {
    "path": "domain/src/main/java/com/wiseassblog/domain/repository/IPublicNoteRepository.kt",
    "content": "package com.wiseassblog.domain.repository\n\nimport com.wiseassblog.domain.domainmodel.Note\nimport com.wiseassblog.domain.domainmodel.NoteTransaction\nimport com.wiseassblog.domain.domainmodel.Result\nimport kotlinx.coroutines.channels.Channel\n\ninterface IPublicNoteRepository {\n    suspend fun getNotes():Result<Exception, List<Note>>\n\n    suspend fun getNote(id: String): Result<Exception, Note?>\n\n    suspend fun deleteNote(note: Note): Result<Exception, Unit>\n\n    suspend fun updateNote(note: Note):Result<Exception, Unit>\n}"
  },
  {
    "path": "domain/src/main/java/com/wiseassblog/domain/repository/IRemoteNoteRepository.kt",
    "content": "package com.wiseassblog.domain.repository\n\nimport com.wiseassblog.domain.domainmodel.Note\nimport com.wiseassblog.domain.domainmodel.NoteTransaction\nimport com.wiseassblog.domain.domainmodel.Result\nimport kotlinx.coroutines.channels.Channel\n\ninterface IRemoteNoteRepository {\n    suspend fun synchronizeTransactions(transactions: List<NoteTransaction>): Result<Exception, Unit>\n\n    suspend fun getNotes():Result<Exception, List<Note>>\n\n    suspend fun getNote(id: String): Result<Exception, Note?>\n\n    suspend fun deleteNote(note: Note): Result<Exception, Unit>\n\n    suspend fun updateNote(note: Note):Result<Exception, Unit>\n}"
  },
  {
    "path": "domain/src/main/java/com/wiseassblog/domain/repository/ITransactionRepository.kt",
    "content": "package com.wiseassblog.domain.repository\n\nimport com.wiseassblog.domain.domainmodel.Result\nimport com.wiseassblog.domain.domainmodel.NoteTransaction\n\ninterface ITransactionRepository {\n    suspend fun getTransactions():Result<Exception, List<NoteTransaction>>\n\n    suspend fun deleteTransactions(): Result<Exception, Unit>\n\n    suspend fun updateTransactions(transaction: NoteTransaction):Result<Exception, Unit>\n}"
  },
  {
    "path": "domain/src/main/java/com/wiseassblog/domain/servicelocator/NoteServiceLocator.kt",
    "content": "package com.wiseassblog.domain.servicelocator\n\nimport com.wiseassblog.domain.repository.ILocalNoteRepository\nimport com.wiseassblog.domain.repository.IPublicNoteRepository\nimport com.wiseassblog.domain.repository.IRemoteNoteRepository\nimport com.wiseassblog.domain.repository.ITransactionRepository\n\nclass NoteServiceLocator(val localAnon: ILocalNoteRepository,\n                         val remoteReg: IRemoteNoteRepository,\n                         val transactionReg: ITransactionRepository,\n                         val remotePublic: IPublicNoteRepository)"
  },
  {
    "path": "domain/src/main/java/com/wiseassblog/domain/servicelocator/UserServiceLocator.kt",
    "content": "package com.wiseassblog.domain.servicelocator\n\nimport com.wiseassblog.domain.repository.IAuthRepository\n\nclass UserServiceLocator(val authRepository: IAuthRepository)"
  },
  {
    "path": "domain/src/test/java/com/wiseassblog/domain/AnonymousNoteSourceTest.kt",
    "content": "package com.wiseassblog.domain\n\nimport com.wiseassblog.domain.domainmodel.Note\nimport com.wiseassblog.domain.domainmodel.Result\nimport com.wiseassblog.domain.domainmodel.User\nimport com.wiseassblog.domain.error.SpaceNotesError\nimport com.wiseassblog.domain.interactor.AnonymousNoteSource\nimport com.wiseassblog.domain.repository.ILocalNoteRepository\nimport com.wiseassblog.domain.servicelocator.NoteServiceLocator\nimport io.mockk.*\nimport kotlinx.coroutines.runBlocking\nimport org.junit.jupiter.api.AfterEach\nimport org.junit.jupiter.api.BeforeEach\nimport org.junit.jupiter.api.Test\nimport kotlin.test.assertEquals\nimport kotlin.test.assertTrue\n\n/**\n * Anonymous Note Source is for users that have not authenticated any social media accounts (such as\n * via Google Sign In)\n * Anonymous users have access to:\n * - A local Repository; nothing else.\n */\n\nclass AnonymousNoteSourceTest {\n\n    val anonSource = AnonymousNoteSource()\n\n    val locator: NoteServiceLocator = mockk()\n\n    val localNoteRepo: ILocalNoteRepository = mockk()\n\n    //Shout out to Philipp Hauer @philipp_hauer for the snippet below (creating test data) with\n    //a default argument wrapper function:\n    fun getNote(creationDate: String = \"28/10/2018\",\n                contents: String = \"When I understand that this glass is already broken, every moment with it becomes precious.\",\n                upVotes: Int = 0,\n                imageUrl: String = \"\",\n                creator: User? = User(\n                        \"8675309\",\n                        \"Ajahn Chah\",\n                        \"\"\n                )\n    ) = Note(\n            creationDate = creationDate,\n            contents = contents,\n            upVotes = upVotes,\n            imageUrl = imageUrl,\n            creator = creator\n    )\n\n\n    @BeforeEach\n    fun setUpRedundantMocks() {\n        clearAllMocks()\n    }\n\n    /**\n     * When an anonymous user navigates to the List Feature, we retrieve data from a local\n     * repository only.\n     *\n     * a. Retrieve Notes Successfully\n     * b. Error cases\n     *\n     * a:\n     *1. Request data from localNoteRepo\n     *2.\n     *\n     */\n    @Test\n    fun `On Get Notes Successful`() = runBlocking {\n        //1 Set up Test data and mock responses\n\n        val testList = listOf(getNote(), getNote(), getNote())\n\n        every { locator.localAnon } returns localNoteRepo\n\n        coEvery { localNoteRepo.getNotes() } returns Result.build { testList }\n\n        //2 Call the Unit to be tested\n        val result: Result<Exception, List<Note>> = anonSource.getNotes(locator)\n\n        //3 Verify behaviour and state\n        verify { locator.localAnon }\n        coVerify { localNoteRepo.getNotes() }\n\n        if (result is Result.Value) assertEquals(result.value, testList)\n        else assertTrue { false }\n    }\n\n    /**\n     *b:\n     *1.\n     *\n     */\n    @Test\n    fun `On Get Notes Error`() = runBlocking {\n\n        every { locator.localAnon } returns localNoteRepo\n\n        coEvery { localNoteRepo.getNotes() } returns Result.build { throw SpaceNotesError.LocalIOException }\n\n        //2 Call the Unit to be tested\n        val result: Result<Exception, List<Note>> = anonSource.getNotes(locator)\n\n        //3 Verify behaviour and state\n        verify { locator.localAnon }\n        coVerify { localNoteRepo.getNotes() }\n\n        assert(result is Result.Error)\n    }\n\n    /**\n     * Retrieve a given note based on a passed in id\n     * a. Note retrieved successfully\n     * b. Error\n     *\n     * 1. Get note from repo\n     */\n    @Test\n    fun `On Get Note Successful`() = runBlocking {\n\n        val testNote = getNote()\n\n        every { locator.localAnon } returns localNoteRepo\n\n        coEvery { localNoteRepo.getNote(testNote.creationDate) } returns Result.build {\n            testNote\n        }\n\n        //2 Call the Unit to be tested\n        val result: Result<Exception, Note?> = anonSource.getNoteById(\n                testNote.creationDate,\n                locator\n        )\n\n        //3 Verify behaviour and state\n        verify { locator.localAnon }\n        coVerify { localNoteRepo.getNote(testNote.creationDate) }\n\n        if (result is Result.Value) assertEquals(result.value, testNote)\n        else assertTrue { false }\n    }\n\n    /**\n     *b:\n     */\n    @Test\n    fun `On Get Note Error`() = runBlocking {\n\n        val testId = getNote().creationDate\n\n        every { locator.localAnon } returns localNoteRepo\n\n        coEvery { localNoteRepo.getNote(testId) } returns Result.build { throw SpaceNotesError.LocalIOException }\n\n        //2 Call the Unit to be tested\n        val result: Result<Exception, Note?> = anonSource.getNoteById(testId, locator)\n\n        //3 Verify behaviour and state\n        verify { locator.localAnon }\n        coVerify { localNoteRepo.getNote(testId) }\n\n        assert(result is Result.Error)\n    }\n\n    /**\n     * When an anonymous user is done editing their note, attempt to update the value\n     * in the local repository\n     * a. Success: true\n     * b. Error\n     */\n    @Test\n    fun `On Update Note Success`() = runBlocking {\n\n        val testNote = getNote()\n\n        every { locator.localAnon } returns localNoteRepo\n\n        coEvery { localNoteRepo.updateNote(testNote) } returns Result.build {\n            Unit\n        }\n\n        //2 Call the Unit to be tested\n        val result: Result<Exception, Unit> = anonSource.updateNote(\n                testNote,\n                locator\n        )\n\n        //3 Verify behaviour and state\n        verify { locator.localAnon }\n        coVerify { localNoteRepo.updateNote(testNote) }\n\n        if (result is Result.Value) assertTrue(true)\n        else assertTrue { false }\n    }\n\n    /**\n     * b:\n     */\n    @Test\n    fun `On Update Note Error`() = runBlocking {\n\n        val testNote = getNote()\n\n        every { locator.localAnon } returns localNoteRepo\n\n        coEvery { localNoteRepo.updateNote(testNote) } returns Result.build {\n            throw SpaceNotesError.LocalIOException\n        }\n\n        //2 Call the Unit to be tested\n        val result: Result<Exception, Unit> = anonSource.updateNote(\n                testNote,\n                locator\n        )\n\n        verify { locator.localAnon }\n        coVerify { localNoteRepo.updateNote(testNote) }\n\n        assertTrue(result is Result.Error)\n    }\n\n    /**\n     * When the user wishes to delete a note then we try to delete the note.\n     *a. successfully deleted : true\n     *b. Error\n     *\n     */\n    @Test\n    fun `On Delete Note Successful`() = runBlocking {\n\n        val testNote = getNote()\n\n        every { locator.localAnon } returns localNoteRepo\n\n        coEvery { localNoteRepo.deleteNote(testNote) } returns Result.build {\n            Unit\n        }\n\n        //2 Call the Unit to be tested\n        val result: Result<Exception, Unit> = anonSource.deleteNote(\n                testNote,\n                locator\n        )\n\n        verify { locator.localAnon }\n        coVerify { localNoteRepo.deleteNote(testNote) }\n\n        if (result is Result.Value) assertTrue(true)\n        else assertTrue { false }\n    }\n\n    /**\n     * b:\n     */\n    @Test\n    fun `On Delete Note Error`() = runBlocking {\n\n        val testNote = getNote()\n\n        every { locator.localAnon } returns localNoteRepo\n\n        coEvery { localNoteRepo.deleteNote(testNote) } returns Result.build {\n            throw SpaceNotesError.LocalIOException\n        }\n\n        //2 Call the Unit to be tested\n        val result: Result<Exception, Unit> = anonSource.deleteNote(\n                testNote,\n                locator\n        )\n\n        verify { locator.localAnon }\n        coVerify { localNoteRepo.deleteNote(testNote) }\n\n        assertTrue(result is Result.Error)\n    }\n\n    @AfterEach\n    fun confirm() {\n        confirmVerified(\n            locator,\n            localNoteRepo\n        )\n    }\n\n}\n"
  },
  {
    "path": "domain/src/test/java/com/wiseassblog/domain/PublicNoteSourceTest.kt",
    "content": "package com.wiseassblog.domain\n\nimport com.wiseassblog.domain.domainmodel.*\nimport com.wiseassblog.domain.error.SpaceNotesError\nimport com.wiseassblog.domain.interactor.PublicNoteSource\nimport com.wiseassblog.domain.repository.IPublicNoteRepository\nimport com.wiseassblog.domain.servicelocator.NoteServiceLocator\nimport io.mockk.coEvery\nimport io.mockk.coVerify\nimport io.mockk.every\nimport io.mockk.mockk\nimport kotlinx.coroutines.runBlocking\nimport org.junit.jupiter.api.Test\nimport kotlin.test.assertEquals\nimport kotlin.test.assertTrue\n\nclass PublicNoteSourceTest {\n\n    val remote: IPublicNoteRepository = mockk()\n\n    val locator: NoteServiceLocator = mockk()\n\n    val source = PublicNoteSource()\n\n    fun getNote(creationDate: String = \"28/10/2018\",\n                contents: String = \"When I understand that this glass is already broken, every moment with it becomes precious.\",\n                upVotes: Int = 0,\n                imageUrl: String = \"\",\n                creator: User? = User(\n                        \"8675309\",\n                        \"Ajahn Chah\",\n                        \"\"\n                )\n    ) = Note(\n            creationDate = creationDate,\n            contents = contents,\n            upVotes = upVotes,\n            imageUrl = imageUrl,\n            creator = creator\n    )\n\n\n    /**\n     * Notes come from a single source, without any other shit involved.\n     *\n     * a: Notes returned\n     * b: error returned\n     *\n     * a:\n     * 1. Ask remote for notes: Notes\n     *\n     */\n    @Test\n    fun `On Get Notes`() = runBlocking {\n        val testList = listOf(getNote(), getNote())\n\n        every { locator.remotePublic } returns remote\n\n        coEvery { remote.getNotes() } returns Result.build { testList }\n\n        val result = source.getNotes(locator)\n\n        coVerify { remote.getNotes() }\n\n        if (result is Result.Value) assertEquals(result.value, testList)\n        else assertTrue { false }\n\n    }\n\n    /**\n     *b:\n     *1. Ask remote for notes: error\n     */\n    @Test\n    fun `On Get Notes error`() = runBlocking {\n        every { locator.remotePublic } returns remote\n\n        coEvery { remote.getNotes() } returns Result.build { throw SpaceNotesError.RemoteIOException }\n\n        val result = source.getNotes(locator)\n\n        coVerify { remote.getNotes() }\n\n        assertTrue { result is Result.Error }\n    }\n\n    /**\n     * Get a note by an id\n     *\n     * a: Note returned\n     * b: error returned\n     *\n     * a:\n     * 1. Ask remote for note: Note\n     *\n     */\n    @Test\n    fun `On Get Note`() = runBlocking {\n        val testNote = getNote()\n\n        every { locator.remotePublic } returns remote\n\n        coEvery { remote.getNote(testNote.creator!!.uid) } returns Result.build { testNote }\n\n        val result = source.getNoteById(testNote.creator!!.uid,locator)\n\n        coVerify { remote.getNote(testNote.creator!!.uid) }\n\n        if (result is Result.Value) assertEquals(result.value, testNote)\n        else assertTrue { false }\n\n    }\n\n    /**\n     *b:\n     *1. Ask remote for notes: error\n     */\n    @Test\n    fun `On Get Note error`() = runBlocking {\n        val testNote = getNote()\n\n        every { locator.remotePublic } returns remote\n\n        coEvery { remote.getNote(testNote.creator!!.uid) } returns Result.build { throw SpaceNotesError.RemoteIOException }\n\n        val result = source.getNoteById(testNote.creator!!.uid,locator)\n\n        coVerify { remote.getNote(testNote.creator!!.uid) }\n\n        assertTrue { result is Result.Error }\n    }\n\n}"
  },
  {
    "path": "domain/src/test/java/com/wiseassblog/domain/RegisteredNoteSourceTest.kt",
    "content": "package com.wiseassblog.domain\n\nimport com.wiseassblog.domain.domainmodel.*\nimport com.wiseassblog.domain.error.SpaceNotesError\nimport com.wiseassblog.domain.interactor.RegisteredNoteSource\nimport com.wiseassblog.domain.repository.IRemoteNoteRepository\nimport com.wiseassblog.domain.repository.ITransactionRepository\nimport com.wiseassblog.domain.servicelocator.NoteServiceLocator\nimport io.mockk.*\nimport kotlinx.coroutines.runBlocking\nimport org.junit.jupiter.api.AfterEach\nimport org.junit.jupiter.api.BeforeEach\nimport org.junit.jupiter.api.Test\nimport kotlin.test.assertEquals\nimport kotlin.test.assertTrue\n\n/**\n * Registered Note Source is for users which have authenticated via appropriate sign in functions.\n * Registered users have access to:\n * - A remote repository to share notes across devices, which is the source of truth for state\n * - A local repository to cache the most recent snap shot of the remote data, and to store offline\n * transactions to be pushed to the remote database.\n */\nclass RegisteredNoteSourceTest {\n\n    val source = RegisteredNoteSource()\n\n    //Stores transactions (NoteTransaction Cache) to be pushed to Remote eventually\n    val transactionRepository: ITransactionRepository = mockk()\n\n    //Contains Remote (SoT) and Local (State Cache)\n    val noteRepository: IRemoteNoteRepository = mockk()\n\n    val locator: NoteServiceLocator = mockk()\n\n    fun getNote(creationDate: String = \"28/10/2018\",\n                contents: String = \"When I understand that this glass is already broken, every moment with it becomes precious.\",\n                upVotes: Int = 0,\n                imageUrl: String = \"\",\n                creator: User? = User(\n                        \"8675309\",\n                        \"Ajahn Chah\",\n                        \"\"\n                )\n    ) = Note(\n            creationDate = creationDate,\n            contents = contents,\n            upVotes = upVotes,\n            imageUrl = imageUrl,\n            creator = creator\n    )\n\n    fun getTransaction(\n            creationDate: String = \"28/10/2018\",\n            contents: String = \"When I understand that this glass is already broken, every moment with it becomes precious.\",\n            upVotes: Int = 0,\n            imageUrl: String = \"\",\n            creator: User? = User(\n                    \"8675309\",\n                    \"Ajahn Chah\",\n                    \"\"\n            ),\n            transactionType: TransactionType = TransactionType.DELETE\n    ) = NoteTransaction(\n            creationDate = creationDate,\n            contents = contents,\n            upVotes = upVotes,\n            imageUrl = imageUrl,\n            creator = creator,\n            transactionType = transactionType\n    )\n\n\n    @BeforeEach\n    fun setUpRedundantMocks() {\n        clearAllMocks()\n    }\n\n\n    /**\n     * Upon requesting Notes, multiple steps must occur:\n     * 1. Check transactionRepo Cache for items:\n     * a. Empty: Proceed 3\n     * b. Not Empty: Proceed 2\n     *\n     * 2. Attempt to synchronize Remote with stored transactions:\n     * c. Successful: Delete all transactions from transactionRepo; Proceed 3\n     * d. Fail: Proceed 3\n     *\n     * 3. Get data from IRemoteNoteSource and return\n     * e. Success: return data\n     * f. Fail: return error\n     *\n     *\n     * successful communication with the remote datasource, and\n     *\n     */\n    @Test\n    fun `On Get Notes a, e`() = runBlocking {\n\n        val testNotes = listOf(getNote(), getNote(), getNote())\n\n        every { locator.transactionReg } returns transactionRepository\n\n        every { locator.remoteReg } returns noteRepository\n\n        coEvery { transactionRepository.getTransactions() } returns Result.build {\n            emptyList<NoteTransaction>()\n        }\n\n        coEvery { noteRepository.getNotes() } returns Result.build {\n            testNotes\n        }\n\n        val result = source.getNotes(locator)\n\n        coVerify { transactionRepository.getTransactions() }\n        coVerify { noteRepository.getNotes() }\n\n\n        if (result is Result.Value) assertEquals(result.value, testNotes)\n        else assertTrue { false }\n    }\n\n    /**\n     * b - transactions not empty\n     * c - successfully synchronized remote\n     * e - successfully returned data form remote\n     */\n    @Test\n    fun `On Get Notes b, c, e`() = runBlocking {\n\n        val testNotes = listOf(getNote(), getNote(), getNote())\n        val testTransactions = listOf(getTransaction(), getTransaction(), getTransaction())\n\n        every { locator.transactionReg } returns transactionRepository\n\n        every { locator.remoteReg } returns noteRepository\n\n        coEvery { transactionRepository.getTransactions() } returns Result.build {\n            testTransactions\n        }\n\n        coEvery { transactionRepository.deleteTransactions() } returns Result.build {\n            Unit\n        }\n\n        coEvery { noteRepository.getNotes() } returns Result.build {\n            testNotes\n        }\n\n        coEvery { noteRepository.synchronizeTransactions(testTransactions) } returns Result.build {\n            Unit\n        }\n\n        val result = source.getNotes(locator)\n\n        coVerify { transactionRepository.getTransactions() }\n        coVerify { noteRepository.synchronizeTransactions(testTransactions) }\n        coVerify { transactionRepository.deleteTransactions() }\n        coVerify { noteRepository.getNotes() }\n\n\n        if (result is Result.Value) assertEquals(result.value, testNotes)\n        else assertTrue { false }\n    }\n\n    /**\n     * Attempt to retrieve a note from remote repository.\n     * a. Success\n     * b. Fail\n     *\n     * a:\n     * 1. Request Note from Remote: success\n     * 2. return data\n     *\n     */\n    @Test\n    fun `On Get Note a`() = runBlocking {\n        val testId = getNote().creationDate\n\n\n        every { locator.remoteReg } returns noteRepository\n\n        coEvery { noteRepository.getNote(testId) } returns Result.build {\n            getNote()\n        }\n\n        val result = source.getNoteById(testId, locator)\n\n        coVerify { noteRepository.getNote(testId) }\n\n\n        if (result is Result.Value) assertEquals(result.value, getNote())\n        else assertTrue { false }\n    }\n\n    /**\n     * b:\n     * 1. Request Note from Remote: fail\n     * 2. return error\n     */\n    @Test\n    fun `On Get Note b`() = runBlocking {\n        val testId = getNote().creationDate\n\n\n        every { locator.remoteReg } returns noteRepository\n\n        coEvery { noteRepository.getNote(testId) } returns Result.build {\n            throw SpaceNotesError.RemoteIOException\n        }\n\n        val result = source.getNoteById(testId, locator)\n\n        coVerify { noteRepository.getNote(testId) }\n\n        assertTrue { result is Result.Error }\n    }\n\n    /**\n     * Attempt to delete a note from remote repository. Failing that, store a transaction object\n     * in transaction database. Failing that, return error.\n     * a. Success\n     * b. Delete Fail\n     * c. Transaction Fail\n     *\n     * a:\n     * 1. Delete Note from Remote: success\n     * 2. return Success\n     *\n     */\n    @Test\n    fun `On Delete Note a`() = runBlocking {\n        val testNote = getNote()\n\n        every { locator.remoteReg } returns noteRepository\n\n        coEvery { noteRepository.deleteNote(testNote) } returns Result.build {\n            Unit\n        }\n\n        val result = source.deleteNote(testNote, locator)\n\n        coVerify { noteRepository.deleteNote(testNote) }\n\n        if (result is Result.Value) {\n            //assert the value as being \"true\"\n            assertTrue { true }\n        } else {\n            assertTrue { false }\n        }\n    }\n\n    /**\n     * b:\n     * 1. Delete Note from Remote: fail\n     * 2. Map to NoteTransaction and store in transactionRepository: success\n     * 3. return Success\n     */\n    @Test\n    fun `On Delete Note b`() = runBlocking {\n        val testNote = getNote()\n        val testTransaction = getNote().toTransaction(TransactionType.DELETE)\n\n        every { locator.remoteReg } returns noteRepository\n        every { locator.transactionReg } returns transactionRepository\n\n\n        coEvery { noteRepository.deleteNote(testNote) } returns Result.build {\n            throw SpaceNotesError.RemoteIOException\n        }\n\n        coEvery { transactionRepository.updateTransactions(testTransaction) } returns Result.build {\n            Unit\n        }\n\n        val result = source.deleteNote(testNote, locator)\n\n        coVerify { noteRepository.deleteNote(testNote) }\n        coVerify { transactionRepository.updateTransactions(testTransaction) }\n\n        if (result is Result.Value) {\n            //assert the value as being \"false\"\n            assertTrue { true }\n        } else {\n            assertTrue { false }\n        }\n    }\n\n    /**\n     * c:\n     * 1. Delete Note from Remote: fail\n     * 2. Map to NoteTransaction and store in transactionRepository: fail\n     * 3. return error\n     */\n    @Test\n    fun `On Delete Note c`() = runBlocking {\n        val testNote = getNote()\n        val testTransaction = getNote().toTransaction(TransactionType.DELETE)\n\n        every { locator.remoteReg } returns noteRepository\n        every { locator.transactionReg } returns transactionRepository\n\n\n        coEvery { noteRepository.deleteNote(testNote) } returns Result.build {\n            throw SpaceNotesError.RemoteIOException\n        }\n\n        coEvery { transactionRepository.updateTransactions(testTransaction) } returns Result.build {\n            throw SpaceNotesError.TransactionIOException\n        }\n\n        val result = source.deleteNote(testNote, locator)\n\n        coVerify { noteRepository.deleteNote(testNote) }\n        coVerify { transactionRepository.updateTransactions(testTransaction) }\n\n        assertTrue { result is Result.Error }\n    }\n\n    /**\n     * Attempt to update a note in remote repository. Failing that, store a transaction object\n     * in transaction database. Failing that, return error.\n     * a. Success\n     * b. Update Fail\n     * c. Transaction Fail\n     *\n     * a:\n     * 1. Update Note from Remote: success\n     * 2. return Success\n     *\n     */\n    @Test\n    fun `On Update Note a`() = runBlocking {\n        val testNote = getNote()\n\n        every { locator.remoteReg } returns noteRepository\n\n        coEvery { noteRepository.updateNote(testNote) } returns Result.build {\n            Unit\n        }\n\n        val result = source.updateNote(testNote, locator)\n\n        coVerify { noteRepository.updateNote(testNote) }\n\n        if (result is Result.Value) {\n            //assert the value as being \"true\"\n            assertTrue { true }\n        } else {\n            assertTrue { false }\n        }\n    }\n\n    /**\n     * b:\n     * 1. Delete Note from Remote: fail\n     * 2. Map to NoteTransaction and store in transactionRepository: success\n     * 3. return Success\n     */\n    @Test\n    fun `On Update Note b`() = runBlocking {\n        val testNote = getNote()\n        val testTransaction = getNote().toTransaction(TransactionType.UPDATE)\n\n        every { locator.remoteReg } returns noteRepository\n        every { locator.transactionReg } returns transactionRepository\n\n        coEvery { noteRepository.updateNote(testNote) } returns Result.build {\n            throw SpaceNotesError.RemoteIOException\n        }\n\n        coEvery { transactionRepository.updateTransactions(testTransaction) } returns Result.build {\n            Unit\n        }\n\n        val result = source.updateNote(testNote, locator)\n\n        coVerify { noteRepository.updateNote(testNote) }\n        coVerify { transactionRepository.updateTransactions(testTransaction) }\n\n        if (result is Result.Value) {\n            //assert the value as being \"false\"\n            assertTrue { true }\n        } else {\n            assertTrue { false }\n        }\n    }\n\n    /**\n     * c:\n     * 1. Delete Note from Remote: fail\n     * 2. Map to NoteTransaction and store in transactionRepository: fail\n     * 3. return error\n     */\n    @Test\n    fun `On Update Note c`() = runBlocking {\n        val testNote = getNote()\n        val testTransaction = getNote().toTransaction(TransactionType.UPDATE)\n\n        every { locator.remoteReg } returns noteRepository\n        every { locator.transactionReg } returns transactionRepository\n\n        coEvery { noteRepository.updateNote(testNote) } returns Result.build {\n            throw SpaceNotesError.RemoteIOException\n        }\n\n        coEvery { transactionRepository.updateTransactions(testTransaction) } returns Result.build {\n            throw SpaceNotesError.TransactionIOException\n        }\n\n        val result = source.updateNote(testNote, locator)\n\n        coVerify { noteRepository.updateNote(testNote) }\n        coVerify { transactionRepository.updateTransactions(testTransaction) }\n\n        assertTrue { result is Result.Error }\n    }\n\n    @AfterEach\n    fun confirm() {\n        excludeRecords {\n            locator.transactionReg\n            locator.remoteReg\n        }\n        confirmVerified(\n            transactionRepository,\n            noteRepository,\n            locator\n        )\n    }\n\n}\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Wed Oct 24 14:24:13 PDT 2018\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-4.10-all.zip\n"
  },
  {
    "path": "gradle.properties",
    "content": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx1536m\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\nandroid.useAndroidX = true\nandroid.enableJetifier = true"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env sh\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=$(save \"$@\")\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\n# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong\nif [ \"$(uname)\" = \"Darwin\" ] && [ \"$HOME\" = \"$PWD\" ]; then\n  cd \"$(dirname \"$0\")\"\nfi\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto init\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto init\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:init\n@rem Get command-line arguments, handling Windows variants\n\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\n\n:win9xME_args\n@rem Slurp the command line arguments.\nset CMD_LINE_ARGS=\nset _SKIP=2\n\n:win9xME_args_slurp\nif \"x%~1\" == \"x\" goto execute\n\nset CMD_LINE_ARGS=%*\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "settings.gradle",
    "content": "include ':app', ':data', ':domain'\n"
  },
  {
    "path": "versions.gradle",
    "content": "/**\n *Source largely taken from this OS repo: https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/versions.gradle\n * Thank you to everyone involved in this awesome OS project!\n *\n * In this file, we basically build a tree of deps in a very DRY way.\n **/\n\n//ext is a way to add extra data key/value pairs to a gradle domain object. Since it has no prefix,\n//it is short hand for project.ext in this case.\n//[:] is groovy syntax for creating a \"Map\" Object, which is a collection of key/value pairs\n\n//create a map of key/value pairs, called deps (deps)\next.deps = [:]\n\n//def means we're making a local variable. We'll use this map to build our deps key/value\n//pairs below\ndef versions = [:]\nversions.lifecycle = \"2.0.0-rc01\"\nversions.junit = \"5.1.1\"\nversions.room = \"2.1.0-alpha01\"\nversions.navigation = \"1.0.0-alpha06\"\nversions.espresso = \"3.0.1\"\nversions.mockito = \"2.13.0\"\nversions.dagger = \"2.15\"\nversions.support_test = \"1.0.1\"\nversions.design = \"1.0.0-rc01\"\nversions.appcompat = \"1.0.0\"\nversions.ktx_fragment = \"1.0.0\"\nversions.rec_view = \"1.0.0\"\nversions.firebase_auth = \"16.1.0\"\nversions.firebase_core = \"16.0.6\"\nversions.firebase_firestore = \"17.1.4\"\nversions.play_services_auth = \"16.0.1\"\nversions.constraint_layout = \"2.0.0-alpha2\"\nversions.coroutine_version = \"1.0.1\"\nversions.android_gradle_plugin = \"3.2.1\"\nversions.kotlin = \"1.3.0-rc-190\"\n\ndef deps = [:]\n\ndef play_services = [:]\nplay_services.auth =  \"com.google.android.gms:play-services-auth:$versions.play_services_auth\"\ndeps.play_services = play_services\n\ndef android = [:]\n\nandroid.appcompat = \"androidx.appcompat:appcompat:$versions.appcompat\"\nandroid.fragment = \"androidx.fragment:fragment:$versions.appcompat\"\nandroid.recyclerview = \"androidx.recyclerview:recyclerview:$versions.appcompat\"\nandroid.design = \"com.google.android.material:material:$versions.design\"\nandroid.constraint_layout = \"androidx.constraintlayout:constraintlayout:$versions.constraint_layout\"\nandroid.lifecycle_extensions = \"androidx.lifecycle:lifecycle-extensions:$versions.lifecycle\"\nandroid.ktx_fragment = \"androidx.fragment:fragment-ktx:$versions.ktx_fragment\"\ndeps.android = android\n\ndef room = [:]\nroom.runtime = \"androidx.room:room-runtime:$versions.room\"\nroom.compiler = \"androidx.room:room-compiler:$versions.room\"\ndeps.room = room\n\ndef firebase = [:]\nfirebase.core = \"com.google.firebase:firebase-core:$versions.firebase_core\"\nfirebase.auth = \"com.google.firebase:firebase-auth:$versions.firebase_auth\"\nfirebase.firestore = \"com.google.firebase:firebase-firestore:$versions.firebase_firestore\"\ndeps.firebase = firebase\n\ndef test = [:]\n\ntest.junit = \"org.junit.jupiter:junit-jupiter-api:$versions.junit\"\ntest.jupiter_engine = \"org.junit.jupiter:junit-jupiter-engine:$versions.junit\"\ntest.vintage_engine = \"org.junit.vintage:junit-vintage-engine:$versions.junit\"\ntest.kotlin_junit = \"org.jetbrains.kotlin:kotlin-test-junit:$versions.kotlin\"\ntest.mockk = \"io.mockk:mockk:1.9\"\ndeps.test = test\n\ndef kotlin = [:]\nkotlin.kotlin_jre = \"org.jetbrains.kotlin:kotlin-stdlib-jdk8:$versions.kotlin\"\nkotlin.kotlin_gradle_plugin = \"org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin\"\nkotlin.coroutines_core = \"org.jetbrains.kotlinx:kotlinx-coroutines-core:$versions.coroutine_version\"\nkotlin.coroutines_android = \"org.jetbrains.kotlinx:kotlinx-coroutines-android:$versions.coroutine_version\"\ndeps.kotlin = kotlin\n\n\ndef build_versions = [:]\nbuild_versions.min_sdk = 21\nbuild_versions.target_sdk = 28\next.build_versions = build_versions\n\ndeps.android_gradle_plugin = \"com.android.tools.build:gradle:$versions.android_gradle_plugin\"\n\next.deps = deps\n\n\ndef addRepos(RepositoryHandler handler) {\n    handler.google()\n    handler.jcenter()\n    handler.maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }\n    handler.maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }\n}\n\next.addRepos = this.&addRepos"
  }
]