[
  {
    "path": ".gitattributes",
    "content": "# Auto detect text files and perform LF normalization\n* text=auto\n"
  },
  {
    "path": ".gitignore",
    "content": "# Built application files\n*.apk\n*.aar\n*.ap_\n*.aab\n\n# Files for the ART/Dalvik VM\n*.dex\n\n# Java class files\n*.class\n\n# Generated files\nbin/\ngen/\nout/\n#  Uncomment the following line in case you need and you don't have the release build type files in your app\n# release/\n\n# Gradle files\n.gradle/\nbuild/\n\n# Local configuration file (sdk path, etc)\nlocal.properties\n\n# Proguard folder generated by Eclipse\nproguard/\n\n# Log Files\n*.log\n\n# Android Studio Navigation editor temp files\n.navigation/\n\n# Android Studio captures folder\ncaptures/\n\n# IntelliJ\n*.iml\n.idea/workspace.xml\n.idea/tasks.xml\n.idea/gradle.xml\n.idea/assetWizardSettings.xml\n.idea/dictionaries\n.idea/libraries\n# Android Studio 3 in .gitignore file.\n.idea/caches\n.idea/modules.xml\n# Comment next line if keeping position of elements in Navigation Editor is relevant for you\n.idea/navEditor.xml\n\n# Keystore files\n# Uncomment the following lines if you do not want to check your keystore files in.\n#*.jks\n#*.keystore\n\n# External native build folder generated in Android Studio 2.2 and later\n.externalNativeBuild\n.cxx/\n\n# Google Services (e.g. APIs or Firebase)\n# google-services.json\n\n# Freeline\nfreeline.py\nfreeline/\nfreeline_project_description.json\n\n# fastlane\nfastlane/report.xml\nfastlane/Preview.html\nfastlane/screenshots\nfastlane/test_output\nfastlane/readme.md\n\n# Version control\nvcs.xml\n\n# lint\nlint/intermediates/\nlint/generated/\nlint/outputs/\nlint/tmp/\n# lint/reports/\n"
  },
  {
    "path": ".idea/.name",
    "content": "Android-Chat-App"
  },
  {
    "path": ".idea/codeStyles/Project.xml",
    "content": "<component name=\"ProjectCodeStyleConfiguration\">\n  <code_scheme name=\"Project\" version=\"173\">\n    <JetCodeStyleSettings>\n      <option name=\"PACKAGES_TO_USE_STAR_IMPORTS\">\n        <value>\n          <package name=\"java.util\" alias=\"false\" withSubpackages=\"false\" />\n          <package name=\"kotlinx.android.synthetic\" alias=\"false\" withSubpackages=\"true\" />\n          <package name=\"io.ktor\" alias=\"false\" withSubpackages=\"true\" />\n        </value>\n      </option>\n      <option name=\"PACKAGES_IMPORT_LAYOUT\">\n        <value>\n          <package name=\"\" alias=\"false\" withSubpackages=\"true\" />\n          <package name=\"java\" alias=\"false\" withSubpackages=\"true\" />\n          <package name=\"javax\" alias=\"false\" withSubpackages=\"true\" />\n          <package name=\"kotlin\" alias=\"false\" withSubpackages=\"true\" />\n          <package name=\"\" alias=\"true\" withSubpackages=\"true\" />\n        </value>\n      </option>\n      <option name=\"CODE_STYLE_DEFAULTS\" value=\"KOTLIN_OFFICIAL\" />\n    </JetCodeStyleSettings>\n    <codeStyleSettings language=\"XML\">\n      <indentOptions>\n        <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\" />\n      </indentOptions>\n      <arrangement>\n        <rules>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>xmlns:android</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>^$</XML_NAMESPACE>\n                </AND>\n              </match>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>xmlns:.*</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>^$</XML_NAMESPACE>\n                </AND>\n              </match>\n              <order>BY_NAME</order>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>.*:id</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n                </AND>\n              </match>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>.*:name</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n                </AND>\n              </match>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>name</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>^$</XML_NAMESPACE>\n                </AND>\n              </match>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>style</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>^$</XML_NAMESPACE>\n                </AND>\n              </match>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>.*</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>^$</XML_NAMESPACE>\n                </AND>\n              </match>\n              <order>BY_NAME</order>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>.*</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n                </AND>\n              </match>\n              <order>ANDROID_ATTRIBUTE_ORDER</order>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>.*</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>.*</XML_NAMESPACE>\n                </AND>\n              </match>\n              <order>BY_NAME</order>\n            </rule>\n          </section>\n        </rules>\n      </arrangement>\n    </codeStyleSettings>\n    <codeStyleSettings language=\"kotlin\">\n      <option name=\"CODE_STYLE_DEFAULTS\" value=\"KOTLIN_OFFICIAL\" />\n    </codeStyleSettings>\n  </code_scheme>\n</component>"
  },
  {
    "path": ".idea/codeStyles/codeStyleConfig.xml",
    "content": "<component name=\"ProjectCodeStyleConfiguration\">\n  <state>\n    <option name=\"USE_PER_PROJECT_SETTINGS\" value=\"true\" />\n  </state>\n</component>"
  },
  {
    "path": ".idea/jarRepositories.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"RemoteRepositoriesConfiguration\">\n    <remote-repository>\n      <option name=\"id\" value=\"central\" />\n      <option name=\"name\" value=\"Maven Central repository\" />\n      <option name=\"url\" value=\"https://repo1.maven.org/maven2\" />\n    </remote-repository>\n    <remote-repository>\n      <option name=\"id\" value=\"jboss.community\" />\n      <option name=\"name\" value=\"JBoss Community repository\" />\n      <option name=\"url\" value=\"https://repository.jboss.org/nexus/content/repositories/public/\" />\n    </remote-repository>\n    <remote-repository>\n      <option name=\"id\" value=\"BintrayJCenter\" />\n      <option name=\"name\" value=\"BintrayJCenter\" />\n      <option name=\"url\" value=\"https://jcenter.bintray.com/\" />\n    </remote-repository>\n    <remote-repository>\n      <option name=\"id\" value=\"Google\" />\n      <option name=\"name\" value=\"Google\" />\n      <option name=\"url\" value=\"https://dl.google.com/dl/android/maven2/\" />\n    </remote-repository>\n  </component>\n</project>"
  },
  {
    "path": ".idea/misc.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ProjectRootManager\" version=\"2\" languageLevel=\"JDK_1_8\" project-jdk-name=\"1.8\" project-jdk-type=\"JavaSDK\">\n    <output url=\"file://$PROJECT_DIR$/build/classes\" />\n  </component>\n  <component name=\"ProjectType\">\n    <option name=\"id\" value=\"Android\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/render.experimental.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"RenderSettings\">\n    <option name=\"showDecorations\" value=\"true\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/runConfigurations.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"RunConfigurationProducerService\">\n    <option name=\"ignoredProducers\">\n      <set>\n        <option value=\"org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer\" />\n        <option value=\"org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer\" />\n        <option value=\"org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer\" />\n      </set>\n    </option>\n  </component>\n</project>"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Fredrik Bogg\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Chat App Android\n![HeaderImage](github_images/header.png)\n\n## Introduction\nThis is a demo application built with the goal to create a fun and challenging application based on the MVVM architectural pattern.\n\nSee below for more information.\n\n## Technologies & Architecture \n\n#### Technologies\nAndroid, Kotlin\n\n#### Architecture\nModel-View-ViewModel (MVVM)\n\n#### Firebase\n* Authentication\n* Realtime Database\n* Storage\n\n#### Architecture Components\n[ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel), [LiveData](https://developer.android.com/topic/libraries/architecture/livedata), [DataBinding](https://developer.android.com/topic/libraries/data-binding), \n[Navigation](https://developer.android.com/guide/navigation/)\n\n## Features\n\n**Start:** Login/create account\n\n**Chats:** List of chats, online status, update on change\n\n**Notifications:** Accept/decline friend requests, notifications symbol\n\n**Users:** List of users\n\n**Settings:** Change image, change status, logout\n\n**Chat:** Send and show messages sorted by timestamp, online status, custom toolbar, update on change\n\n**Profile:** Add/remove friend, accept/decline friend request\n\n**General:** Auto login, bottom navigation, error messages with snackbar, progress bar\n\n## Screenshots\n\n### Start | Login | Create Account\n\n<p align = \"left\" >\n  <img width=\"250\" height=\"500\" src=\"github_images/start.png\">\n  <img width=\"250\" height=\"500\"  src=\"github_images/login.png\"> \n  <img width=\"250\" height=\"500\" src=\"github_images/create.png\"> \n</p>\n\n### Chats | Notifications | Users\n\n<p align = \"left\" >\n  <img width=\"250\" height=\"500\" src=\"github_images/chats.png\">\n  <img width=\"250\" height=\"500\"  src=\"github_images/notifications.png\"> \n  <img width=\"250\" height=\"500\" src=\"github_images/users.png\"> \n</p>\n\n### Settings | Chat | Profile\n\n<p align = \"left\" >\n  <img width=\"250\" height=\"500\" src=\"github_images/settings.png\">\n  <img width=\"250\" height=\"500\"  src=\"github_images/chat.png\"> \n  <img width=\"250\" height=\"500\" src=\"github_images/profile.png\"> \n</p>\n\n### Firebase\n<p align = \"left\" >\n  <img  width=\"378\" height=\"332\" src=\"github_images/db.png\"> \n</p>\n\n## Setup\n#### Requirements\n* Basic knowledge about Android Studio\n* Basic knowledge about Firebase\n\n#### Firebase\n * Setup Authentication and use the Sign-in method 'Email/Password'\n * Setup Realtime Database\n * Setup Storage\n * Replace the file [google-services.json](app/google-services.json)\n * <b>Note:</b> Download the google-services.json file after the Firebase services are set up to automatically include the services in the json file.\n * <b>Note:</b> When updating the google-services.json file then make sure to invalidate the caches as well as doing a clean + rebuild.\n\n#### Project\n1. Download and open the project in Android Studio\n2. Connect your Android phone or use the emulator to start the application\n"
  },
  {
    "path": "app/.gitignore",
    "content": "/build"
  },
  {
    "path": "app/build.gradle",
    "content": "apply plugin: 'com.android.application'\napply plugin: 'kotlin-android'\napply plugin: 'kotlin-android-extensions'\napply plugin: 'com.google.gms.google-services'\napply plugin: 'kotlin-kapt'\n\nandroid {\n    compileSdkVersion 29\n    buildToolsVersion \"29.0.3\"\n\n    defaultConfig {\n        applicationId \"com.fredrikbogg.android_chat_app\"\n        minSdkVersion 26\n        targetSdkVersion 29\n        versionCode 1\n        versionName \"1.0\"\n\n        testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n    }\n\n    buildFeatures {\n        dataBinding = true\n        viewBinding = true\n    }\n\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_8\n        targetCompatibility JavaVersion.VERSION_1_8\n    }\n    kotlinOptions {\n        jvmTarget = '1.8'\n    }\n}\n\ndependencies {\n    implementation fileTree(dir: \"libs\", include: [\"*.jar\"])\n    implementation \"org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version\"\n    implementation 'androidx.core:core-ktx:1.3.1'\n    implementation 'androidx.appcompat:appcompat:1.2.0'\n    implementation 'com.google.android.material:material:1.2.0'\n    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'\n    implementation 'androidx.legacy:legacy-support-v4:1.0.0'\n\n    //Navigation, lifecycle\n    implementation 'androidx.navigation:navigation-fragment:2.3.0'\n    implementation 'androidx.navigation:navigation-ui:2.3.0'\n    implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'\n    implementation 'androidx.navigation:navigation-fragment-ktx:2.3.0'\n    implementation 'androidx.navigation:navigation-ui-ktx:2.3.0'\n    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'\n\n    // Firebase\n    implementation 'com.google.firebase:firebase-database:19.3.1'\n    implementation 'com.google.firebase:firebase-auth:19.3.2'\n    implementation 'com.google.firebase:firebase-storage:19.1.1'\n\n    // Picasso\n    implementation 'com.squareup.picasso:picasso:2.71828'\n    implementation 'jp.wasabeef:picasso-transformations:2.2.1'\n\n    testImplementation 'junit:junit:4.13'\n    androidTestImplementation 'androidx.test.ext:junit:1.1.1'\n    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'\n}\n"
  },
  {
    "path": "app/google-services.json",
    "content": "-EDIT THIS-"
  },
  {
    "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"
  },
  {
    "path": "app/src/androidTest/java/com/fredrikbogg/android_chat_app/ExampleInstrumentedTest.kt",
    "content": "package com.fredrikbogg.android_chat_app\n\nimport androidx.test.platform.app.InstrumentationRegistry\nimport androidx.test.ext.junit.runners.AndroidJUnit4\n\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\nimport org.junit.Assert.*\n\n/**\n * Instrumented test, which will execute on an Android device.\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\n@RunWith(AndroidJUnit4::class)\nclass ExampleInstrumentedTest {\n    @Test\n    fun useAppContext() {\n        // Context of the app under test.\n        val appContext = InstrumentationRegistry.getInstrumentation().targetContext\n        assertEquals(\"com.fredrikbogg.android_chat_app\", appContext.packageName)\n    }\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.fredrikbogg.android_chat_app\">\n\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <application\n        android:name=\"com.fredrikbogg.android_chat_app.App\"\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\n            android:name=\"com.fredrikbogg.android_chat_app.ui.main.MainActivity\"\n            android:label=\"@string/app_name\"\n            android:screenOrientation=\"portrait\">\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        <meta-data\n            android:name=\"preloaded_fonts\"\n            android:resource=\"@array/preloaded_fonts\" />\n    </application>\n\n</manifest>"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/App.kt",
    "content": "package com.fredrikbogg.android_chat_app\n\nimport android.app.Application\nimport com.fredrikbogg.android_chat_app.util.SharedPreferencesUtil\n\n\nclass App : Application() {\n\n    override fun onCreate() {\n        super.onCreate()\n        application = this\n    }\n\n    companion object {\n        lateinit var application: Application\n            private set\n\n        var myUserID: String = \"\"\n            get() {\n                field = SharedPreferencesUtil.getUserID(application.applicationContext).orEmpty()\n                return field\n            }\n            private set\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/data/Event.kt",
    "content": "package com.fredrikbogg.android_chat_app.data\n\nimport androidx.lifecycle.Observer\n\n\nopen class Event<out T>(private val content: T) {\n    private var isHandled = false\n\n    fun getContentIfNotHandled(): T? {\n        return if (isHandled) {\n            null\n        } else {\n            isHandled = true\n            content\n        }\n    }\n}\n\nclass EventObserver<T>(private val onEventUnhandledContent: (T) -> Unit) : Observer<Event<T>> {\n    override fun onChanged(event: Event<T>?) {\n        event?.getContentIfNotHandled()?.let { onEventUnhandledContent(it) }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/data/Result.kt",
    "content": "package com.fredrikbogg.android_chat_app.data\n\n\nsealed class Result<out R> {\n    data class Success<out T>(val data: T? = null, val msg: String? = null) : Result<T>()\n    class Error(val msg: String? = null) : Result<Nothing>()\n    object Loading : Result<Nothing>()\n}\n"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/data/db/entity/Chat.kt",
    "content": "package com.fredrikbogg.android_chat_app.data.db.entity\n\nimport com.google.firebase.database.PropertyName\n\n\ndata class Chat(\n    @get:PropertyName(\"lastMessage\") @set:PropertyName(\"lastMessage\") var lastMessage: Message = Message(),\n    @get:PropertyName(\"info\") @set:PropertyName(\"info\") var info: ChatInfo = ChatInfo()\n)\n\ndata class ChatInfo(\n    @get:PropertyName(\"id\") @set:PropertyName(\"id\") var id: String = \"\"\n)"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/data/db/entity/Message.kt",
    "content": "package com.fredrikbogg.android_chat_app.data.db.entity\n\nimport com.google.firebase.database.PropertyName\nimport java.util.*\n\n\ndata class Message(\n    @get:PropertyName(\"senderID\") @set:PropertyName(\"senderID\") var senderID: String = \"\",\n    @get:PropertyName(\"text\") @set:PropertyName(\"text\") var text: String = \"\",\n    @get:PropertyName(\"epochTimeMs\") @set:PropertyName(\"epochTimeMs\") var epochTimeMs: Long = Date().time,\n    @get:PropertyName(\"seen\") @set:PropertyName(\"seen\") var seen: Boolean = false\n)"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/data/db/entity/User.kt",
    "content": "package com.fredrikbogg.android_chat_app.data.db.entity\n\nimport com.google.firebase.database.PropertyName\n\n\ndata class User(\n    @get:PropertyName(\"info\") @set:PropertyName(\"info\") var info: UserInfo = UserInfo(),\n    @get:PropertyName(\"friends\") @set:PropertyName(\"friends\") var friends: HashMap<String, UserFriend> = HashMap(),\n    @get:PropertyName(\"notifications\") @set:PropertyName(\"notifications\") var notifications: HashMap<String, UserNotification> = HashMap(),\n    @get:PropertyName(\"sentRequests\") @set:PropertyName(\"sentRequests\") var sentRequests: HashMap<String, UserRequest> = HashMap()\n)\n\ndata class UserFriend(\n    @get:PropertyName(\"userID\") @set:PropertyName(\"userID\") var userID: String = \"\"\n)\n\ndata class UserInfo(\n    @get:PropertyName(\"id\") @set:PropertyName(\"id\") var id: String = \"\",\n    @get:PropertyName(\"displayName\") @set:PropertyName(\"displayName\") var displayName: String = \"\",\n    @get:PropertyName(\"status\") @set:PropertyName(\"status\") var status: String = \"No status\",\n    @get:PropertyName(\"profileImageUrl\") @set:PropertyName(\"profileImageUrl\") var profileImageUrl: String = \"\",\n    @get:PropertyName(\"online\") @set:PropertyName(\"online\") var online: Boolean = false\n)\n\ndata class UserNotification(\n    @get:PropertyName(\"userID\") @set:PropertyName(\"userID\") var userID: String = \"\"\n)\n\ndata class UserRequest(\n    @get:PropertyName(\"userID\") @set:PropertyName(\"userID\") var userID: String = \"\"\n)\n\n"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/data/db/remote/FirebaseAuthSource.kt",
    "content": "package com.fredrikbogg.android_chat_app.data.db.remote\n\nimport com.fredrikbogg.android_chat_app.data.model.CreateUser\nimport com.fredrikbogg.android_chat_app.data.model.Login\nimport com.fredrikbogg.android_chat_app.data.Result\nimport com.google.android.gms.tasks.Task\nimport com.google.firebase.auth.AuthResult\nimport com.google.firebase.auth.FirebaseAuth\nimport com.google.firebase.auth.FirebaseUser\n\nclass FirebaseAuthStateObserver {\n\n    private var authListener: FirebaseAuth.AuthStateListener? = null\n    private var instance: FirebaseAuth? = null\n\n    fun start(valueEventListener: FirebaseAuth.AuthStateListener, instance: FirebaseAuth) {\n        this.authListener = valueEventListener\n        this.instance = instance\n        this.instance!!.addAuthStateListener(authListener!!)\n    }\n\n    fun clear() {\n        authListener?.let { instance?.removeAuthStateListener(it) }\n    }\n}\n\nclass FirebaseAuthSource {\n\n    companion object {\n        val authInstance = FirebaseAuth.getInstance()\n    }\n\n    private fun attachAuthObserver(b: ((Result<FirebaseUser>) -> Unit)): FirebaseAuth.AuthStateListener {\n        return FirebaseAuth.AuthStateListener {\n            if (it.currentUser == null) {\n                b.invoke(Result.Error(\"No user\"))\n            } else { b.invoke(Result.Success(it.currentUser)) }\n        }\n    }\n\n    fun loginWithEmailAndPassword(login: Login): Task<AuthResult> {\n        return authInstance.signInWithEmailAndPassword(login.email, login.password)\n    }\n\n    fun createUser(createUser: CreateUser): Task<AuthResult> {\n        return authInstance.createUserWithEmailAndPassword(createUser.email, createUser.password)\n    }\n\n    fun logout() {\n        authInstance.signOut()\n    }\n\n    fun attachAuthStateObserver(firebaseAuthStateObserver: FirebaseAuthStateObserver, b: ((Result<FirebaseUser>) -> Unit)) {\n        val listener = attachAuthObserver(b)\n        firebaseAuthStateObserver.start(listener, authInstance)\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/data/db/remote/FirebaseDatabaseSource.kt",
    "content": "package com.fredrikbogg.android_chat_app.data.db.remote\n\nimport com.fredrikbogg.android_chat_app.data.Result\nimport com.fredrikbogg.android_chat_app.data.db.entity.*\nimport com.fredrikbogg.android_chat_app.util.wrapSnapshotToArrayList\nimport com.fredrikbogg.android_chat_app.util.wrapSnapshotToClass\nimport com.google.android.gms.tasks.Task\nimport com.google.android.gms.tasks.TaskCompletionSource\nimport com.google.firebase.database.*\n\nclass FirebaseReferenceConnectedObserver {\n\n    private var valueEventListener: ValueEventListener? = null\n    private var dbRef: DatabaseReference? = null\n    private var userRef: DatabaseReference? = null\n\n    fun start(userID: String) {\n        this.userRef = FirebaseDataSource.dbInstance.reference.child(\"users/$userID/info/online\")\n        this.valueEventListener = getEventListener(userID)\n        this.dbRef = FirebaseDataSource.dbInstance.getReference(\".info/connected\").apply { addValueEventListener(valueEventListener!!) }\n    }\n\n    private fun getEventListener(userID: String): ValueEventListener {\n        return (object : ValueEventListener {\n            override fun onDataChange(snapshot: DataSnapshot) {\n                val connected = snapshot.getValue(Boolean::class.java) ?: false\n                if (connected) {\n                    FirebaseDataSource.dbInstance.reference.child(\"users/$userID/info/online\").setValue(true)\n                    userRef?.onDisconnect()?.setValue(false)\n                }\n            }\n\n            override fun onCancelled(error: DatabaseError) {}\n        })\n    }\n\n    fun clear() {\n        valueEventListener?.let { dbRef?.removeEventListener(it) }\n        userRef?.setValue(false)\n        valueEventListener = null\n        dbRef = null\n        userRef = null\n    }\n}\n\nclass FirebaseReferenceValueObserver {\n    private var valueEventListener: ValueEventListener? = null\n    private var dbRef: DatabaseReference? = null\n\n    fun start(valueEventListener: ValueEventListener, reference: DatabaseReference) {\n        reference.addValueEventListener(valueEventListener)\n        this.valueEventListener = valueEventListener\n        this.dbRef = reference\n    }\n\n    fun clear() {\n        valueEventListener?.let { dbRef?.removeEventListener(it) }\n        valueEventListener = null\n        dbRef = null\n    }\n}\n\nclass FirebaseReferenceChildObserver {\n    private var valueEventListener: ChildEventListener? = null\n    private var dbRef: DatabaseReference? = null\n    private var isObserving: Boolean = false\n\n    fun start(valueEventListener: ChildEventListener, reference: DatabaseReference) {\n        isObserving = true\n        reference.addChildEventListener(valueEventListener)\n        this.valueEventListener = valueEventListener\n        this.dbRef = reference\n    }\n\n    fun clear() {\n        valueEventListener?.let { dbRef?.removeEventListener(it) }\n        isObserving = false\n        valueEventListener = null\n        dbRef = null\n    }\n\n    fun isObserving(): Boolean {\n        return isObserving\n    }\n}\n\n// Task based\nclass FirebaseDataSource {\n\n    companion object {\n        val dbInstance = FirebaseDatabase.getInstance()\n    }\n\n    //region Private\n\n    private fun refToPath(path: String): DatabaseReference {\n        return dbInstance.reference.child(path)\n    }\n\n    private fun attachValueListenerToTaskCompletion(src: TaskCompletionSource<DataSnapshot>): ValueEventListener {\n        return (object : ValueEventListener {\n            override fun onCancelled(error: DatabaseError) { src.setException(Exception(error.message)) }\n\n            override fun onDataChange(snapshot: DataSnapshot) { src.setResult(snapshot) }\n        })\n    }\n\n    private fun <T> attachValueListenerToBlock(resultClassName: Class<T>, b: ((Result<T>) -> Unit)): ValueEventListener {\n        return (object : ValueEventListener {\n            override fun onCancelled(error: DatabaseError) { b.invoke(Result.Error(error.message)) }\n\n            override fun onDataChange(snapshot: DataSnapshot) {\n                if (wrapSnapshotToClass(resultClassName, snapshot) == null) {\n                    b.invoke(Result.Error(msg = snapshot.key))\n                } else {\n                    b.invoke(Result.Success(wrapSnapshotToClass(resultClassName, snapshot)))\n                }\n            }\n        })\n    }\n\n    private fun <T> attachValueListenerToBlockWithList(resultClassName: Class<T>, b: ((Result<MutableList<T>>) -> Unit)): ValueEventListener {\n        return (object : ValueEventListener {\n            override fun onCancelled(error: DatabaseError) { b.invoke(Result.Error(error.message)) }\n            override fun onDataChange(snapshot: DataSnapshot) {\n                b.invoke(Result.Success(wrapSnapshotToArrayList(resultClassName, snapshot)))\n            }\n        })\n    }\n\n    private fun <T> attachChildListenerToBlock(resultClassName: Class<T>, b: ((Result<T>) -> Unit)): ChildEventListener {\n        return (object : ChildEventListener {\n            override fun onChildAdded(snapshot: DataSnapshot, previousChildName: String?) {\n                b.invoke(Result.Success(wrapSnapshotToClass(resultClassName, snapshot)))\n            }\n\n            override fun onCancelled(error: DatabaseError) { b.invoke(Result.Error(error.message)) }\n\n            override fun onChildMoved(snapshot: DataSnapshot, previousChildName: String?) {}\n\n            override fun onChildChanged(snapshot: DataSnapshot, previousChildName: String?) {}\n\n            override fun onChildRemoved(snapshot: DataSnapshot) {}\n        })\n    }\n\n    //endregion\n\n    //region Update\n\n    fun updateUserProfileImageUrl(userID: String, url: String) {\n        refToPath(\"users/$userID/info/profileImageUrl\").setValue(url)\n    }\n\n    fun updateUserStatus(userID: String, status: String) {\n        refToPath(\"users/$userID/info/status\").setValue(status)\n    }\n\n    fun updateLastMessage(chatID: String, message: Message) {\n        refToPath(\"chats/$chatID/lastMessage\").setValue(message)\n    }\n\n    fun updateNewFriend(myUser: UserFriend, otherUser: UserFriend) {\n        refToPath(\"users/${myUser.userID}/friends/${otherUser.userID}\").setValue(otherUser)\n        refToPath(\"users/${otherUser.userID}/friends/${myUser.userID}\").setValue(myUser)\n    }\n\n    fun updateNewSentRequest(userID: String, userRequest: UserRequest) {\n        refToPath(\"users/${userID}/sentRequests/${userRequest.userID}\").setValue(userRequest)\n    }\n\n    fun updateNewNotification(otherUserID: String, userNotification: UserNotification) {\n        refToPath(\"users/${otherUserID}/notifications/${userNotification.userID}\").setValue(userNotification)\n    }\n\n    fun updateNewUser(user: User) {\n        refToPath(\"users/${user.info.id}\").setValue(user)\n    }\n\n    fun updateNewChat(chat: Chat) {\n        refToPath(\"chats/${chat.info.id}\").setValue(chat)\n    }\n\n    fun pushNewMessage(messagesID: String, message: Message) {\n        refToPath(\"messages/$messagesID\").push().setValue(message)\n    }\n\n    //endregion\n\n    //region Remove\n\n    fun removeNotification(userID: String, notificationID: String) {\n        refToPath(\"users/${userID}/notifications/$notificationID\").setValue(null)\n    }\n\n    fun removeFriend(userID: String, friendID: String) {\n        refToPath(\"users/${userID}/friends/$friendID\").setValue(null)\n        refToPath(\"users/${friendID}/friends/$userID\").setValue(null)\n    }\n\n    fun removeSentRequest(userID: String, sentRequestID: String) {\n        refToPath(\"users/${userID}/sentRequests/$sentRequestID\").setValue(null)\n    }\n\n    fun removeChat(chatID: String) {\n        refToPath(\"chats/$chatID\").setValue(null)\n    }\n\n    fun removeMessages(messagesID: String) {\n        refToPath(\"messages/$messagesID\").setValue(null)\n    }\n\n    //endregion\n\n    //region Load\n    fun loadUserTask(userID: String): Task<DataSnapshot> {\n        val src = TaskCompletionSource<DataSnapshot>()\n        val listener = attachValueListenerToTaskCompletion(src)\n        refToPath(\"users/$userID\").addListenerForSingleValueEvent(listener)\n        return src.task\n    }\n\n    fun loadUserInfoTask(userID: String): Task<DataSnapshot> {\n        val src = TaskCompletionSource<DataSnapshot>()\n        val listener = attachValueListenerToTaskCompletion(src)\n        refToPath(\"users/$userID/info\").addListenerForSingleValueEvent(listener)\n        return src.task\n    }\n\n    fun loadUsersTask(): Task<DataSnapshot> {\n        val src = TaskCompletionSource<DataSnapshot>()\n        val listener = attachValueListenerToTaskCompletion(src)\n        refToPath(\"users\").addListenerForSingleValueEvent(listener)\n        return src.task\n    }\n\n    fun loadFriendsTask(userID: String): Task<DataSnapshot> {\n        val src = TaskCompletionSource<DataSnapshot>()\n        val listener = attachValueListenerToTaskCompletion(src)\n        refToPath(\"users/$userID/friends\").addListenerForSingleValueEvent(listener)\n        return src.task\n    }\n\n    fun loadChatTask(chatID: String): Task<DataSnapshot> {\n        val src = TaskCompletionSource<DataSnapshot>()\n        val listener = attachValueListenerToTaskCompletion(src)\n        refToPath(\"chats/$chatID\").addListenerForSingleValueEvent(listener)\n        return src.task\n    }\n\n    fun loadNotificationsTask(userID: String): Task<DataSnapshot> {\n        val src = TaskCompletionSource<DataSnapshot>()\n        val listener = attachValueListenerToTaskCompletion(src)\n        refToPath(\"users/$userID/notifications\").addListenerForSingleValueEvent(listener)\n        return src.task\n    }\n\n    //endregion\n\n    //region Value Observers\n\n    fun <T> attachUserObserver(resultClassName: Class<T>, userID: String, refObs: FirebaseReferenceValueObserver, b: ((Result<T>) -> Unit)) {\n        val listener = attachValueListenerToBlock(resultClassName, b)\n        refObs.start(listener, refToPath(\"users/$userID\"))\n    }\n\n    fun <T> attachUserInfoObserver(resultClassName: Class<T>, userID: String, refObs: FirebaseReferenceValueObserver, b: ((Result<T>) -> Unit)) {\n        val listener = attachValueListenerToBlock(resultClassName, b)\n        refObs.start(listener, refToPath(\"users/$userID/info\"))\n    }\n\n    fun <T> attachUserNotificationsObserver(resultClassName: Class<T>, userID: String, firebaseReferenceValueObserver: FirebaseReferenceValueObserver,\n        b: ((Result<MutableList<T>>) -> Unit)\n    ) {\n        val listener = attachValueListenerToBlockWithList(resultClassName, b)\n        firebaseReferenceValueObserver.start(listener, refToPath(\"users/$userID/notifications\"))\n    }\n\n    fun <T> attachMessagesObserver(resultClassName: Class<T>, messagesID: String, refObs: FirebaseReferenceChildObserver, b: ((Result<T>) -> Unit)) {\n        val listener = attachChildListenerToBlock(resultClassName, b)\n        refObs.start(listener, refToPath(\"messages/$messagesID\"))\n    }\n\n    fun <T> attachChatObserver(resultClassName: Class<T>, chatID: String, refObs: FirebaseReferenceValueObserver, b: ((Result<T>) -> Unit)) {\n        val listener = attachValueListenerToBlock(resultClassName, b)\n        refObs.start(listener, refToPath(\"chats/$chatID\"))\n    }\n\n    //endregion\n}\n"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/data/db/remote/FirebaseStorageSource.kt",
    "content": "package com.fredrikbogg.android_chat_app.data.db.remote\n\nimport android.net.Uri\nimport com.google.android.gms.tasks.Task\nimport com.google.firebase.storage.FirebaseStorage\n\n// Task based\nclass FirebaseStorageSource {\n    private val storageInstance = FirebaseStorage.getInstance()\n\n    fun uploadUserImage(userID: String, bArr: ByteArray): Task<Uri> {\n        val path = \"user_photos/$userID/profile_image\"\n        val ref = storageInstance.reference.child(path)\n\n        return ref.putBytes(bArr).continueWithTask {\n            ref.downloadUrl\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/data/db/repository/AuthRepository.kt",
    "content": "package com.fredrikbogg.android_chat_app.data.db.repository\n\nimport com.fredrikbogg.android_chat_app.data.model.CreateUser\nimport com.fredrikbogg.android_chat_app.data.model.Login\nimport com.fredrikbogg.android_chat_app.data.db.remote.FirebaseAuthSource\nimport com.fredrikbogg.android_chat_app.data.db.remote.FirebaseAuthStateObserver\nimport com.fredrikbogg.android_chat_app.data.Result\nimport com.google.firebase.auth.FirebaseUser\n\nclass AuthRepository{\n    private val firebaseAuthService = FirebaseAuthSource()\n\n    fun observeAuthState(stateObserver: FirebaseAuthStateObserver, b: ((Result<FirebaseUser>) -> Unit)){\n        firebaseAuthService.attachAuthStateObserver(stateObserver,b)\n    }\n\n    fun loginUser(login: Login, b: ((Result<FirebaseUser>) -> Unit)) {\n        b.invoke(Result.Loading)\n        firebaseAuthService.loginWithEmailAndPassword(login).addOnSuccessListener {\n            b.invoke(Result.Success(it.user))\n        }.addOnFailureListener {\n            b.invoke(Result.Error(msg = it.message))\n        }\n    }\n\n    fun createUser(createUser: CreateUser, b: ((Result<FirebaseUser>) -> Unit)) {\n        b.invoke(Result.Loading)\n        firebaseAuthService.createUser(createUser).addOnSuccessListener {\n            b.invoke(Result.Success(it.user))\n        }.addOnFailureListener {\n            b.invoke(Result.Error(msg = it.message))\n        }\n    }\n\n    fun logoutUser() {\n        firebaseAuthService.logout()\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/data/db/repository/DatabaseRepository.kt",
    "content": "package com.fredrikbogg.android_chat_app.data.db.repository\n\nimport com.fredrikbogg.android_chat_app.data.db.entity.*\nimport com.fredrikbogg.android_chat_app.data.db.remote.FirebaseDataSource\nimport com.fredrikbogg.android_chat_app.data.db.remote.FirebaseReferenceChildObserver\nimport com.fredrikbogg.android_chat_app.data.db.remote.FirebaseReferenceValueObserver\nimport com.fredrikbogg.android_chat_app.data.Result\nimport com.fredrikbogg.android_chat_app.util.wrapSnapshotToArrayList\nimport com.fredrikbogg.android_chat_app.util.wrapSnapshotToClass\n\n\nclass DatabaseRepository {\n    private val firebaseDatabaseService = FirebaseDataSource()\n\n    //region Update\n    fun updateUserStatus(userID: String, status: String) {\n        firebaseDatabaseService.updateUserStatus(userID, status)\n    }\n\n    fun updateNewMessage(messagesID: String, message: Message) {\n        firebaseDatabaseService.pushNewMessage(messagesID, message)\n    }\n\n    fun updateNewUser(user: User) {\n        firebaseDatabaseService.updateNewUser(user)\n    }\n\n    fun updateNewFriend(myUser: UserFriend, otherUser: UserFriend) {\n        firebaseDatabaseService.updateNewFriend(myUser, otherUser)\n    }\n\n    fun updateNewSentRequest(userID: String, userRequest: UserRequest) {\n        firebaseDatabaseService.updateNewSentRequest(userID, userRequest)\n    }\n\n    fun updateNewNotification(otherUserID: String, userNotification: UserNotification) {\n        firebaseDatabaseService.updateNewNotification(otherUserID, userNotification)\n    }\n\n    fun updateChatLastMessage(chatID: String, message: Message) {\n        firebaseDatabaseService.updateLastMessage(chatID, message)\n    }\n\n    fun updateNewChat(chat: Chat){\n        firebaseDatabaseService.updateNewChat(chat)\n    }\n\n    fun updateUserProfileImageUrl(userID: String, url: String){\n        firebaseDatabaseService.updateUserProfileImageUrl(userID, url)\n    }\n\n    //endregion\n\n    //region Remove\n    fun removeNotification(userID: String, notificationID: String) {\n        firebaseDatabaseService.removeNotification(userID, notificationID)\n    }\n\n    fun removeFriend(userID: String, friendID: String) {\n        firebaseDatabaseService.removeFriend(userID, friendID)\n    }\n\n    fun removeSentRequest(otherUserID: String, myUserID: String) {\n        firebaseDatabaseService.removeSentRequest(otherUserID, myUserID)\n    }\n\n    fun removeChat(chatID: String) {\n        firebaseDatabaseService.removeChat(chatID)\n    }\n\n    fun removeMessages(messagesID: String){\n        firebaseDatabaseService.removeMessages(messagesID)\n    }\n\n    //endregion\n\n    //region Load Single\n\n    fun loadUser(userID: String, b: ((Result<User>) -> Unit)) {\n        firebaseDatabaseService.loadUserTask(userID).addOnSuccessListener {\n            b.invoke(Result.Success(wrapSnapshotToClass(User::class.java, it)))\n        }.addOnFailureListener { b.invoke(Result.Error(it.message)) }\n    }\n\n    fun loadUserInfo(userID: String, b: ((Result<UserInfo>) -> Unit)) {\n        firebaseDatabaseService.loadUserInfoTask(userID).addOnSuccessListener {\n            b.invoke(Result.Success(wrapSnapshotToClass(UserInfo::class.java, it)))\n        }.addOnFailureListener { b.invoke(Result.Error(it.message)) }\n    }\n\n    fun loadChat(chatID: String, b: ((Result<Chat>) -> Unit)) {\n        firebaseDatabaseService.loadChatTask(chatID).addOnSuccessListener {\n            b.invoke(Result.Success(wrapSnapshotToClass(Chat::class.java, it)))\n        }.addOnFailureListener { b.invoke(Result.Error(it.message)) }\n    }\n\n    //endregion\n\n    //region Load List\n\n    fun loadUsers(b: ((Result<MutableList<User>>) -> Unit)) {\n        b.invoke(Result.Loading)\n        firebaseDatabaseService.loadUsersTask().addOnSuccessListener {\n            val usersList = wrapSnapshotToArrayList(User::class.java, it)\n            b.invoke(Result.Success(usersList))\n        }.addOnFailureListener { b.invoke(Result.Error(it.message)) }\n    }\n\n    fun loadFriends(userID: String, b: ((Result<List<UserFriend>>) -> Unit)) {\n        b.invoke(Result.Loading)\n        firebaseDatabaseService.loadFriendsTask(userID).addOnSuccessListener {\n            val friendsList = wrapSnapshotToArrayList(UserFriend::class.java, it)\n            b.invoke(Result.Success(friendsList))\n        }.addOnFailureListener { b.invoke(Result.Error(it.message)) }\n    }\n\n    fun loadNotifications(userID: String, b: ((Result<MutableList<UserNotification>>) -> Unit)) {\n        b.invoke(Result.Loading)\n        firebaseDatabaseService.loadNotificationsTask(userID).addOnSuccessListener {\n            val notificationsList = wrapSnapshotToArrayList(UserNotification::class.java, it)\n            b.invoke(Result.Success(notificationsList))\n        }.addOnFailureListener { b.invoke(Result.Error(it.message)) }\n    }\n\n    //endregion\n\n    //#region Load and Observe\n\n    fun loadAndObserveUser(userID: String, observer: FirebaseReferenceValueObserver, b: ((Result<User>) -> Unit)) {\n        firebaseDatabaseService.attachUserObserver(User::class.java, userID, observer, b)\n    }\n\n    fun loadAndObserveUserInfo(userID: String, observer: FirebaseReferenceValueObserver, b: ((Result<UserInfo>) -> Unit)) {\n        firebaseDatabaseService.attachUserInfoObserver(UserInfo::class.java, userID, observer, b)\n    }\n\n    fun loadAndObserveUserNotifications(userID: String, observer: FirebaseReferenceValueObserver, b: ((Result<MutableList<UserNotification>>) -> Unit)){\n        firebaseDatabaseService.attachUserNotificationsObserver(UserNotification::class.java, userID, observer, b)\n    }\n\n    fun loadAndObserveMessagesAdded(messagesID: String, observer: FirebaseReferenceChildObserver, b: ((Result<Message>) -> Unit)) {\n        firebaseDatabaseService.attachMessagesObserver(Message::class.java, messagesID, observer, b)\n    }\n\n    fun loadAndObserveChat(chatID: String, observer: FirebaseReferenceValueObserver, b: ((Result<Chat>) -> Unit)) {\n        firebaseDatabaseService.attachChatObserver(Chat::class.java, chatID, observer, b)\n    }\n\n    //endregion\n}\n\n"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/data/db/repository/StorageRepository.kt",
    "content": "package com.fredrikbogg.android_chat_app.data.db.repository\n\nimport android.net.Uri\nimport com.fredrikbogg.android_chat_app.data.db.remote.FirebaseStorageSource\nimport com.fredrikbogg.android_chat_app.data.Result\n\nclass StorageRepository {\n    private val firebaseStorageService = FirebaseStorageSource()\n\n    fun updateUserProfileImage(userID: String, byteArray: ByteArray, b: (Result<Uri>) -> Unit) {\n        b.invoke(Result.Loading)\n        firebaseStorageService.uploadUserImage(userID, byteArray).addOnSuccessListener {\n            b.invoke((Result.Success(it)))\n        }.addOnFailureListener {\n            b.invoke(Result.Error(it.message))\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/data/model/ChatWithUserInfo.kt",
    "content": "package com.fredrikbogg.android_chat_app.data.model\n\nimport com.fredrikbogg.android_chat_app.data.db.entity.Chat\nimport com.fredrikbogg.android_chat_app.data.db.entity.UserInfo\n\ndata class ChatWithUserInfo(\n    var mChat: Chat,\n    var mUserInfo: UserInfo\n)\n"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/data/model/CreateUser.kt",
    "content": "package com.fredrikbogg.android_chat_app.data.model\n\ndata class CreateUser(\n    var displayName: String = \"\",\n    var email: String = \"\",\n    var password: String = \"\"\n)"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/data/model/Login.kt",
    "content": "package com.fredrikbogg.android_chat_app.data.model\n\ndata class Login(\n    var email: String = \"\",\n    var password: String = \"\"\n)"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/DefaultBindings.kt",
    "content": "package com.fredrikbogg.android_chat_app.ui\n\nimport android.annotation.SuppressLint\nimport android.widget.ImageView\nimport android.widget.TextView\nimport androidx.databinding.BindingAdapter\nimport androidx.recyclerview.widget.RecyclerView\nimport com.fredrikbogg.android_chat_app.R\nimport com.squareup.picasso.Picasso\nimport jp.wasabeef.picasso.transformations.BlurTransformation\nimport java.text.SimpleDateFormat\nimport java.util.*\nimport java.util.concurrent.TimeUnit\n\n\n@BindingAdapter(\"bind_image_url_blur\")\nfun bindBlurImageWithPicasso(imageView: ImageView, url: String?) {\n    if (!url.isNullOrBlank()) {\n        Picasso.get().load(url).error(R.drawable.ic_baseline_error_24)\n            .transform(BlurTransformation(imageView.context, 15, 1)).into(imageView)\n    }\n}\n\n@BindingAdapter(\"bind_image_url\")\nfun bindImageWithPicasso(imageView: ImageView, url: String?) {\n    when (url) {\n        null -> Unit\n        \"\" -> imageView.setBackgroundResource(R.drawable.ic_baseline_person_24)\n        else -> Picasso.get().load(url).error(R.drawable.ic_baseline_error_24).into(imageView)\n    }\n}\n\n@SuppressLint(\"SimpleDateFormat\")\n@BindingAdapter(\"bind_epochTimeMsToDate_with_days_ago\")\nfun TextView.bindEpochTimeMsToDateWithDaysAgo(epochTimeMs: Long) {\n    val numOfDays = TimeUnit.MILLISECONDS.toDays(Date().time - epochTimeMs)\n\n    this.text = when {\n        numOfDays == 1.toLong() -> \"Yesterday\"\n        numOfDays > 1.toLong() -> \"$numOfDays days ago\"\n        else -> {\n            val pat =\n                SimpleDateFormat().toLocalizedPattern().replace(\"\\\\W?[YyMd]+\\\\W?\".toRegex(), \" \")\n            val formatter = SimpleDateFormat(pat, Locale.getDefault())\n            formatter.format(Date(epochTimeMs))\n        }\n    }\n}\n\n@SuppressLint(\"SimpleDateFormat\")\n@BindingAdapter(\"bind_epochTimeMsToDate\")\nfun TextView.bindEpochTimeMsToDate(epochTimeMs: Long) {\n    if (epochTimeMs > 0) {\n        val currentTimeMs = Date().time\n        val numOfDays = TimeUnit.MILLISECONDS.toDays(currentTimeMs - epochTimeMs)\n\n        val replacePattern = when {\n            numOfDays >= 1.toLong() -> \"Yy\"\n            else -> \"YyMd\"\n        }\n        val pat = SimpleDateFormat().toLocalizedPattern().replace(\"\\\\W?[$replacePattern]+\\\\W?\".toRegex(), \" \")\n        val formatter = SimpleDateFormat(pat, Locale.getDefault())\n        this.text = formatter.format(Date(epochTimeMs))\n    }\n}\n\n@BindingAdapter(\"bind_disable_item_animator\")\nfun bindDisableRecyclerViewItemAnimator(recyclerView: RecyclerView, disable: Boolean) {\n    if (disable) {\n        recyclerView.itemAnimator = null\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/DefaultViewModel.kt",
    "content": "package com.fredrikbogg.android_chat_app.ui\n\nimport androidx.lifecycle.LiveData\nimport androidx.lifecycle.MutableLiveData\nimport androidx.lifecycle.ViewModel\nimport com.fredrikbogg.android_chat_app.data.Event\nimport com.fredrikbogg.android_chat_app.data.Result\n\nabstract class DefaultViewModel : ViewModel() {\n    protected val mSnackBarText = MutableLiveData<Event<String>>()\n    val snackBarText: LiveData<Event<String>> = mSnackBarText\n\n    private val mDataLoading = MutableLiveData<Event<Boolean>>()\n    val dataLoading: LiveData<Event<Boolean>> = mDataLoading\n\n    protected fun <T> onResult(mutableLiveData: MutableLiveData<T>? = null, result: Result<T>) {\n        when (result) {\n            is Result.Loading -> mDataLoading.value = Event(true)\n\n            is Result.Error -> {\n                mDataLoading.value = Event(false)\n                result.msg?.let { mSnackBarText.value = Event(it) }\n            }\n\n            is Result.Success -> {\n                mDataLoading.value = Event(false)\n                result.data?.let { mutableLiveData?.value = it }\n                result.msg?.let { mSnackBarText.value = Event(it) }\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/chat/ChatFragment.kt",
    "content": "package com.fredrikbogg.android_chat_app.ui.chat\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.viewModels\nimport androidx.navigation.fragment.findNavController\nimport androidx.recyclerview.widget.RecyclerView\nimport com.fredrikbogg.android_chat_app.databinding.FragmentChatBinding\nimport com.fredrikbogg.android_chat_app.databinding.ToolbarAddonChatBinding\nimport kotlinx.android.synthetic.main.fragment_chat.*\n\n\nclass ChatFragment : Fragment() {\n\n    companion object {\n        const val ARGS_KEY_USER_ID = \"bundle_user_id\"\n        const val ARGS_KEY_OTHER_USER_ID = \"bundle_other_user_id\"\n        const val ARGS_KEY_CHAT_ID = \"bundle_other_chat_id\"\n    }\n\n    private val viewModel: ChatViewModel by viewModels {\n        ChatViewModelFactory(\n            requireArguments().getString(ARGS_KEY_USER_ID)!!,\n            requireArguments().getString(ARGS_KEY_OTHER_USER_ID)!!,\n            requireArguments().getString(ARGS_KEY_CHAT_ID)!!\n        )\n    }\n\n    private lateinit var viewDataBinding: FragmentChatBinding\n    private lateinit var listAdapter: MessagesListAdapter\n    private lateinit var listAdapterObserver: RecyclerView.AdapterDataObserver\n    private lateinit var toolbarAddonChatBinding: ToolbarAddonChatBinding\n\n    override fun onDestroy() {\n        super.onDestroy()\n        removeCustomToolbar()\n    }\n\n    override fun onCreateView(\n        inflater: LayoutInflater, container: ViewGroup?,\n        savedInstanceState: Bundle?\n    ): View? {\n        viewDataBinding =\n            FragmentChatBinding.inflate(inflater, container, false).apply { viewmodel = viewModel }\n        viewDataBinding.lifecycleOwner = this.viewLifecycleOwner\n        setHasOptionsMenu(true)\n\n        toolbarAddonChatBinding =\n            ToolbarAddonChatBinding.inflate(inflater, container, false)\n                .apply { viewmodel = viewModel }\n        toolbarAddonChatBinding.lifecycleOwner = this.viewLifecycleOwner\n\n        return viewDataBinding.root\n    }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        super.onActivityCreated(savedInstanceState)\n        setupCustomToolbar()\n        setupListAdapter()\n    }\n\n    override fun onOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            android.R.id.home -> {\n                findNavController().popBackStack()\n                return true\n            }\n        }\n        return super.onOptionsItemSelected(item)\n    }\n\n    private fun removeCustomToolbar() {\n        val supportActionBar = (activity as AppCompatActivity?)!!.supportActionBar\n        supportActionBar!!.setDisplayShowCustomEnabled(false)\n        supportActionBar.customView = null\n    }\n\n    private fun setupCustomToolbar() {\n        val supportActionBar = (activity as AppCompatActivity?)!!.supportActionBar\n        supportActionBar!!.setDisplayShowCustomEnabled(true)\n        supportActionBar.customView = toolbarAddonChatBinding.root\n    }\n\n    private fun setupListAdapter() {\n        val viewModel = viewDataBinding.viewmodel\n        if (viewModel != null) {\n            listAdapterObserver = (object : RecyclerView.AdapterDataObserver() {\n                override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {\n                    messagesRecyclerView.scrollToPosition(positionStart)\n                }\n            })\n            listAdapter =\n                MessagesListAdapter(viewModel, requireArguments().getString(ARGS_KEY_USER_ID)!!)\n            listAdapter.registerAdapterDataObserver(listAdapterObserver)\n            viewDataBinding.messagesRecyclerView.adapter = listAdapter\n        } else {\n            throw Exception(\"The viewmodel is not initialized\")\n        }\n    }\n\n    override fun onDestroyView() {\n        super.onDestroyView()\n        listAdapter.unregisterAdapterDataObserver(listAdapterObserver)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/chat/ChatViewModel.kt",
    "content": "package com.fredrikbogg.android_chat_app.ui.chat\n\nimport androidx.lifecycle.*\nimport com.fredrikbogg.android_chat_app.data.db.entity.Chat\nimport com.fredrikbogg.android_chat_app.data.db.entity.Message\nimport com.fredrikbogg.android_chat_app.data.db.entity.UserInfo\nimport com.fredrikbogg.android_chat_app.data.db.remote.FirebaseReferenceChildObserver\nimport com.fredrikbogg.android_chat_app.data.db.remote.FirebaseReferenceValueObserver\nimport com.fredrikbogg.android_chat_app.data.db.repository.DatabaseRepository\nimport com.fredrikbogg.android_chat_app.ui.DefaultViewModel\nimport com.fredrikbogg.android_chat_app.data.Result\nimport com.fredrikbogg.android_chat_app.util.addNewItem\n\nclass ChatViewModelFactory(private val myUserID: String, private val otherUserID: String, private val chatID: String) :\n    ViewModelProvider.Factory {\n    override fun <T : ViewModel?> create(modelClass: Class<T>): T {\n        return ChatViewModel(myUserID, otherUserID, chatID) as T\n    }\n}\n\nclass ChatViewModel(private val myUserID: String, private val otherUserID: String, private val chatID: String) : DefaultViewModel() {\n\n    private val dbRepository: DatabaseRepository = DatabaseRepository()\n\n    private val _otherUser: MutableLiveData<UserInfo> = MutableLiveData()\n    private val _addedMessage = MutableLiveData<Message>()\n\n    private val fbRefMessagesChildObserver = FirebaseReferenceChildObserver()\n    private val fbRefUserInfoObserver = FirebaseReferenceValueObserver()\n\n    val messagesList = MediatorLiveData<MutableList<Message>>()\n    val newMessageText = MutableLiveData<String>()\n    val otherUser: LiveData<UserInfo> = _otherUser\n\n    init {\n        setupChat()\n        checkAndUpdateLastMessageSeen()\n    }\n\n    override fun onCleared() {\n        super.onCleared()\n        fbRefMessagesChildObserver.clear()\n        fbRefUserInfoObserver.clear()\n    }\n\n    private fun checkAndUpdateLastMessageSeen() {\n        dbRepository.loadChat(chatID) { result: Result<Chat> ->\n            if (result is Result.Success && result.data != null) {\n                result.data.lastMessage.let {\n                    if (!it.seen && it.senderID != myUserID) {\n                        it.seen = true\n                        dbRepository.updateChatLastMessage(chatID, it)\n                    }\n                }\n            }\n        }\n    }\n\n    private fun setupChat() {\n        dbRepository.loadAndObserveUserInfo(otherUserID, fbRefUserInfoObserver) { result: Result<UserInfo> ->\n            onResult(_otherUser, result)\n            if (result is Result.Success && !fbRefMessagesChildObserver.isObserving()) {\n                loadAndObserveNewMessages()\n            }\n        }\n    }\n\n    private fun loadAndObserveNewMessages() {\n        messagesList.addSource(_addedMessage) { messagesList.addNewItem(it) }\n\n        dbRepository.loadAndObserveMessagesAdded(\n            chatID,\n            fbRefMessagesChildObserver\n        ) { result: Result<Message> ->\n            onResult(_addedMessage, result)\n        }\n    }\n\n    fun sendMessagePressed() {\n        if (!newMessageText.value.isNullOrBlank()) {\n            val newMsg = Message(myUserID, newMessageText.value!!)\n            dbRepository.updateNewMessage(chatID, newMsg)\n            dbRepository.updateChatLastMessage(chatID, newMsg)\n            newMessageText.value = null\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/chat/MessagesBindings.kt",
    "content": "package com.fredrikbogg.android_chat_app.ui.chat\n\nimport android.view.View\nimport androidx.databinding.BindingAdapter\nimport androidx.recyclerview.widget.RecyclerView\nimport com.fredrikbogg.android_chat_app.data.db.entity.Message\nimport kotlin.math.abs\n\n@BindingAdapter(\"bind_messages_list\")\nfun bindMessagesList(listView: RecyclerView, items: List<Message>?) {\n    items?.let {\n        (listView.adapter as MessagesListAdapter).submitList(items)\n        listView.scrollToPosition(items.size - 1)\n    }\n}\n\n@BindingAdapter(\"bind_message\", \"bind_message_viewModel\")\nfun View.bindShouldMessageShowTimeText(message: Message, viewModel: ChatViewModel) {\n    val halfHourInMilli = 1800000\n    val index = viewModel.messagesList.value!!.indexOf(message)\n\n    if (index == 0) {\n        this.visibility = View.VISIBLE\n    } else {\n        val messageBefore = viewModel.messagesList.value!![index - 1]\n\n        if (abs(messageBefore.epochTimeMs - message.epochTimeMs) > halfHourInMilli) {\n            this.visibility = View.VISIBLE\n        } else {\n            this.visibility = View.GONE\n        }\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/chat/MessagesListAdapter.kt",
    "content": "package com.fredrikbogg.android_chat_app.ui.chat\n\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.DiffUtil\nimport androidx.recyclerview.widget.ListAdapter\nimport androidx.recyclerview.widget.RecyclerView\nimport com.fredrikbogg.android_chat_app.data.db.entity.Message\nimport com.fredrikbogg.android_chat_app.databinding.ListItemMessageReceivedBinding\nimport com.fredrikbogg.android_chat_app.databinding.ListItemMessageSentBinding\n\nclass MessagesListAdapter internal constructor(private val viewModel: ChatViewModel, private val userId: String) : ListAdapter<Message, RecyclerView.ViewHolder>(MessageDiffCallback()) {\n\n    private val holderTypeMessageReceived = 1\n    private val holderTypeMessageSent = 2\n\n    class ReceivedViewHolder(private val binding: ListItemMessageReceivedBinding) :\n        RecyclerView.ViewHolder(binding.root) {\n        fun bind(viewModel: ChatViewModel, item: Message) {\n            binding.viewmodel = viewModel\n            binding.message = item\n            binding.executePendingBindings()\n        }\n    }\n\n    class SentViewHolder(private val binding: ListItemMessageSentBinding) :\n        RecyclerView.ViewHolder(binding.root) {\n        fun bind(viewModel: ChatViewModel, item: Message) {\n            binding.viewmodel = viewModel\n            binding.message = item\n            binding.executePendingBindings()\n        }\n    }\n\n    override fun getItemViewType(position: Int): Int {\n        return if (getItem(position).senderID != userId) {\n            holderTypeMessageReceived\n        } else {\n            holderTypeMessageSent\n        }\n    }\n\n    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n        when (holder.itemViewType) {\n            holderTypeMessageSent -> (holder as SentViewHolder).bind(\n                viewModel,\n                getItem(position)\n            )\n            holderTypeMessageReceived -> (holder as ReceivedViewHolder).bind(\n                viewModel,\n                getItem(position)\n            )\n        }\n    }\n\n    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {\n        val layoutInflater = LayoutInflater.from(parent.context)\n\n        return when (viewType) {\n            holderTypeMessageSent -> {\n                val binding = ListItemMessageSentBinding.inflate(layoutInflater, parent, false)\n                SentViewHolder(binding)\n            }\n            holderTypeMessageReceived -> {\n                val binding = ListItemMessageReceivedBinding.inflate(layoutInflater, parent, false)\n                ReceivedViewHolder(binding)\n            }\n            else -> {\n                throw Exception(\"Error reading holder type\")\n            }\n        }\n    }\n}\n\nclass MessageDiffCallback : DiffUtil.ItemCallback<Message>() {\n    override fun areItemsTheSame(oldItem: Message, newItem: Message): Boolean {\n        return oldItem == newItem\n    }\n\n    override fun areContentsTheSame(oldItem: Message, newItem: Message): Boolean {\n        return oldItem.epochTimeMs == newItem.epochTimeMs\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/chats/ChatsBindings.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage com.fredrikbogg.android_chat_app.ui.chats\n\nimport android.view.View\nimport android.widget.TextView\nimport androidx.databinding.BindingAdapter\nimport androidx.recyclerview.widget.RecyclerView\nimport com.fredrikbogg.android_chat_app.R\nimport com.fredrikbogg.android_chat_app.data.model.ChatWithUserInfo\nimport com.fredrikbogg.android_chat_app.data.db.entity.Message\n\n@BindingAdapter(\"bind_chats_list\")\nfun bindChatsList(listView: RecyclerView, items: List<ChatWithUserInfo>?) {\n    items?.let { (listView.adapter as ChatsListAdapter).submitList(items) }\n}\n\n@BindingAdapter(\"bind_chat_message_text\", \"bind_chat_message_text_viewModel\")\nfun TextView.bindMessageYouToText(message: Message, viewModel: ChatsViewModel) {\n    this.text = if (message.senderID == viewModel.myUserID) {\n        \"You: \" + message.text\n    } else {\n        message.text\n    }\n}\n\n@BindingAdapter(\"bind_message_view\", \"bind_message_textView\", \"bind_message\", \"bind_myUserID\")\nfun View.bindMessageSeen(view: View, textView: TextView, message: Message, myUserID: String) {\n    if (message.senderID != myUserID && !message.seen) {\n        view.visibility = View.VISIBLE\n        textView.setTextAppearance(R.style.MessageNotSeen)\n//        textView.alpha = 1f\n    } else {\n        view.visibility = View.INVISIBLE\n        textView.setTextAppearance(R.style.MessageSeen)\n//        textView.alpha = 1f\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/chats/ChatsFragment.kt",
    "content": "package com.fredrikbogg.android_chat_app.ui.chats\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.core.os.bundleOf\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.viewModels\nimport androidx.navigation.fragment.findNavController\nimport com.fredrikbogg.android_chat_app.App\nimport com.fredrikbogg.android_chat_app.R\nimport com.fredrikbogg.android_chat_app.data.model.ChatWithUserInfo\nimport com.fredrikbogg.android_chat_app.databinding.FragmentChatsBinding\nimport com.fredrikbogg.android_chat_app.data.EventObserver\nimport com.fredrikbogg.android_chat_app.ui.chat.ChatFragment\nimport com.fredrikbogg.android_chat_app.util.convertTwoUserIDs\n\nclass ChatsFragment : Fragment() {\n\n    private val viewModel: ChatsViewModel by viewModels { ChatsViewModelFactory(App.myUserID) }\n    private lateinit var viewDataBinding: FragmentChatsBinding\n    private lateinit var listAdapter: ChatsListAdapter\n\n    override fun onCreateView(\n        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?\n    ): View? {\n        viewDataBinding =\n            FragmentChatsBinding.inflate(inflater, container, false).apply { viewmodel = viewModel }\n        viewDataBinding.lifecycleOwner = this.viewLifecycleOwner\n        return viewDataBinding.root\n    }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        super.onActivityCreated(savedInstanceState)\n        setupListAdapter()\n        setupObservers()\n    }\n\n    private fun setupListAdapter() {\n        val viewModel = viewDataBinding.viewmodel\n        if (viewModel != null) {\n            listAdapter = ChatsListAdapter(viewModel)\n            viewDataBinding.chatsRecyclerView.adapter = listAdapter\n        } else {\n            throw Exception(\"The viewmodel is not initialized\")\n        }\n    }\n\n    private fun setupObservers() {\n        viewModel.selectedChat.observe(viewLifecycleOwner,\n            EventObserver { navigateToChat(it) })\n    }\n\n    private fun navigateToChat(chatWithUserInfo: ChatWithUserInfo) {\n        val bundle = bundleOf(\n            ChatFragment.ARGS_KEY_USER_ID to App.myUserID,\n            ChatFragment.ARGS_KEY_OTHER_USER_ID to chatWithUserInfo.mUserInfo.id,\n            ChatFragment.ARGS_KEY_CHAT_ID to convertTwoUserIDs(App.myUserID, chatWithUserInfo.mUserInfo.id)\n        )\n        findNavController().navigate(R.id.action_navigation_chats_to_chatFragment, bundle)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/chats/ChatsListAdapter.kt",
    "content": "package com.fredrikbogg.android_chat_app.ui.chats\n\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.DiffUtil\nimport androidx.recyclerview.widget.ListAdapter\nimport androidx.recyclerview.widget.RecyclerView\nimport com.fredrikbogg.android_chat_app.data.model.ChatWithUserInfo\nimport com.fredrikbogg.android_chat_app.databinding.ListItemChatBinding\n\nclass ChatsListAdapter internal constructor(private val viewModel: ChatsViewModel) :\n    ListAdapter<(ChatWithUserInfo), ChatsListAdapter.ViewHolder>(ChatDiffCallback()) {\n\n    class ViewHolder(private val binding: ListItemChatBinding) :\n        RecyclerView.ViewHolder(binding.root) {\n        fun bind(viewModel: ChatsViewModel, item: ChatWithUserInfo) {\n            binding.viewmodel = viewModel\n            binding.chatwithuserinfo = item\n            binding.executePendingBindings()\n        }\n    }\n\n    override fun onBindViewHolder(holder: ViewHolder, position: Int) {\n        holder.bind(viewModel, getItem(position))\n    }\n\n    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {\n        val layoutInflater = LayoutInflater.from(parent.context)\n        val binding = ListItemChatBinding.inflate(layoutInflater, parent, false)\n        return ViewHolder(binding)\n    }\n}\n\nclass ChatDiffCallback : DiffUtil.ItemCallback<ChatWithUserInfo>() {\n    override fun areItemsTheSame(oldItem: ChatWithUserInfo, itemWithUserInfo: ChatWithUserInfo): Boolean {\n        return oldItem == itemWithUserInfo\n    }\n\n    override fun areContentsTheSame(oldItem: ChatWithUserInfo, itemWithUserInfo: ChatWithUserInfo): Boolean {\n        return oldItem.mChat.info.id == itemWithUserInfo.mChat.info.id\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/chats/ChatsViewModel.kt",
    "content": "package com.fredrikbogg.android_chat_app.ui.chats\n\nimport androidx.lifecycle.*\nimport com.fredrikbogg.android_chat_app.data.Event\nimport com.fredrikbogg.android_chat_app.data.Result\nimport com.fredrikbogg.android_chat_app.data.db.entity.Chat\nimport com.fredrikbogg.android_chat_app.data.model.ChatWithUserInfo\nimport com.fredrikbogg.android_chat_app.data.db.entity.UserFriend\nimport com.fredrikbogg.android_chat_app.data.db.entity.UserInfo\nimport com.fredrikbogg.android_chat_app.data.db.remote.FirebaseReferenceValueObserver\nimport com.fredrikbogg.android_chat_app.data.db.repository.DatabaseRepository\nimport com.fredrikbogg.android_chat_app.ui.DefaultViewModel\nimport com.fredrikbogg.android_chat_app.util.addNewItem\nimport com.fredrikbogg.android_chat_app.util.convertTwoUserIDs\nimport com.fredrikbogg.android_chat_app.util.updateItemAt\n\n\nclass ChatsViewModelFactory(private val myUserID: String) :\n    ViewModelProvider.Factory {\n    override fun <T : ViewModel?> create(modelClass: Class<T>): T {\n        return ChatsViewModel(myUserID) as T\n    }\n}\n\nclass ChatsViewModel(val myUserID: String) : DefaultViewModel() {\n\n    private val repository: DatabaseRepository = DatabaseRepository()\n    private val firebaseReferenceObserverList = ArrayList<FirebaseReferenceValueObserver>()\n    private val _updatedChatWithUserInfo = MutableLiveData<ChatWithUserInfo>()\n    private val _selectedChat = MutableLiveData<Event<ChatWithUserInfo>>()\n\n    var selectedChat: LiveData<Event<ChatWithUserInfo>> = _selectedChat\n    val chatsList = MediatorLiveData<MutableList<ChatWithUserInfo>>()\n\n    init {\n        chatsList.addSource(_updatedChatWithUserInfo) { newChat ->\n            val chat = chatsList.value?.find { it.mChat.info.id == newChat.mChat.info.id }\n            if (chat == null) {\n                chatsList.addNewItem(newChat)\n            } else {\n                chatsList.updateItemAt(newChat, chatsList.value!!.indexOf(chat))\n            }\n        }\n        setupChats()\n    }\n\n    override fun onCleared() {\n        super.onCleared()\n        firebaseReferenceObserverList.forEach { it.clear() }\n    }\n\n    private fun setupChats() {\n        loadFriends()\n    }\n\n    private fun loadFriends() {\n        repository.loadFriends(myUserID) { result: Result<List<UserFriend>> ->\n            onResult(null, result)\n            if (result is Result.Success) result.data?.forEach { loadUserInfo(it) }\n        }\n    }\n\n    private fun loadUserInfo(userFriend: UserFriend) {\n        repository.loadUserInfo(userFriend.userID) { result: Result<UserInfo> ->\n            onResult(null, result)\n            if (result is Result.Success) result.data?.let { loadAndObserveChat(it) }\n        }\n    }\n\n    private fun loadAndObserveChat(userInfo: UserInfo) {\n        val observer = FirebaseReferenceValueObserver()\n        firebaseReferenceObserverList.add(observer)\n        repository.loadAndObserveChat(convertTwoUserIDs(myUserID, userInfo.id), observer) { result: Result<Chat> ->\n            if (result is Result.Success) {\n                _updatedChatWithUserInfo.value = result.data?.let { ChatWithUserInfo(it, userInfo) }\n            } else if (result is Result.Error) {\n                chatsList.value?.let {\n                    val newList = mutableListOf<ChatWithUserInfo>().apply { addAll(it) }\n                    newList.removeIf { it2 -> result.msg.toString().contains(it2.mUserInfo.id) }\n                    chatsList.value = newList\n                }\n            }\n        }\n    }\n\n    fun selectChatWithUserInfoPressed(chat: ChatWithUserInfo) {\n        _selectedChat.value = Event(chat)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/main/MainActivity.kt",
    "content": "package com.fredrikbogg.android_chat_app.ui.main\n\nimport android.os.Bundle\nimport android.view.View\nimport android.widget.ProgressBar\nimport androidx.activity.viewModels\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.appcompat.widget.Toolbar\nimport androidx.navigation.findNavController\nimport androidx.navigation.ui.AppBarConfiguration\nimport androidx.navigation.ui.setupActionBarWithNavController\nimport androidx.navigation.ui.setupWithNavController\nimport com.fredrikbogg.android_chat_app.R\nimport com.fredrikbogg.android_chat_app.data.db.remote.FirebaseDataSource\nimport com.fredrikbogg.android_chat_app.util.forceHideKeyboard\nimport com.google.android.material.badge.BadgeDrawable\nimport com.google.android.material.bottomnavigation.BottomNavigationView\n\n\nclass MainActivity : AppCompatActivity() {\n\n    private lateinit var navView: BottomNavigationView\n    private lateinit var mainProgressBar: ProgressBar\n    private lateinit var mainToolbar: Toolbar\n    private lateinit var notificationsBadge: BadgeDrawable\n    private val viewModel: MainViewModel by viewModels()\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_main)\n\n        mainToolbar = findViewById(R.id.main_toolbar)\n        navView = findViewById(R.id.nav_view)\n        mainProgressBar = findViewById(R.id.main_progressBar)\n\n        notificationsBadge =\n            navView.getOrCreateBadge(R.id.navigation_notifications).apply { isVisible = false }\n\n        setSupportActionBar(mainToolbar)\n\n        val navController = findNavController(R.id.nav_host_fragment)\n        navController.addOnDestinationChangedListener { _, destination, _ ->\n\n            when (destination.id) {\n                R.id.profileFragment -> navView.visibility = View.GONE\n                R.id.chatFragment -> navView.visibility = View.GONE\n                R.id.startFragment -> navView.visibility = View.GONE\n                R.id.loginFragment -> navView.visibility = View.GONE\n                R.id.createAccountFragment -> navView.visibility = View.GONE\n                else -> navView.visibility = View.VISIBLE\n            }\n            showGlobalProgressBar(false)\n            currentFocus?.rootView?.forceHideKeyboard()\n        }\n\n        val appBarConfiguration = AppBarConfiguration(\n            setOf(\n                R.id.navigation_chats,\n                R.id.navigation_notifications,\n                R.id.navigation_users,\n                R.id.navigation_settings,\n                R.id.startFragment\n            )\n        )\n\n        setupActionBarWithNavController(navController, appBarConfiguration)\n        navView.setupWithNavController(navController)\n    }\n\n    override fun onPause() {\n        super.onPause()\n        FirebaseDataSource.dbInstance.goOffline()\n    }\n\n    override fun onResume() {\n        FirebaseDataSource.dbInstance.goOnline()\n        setupViewModelObservers()\n        super.onResume()\n    }\n\n    private fun setupViewModelObservers() {\n        viewModel.userNotificationsList.observe(this, {\n            if (it.size > 0) {\n                notificationsBadge.number = it.size\n                notificationsBadge.isVisible = true\n            } else {\n                notificationsBadge.isVisible = false\n            }\n        })\n    }\n\n    fun showGlobalProgressBar(show: Boolean) {\n        if (show) mainProgressBar.visibility = View.VISIBLE\n        else mainProgressBar.visibility = View.GONE\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/main/MainViewModel.kt",
    "content": "package com.fredrikbogg.android_chat_app.ui.main\n\nimport androidx.lifecycle.LiveData\nimport androidx.lifecycle.MutableLiveData\nimport androidx.lifecycle.ViewModel\nimport com.fredrikbogg.android_chat_app.App\nimport com.fredrikbogg.android_chat_app.data.db.entity.UserNotification\nimport com.fredrikbogg.android_chat_app.data.db.remote.FirebaseAuthStateObserver\nimport com.fredrikbogg.android_chat_app.data.db.remote.FirebaseReferenceConnectedObserver\nimport com.fredrikbogg.android_chat_app.data.db.remote.FirebaseReferenceValueObserver\nimport com.fredrikbogg.android_chat_app.data.db.repository.AuthRepository\nimport com.fredrikbogg.android_chat_app.data.db.repository.DatabaseRepository\nimport com.fredrikbogg.android_chat_app.data.Result\nimport com.google.firebase.auth.FirebaseUser\n\n\nclass MainViewModel : ViewModel() {\n\n    private val dbRepository = DatabaseRepository()\n    private val authRepository = AuthRepository()\n\n    private val _userNotificationsList = MutableLiveData<MutableList<UserNotification>>()\n\n    private val fbRefNotificationsObserver = FirebaseReferenceValueObserver()\n    private val fbAuthStateObserver = FirebaseAuthStateObserver()\n    private val fbRefConnectedObserver = FirebaseReferenceConnectedObserver()\n    private var userID = App.myUserID\n\n    var userNotificationsList: LiveData<MutableList<UserNotification>> = _userNotificationsList\n\n    init {\n       setupAuthObserver()\n    }\n\n    override fun onCleared() {\n        super.onCleared()\n        fbRefNotificationsObserver.clear()\n        fbRefConnectedObserver.clear()\n        fbAuthStateObserver.clear()\n    }\n\n    private fun setupAuthObserver(){\n        authRepository.observeAuthState(fbAuthStateObserver) { result: Result<FirebaseUser> ->\n            if (result is Result.Success) {\n                userID = result.data!!.uid\n                startObservingNotifications()\n                fbRefConnectedObserver.start(userID)\n            } else {\n                fbRefConnectedObserver.clear()\n                stopObservingNotifications()\n            }\n        }\n    }\n\n    private fun startObservingNotifications() {\n        dbRepository.loadAndObserveUserNotifications(userID, fbRefNotificationsObserver) { result: Result<MutableList<UserNotification>> ->\n            if (result is Result.Success) {\n                _userNotificationsList.value = result.data\n            }\n        }\n    }\n\n    private fun stopObservingNotifications() {\n        fbRefNotificationsObserver.clear()\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/notifications/NotificationsBindings.kt",
    "content": "package com.fredrikbogg.android_chat_app.ui.notifications\n\nimport androidx.databinding.BindingAdapter\nimport androidx.recyclerview.widget.RecyclerView\nimport com.fredrikbogg.android_chat_app.data.db.entity.UserInfo\n\n@BindingAdapter(\"bind_notifications_list\")\nfun bindNotificationsList(listView: RecyclerView, items: List<UserInfo>?) {\n    items?.let { (listView.adapter as NotificationsListAdapter).submitList(items) }\n}\n"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/notifications/NotificationsFragment.kt",
    "content": "package com.fredrikbogg.android_chat_app.ui.notifications\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.viewModels\nimport com.fredrikbogg.android_chat_app.App\nimport com.fredrikbogg.android_chat_app.databinding.FragmentNotificationsBinding\n\nclass NotificationsFragment : Fragment() {\n\n    private val viewModel: NotificationsViewModel by viewModels { NotificationsViewModelFactory(App.myUserID) }\n    private lateinit var viewDataBinding: FragmentNotificationsBinding\n    private lateinit var listAdapter: NotificationsListAdapter\n\n    override fun onCreateView(\n        inflater: LayoutInflater, container: ViewGroup?,\n        savedInstanceState: Bundle?\n    ): View? {\n        viewDataBinding = FragmentNotificationsBinding.inflate(inflater, container, false)\n            .apply { viewmodel = viewModel }\n        viewDataBinding.lifecycleOwner = this.viewLifecycleOwner\n        return viewDataBinding.root\n    }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        super.onActivityCreated(savedInstanceState)\n        setupListAdapter()\n    }\n\n    private fun setupListAdapter() {\n        val viewModel = viewDataBinding.viewmodel\n        if (viewModel != null) {\n            listAdapter = NotificationsListAdapter(viewModel)\n            viewDataBinding.usersRecyclerView.adapter = listAdapter\n        } else {\n            throw Exception(\"The viewmodel is not initialized\")\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/notifications/NotificationsListAdapter.kt",
    "content": "package com.fredrikbogg.android_chat_app.ui.notifications\n\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.DiffUtil\nimport androidx.recyclerview.widget.ListAdapter\nimport androidx.recyclerview.widget.RecyclerView\nimport com.fredrikbogg.android_chat_app.data.db.entity.UserInfo\nimport com.fredrikbogg.android_chat_app.databinding.ListItemNotificationBinding\n\n\nclass NotificationsListAdapter internal constructor(private val viewModel: NotificationsViewModel) :\n    ListAdapter<UserInfo, NotificationsListAdapter.ViewHolder>(UserInfoDiffCallback()) {\n\n    class ViewHolder(private val binding: ListItemNotificationBinding) :\n        RecyclerView.ViewHolder(binding.root) {\n        fun bind(viewModel: NotificationsViewModel, item: UserInfo) {\n            binding.viewmodel = viewModel\n            binding.userinfo = item\n            binding.executePendingBindings()\n        }\n    }\n\n    override fun onBindViewHolder(holder: ViewHolder, position: Int) {\n        holder.bind(viewModel, getItem(position))\n    }\n\n    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {\n        val layoutInflater = LayoutInflater.from(parent.context)\n        val binding = ListItemNotificationBinding.inflate(layoutInflater, parent, false)\n        return ViewHolder(binding)\n    }\n}\n\nclass UserInfoDiffCallback : DiffUtil.ItemCallback<UserInfo>() {\n    override fun areItemsTheSame(oldItem: UserInfo, newItem: UserInfo): Boolean {\n        return oldItem == newItem\n    }\n\n    override fun areContentsTheSame(oldItem: UserInfo, newItem: UserInfo): Boolean {\n        return oldItem.id == newItem.id\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/notifications/NotificationsViewModel.kt",
    "content": "package com.fredrikbogg.android_chat_app.ui.notifications\n\nimport androidx.lifecycle.MediatorLiveData\nimport androidx.lifecycle.MutableLiveData\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.ViewModelProvider\nimport com.fredrikbogg.android_chat_app.data.db.entity.*\nimport com.fredrikbogg.android_chat_app.data.db.repository.DatabaseRepository\nimport com.fredrikbogg.android_chat_app.ui.DefaultViewModel\nimport com.fredrikbogg.android_chat_app.data.Result\nimport com.fredrikbogg.android_chat_app.util.addNewItem\nimport com.fredrikbogg.android_chat_app.util.removeItem\nimport com.fredrikbogg.android_chat_app.util.convertTwoUserIDs\n\nclass NotificationsViewModelFactory(private val myUserID: String) :\n    ViewModelProvider.Factory {\n    override fun <T : ViewModel?> create(modelClass: Class<T>): T {\n        return NotificationsViewModel(myUserID) as T\n    }\n}\n\nclass NotificationsViewModel(private val myUserID: String) : DefaultViewModel() {\n\n    private val dbRepository: DatabaseRepository = DatabaseRepository()\n    private val updatedUserInfo = MutableLiveData<UserInfo>()\n    private val userNotificationsList = MutableLiveData<MutableList<UserNotification>>()\n\n    val usersInfoList = MediatorLiveData<MutableList<UserInfo>>()\n\n    init {\n        usersInfoList.addSource(updatedUserInfo) { usersInfoList.addNewItem(it) }\n        loadNotifications()\n    }\n\n    private fun loadNotifications() {\n        dbRepository.loadNotifications(myUserID) { result: Result<MutableList<UserNotification>> ->\n            onResult(userNotificationsList, result)\n            if (result is Result.Success) result.data?.forEach { loadUserInfo(it) }\n        }\n    }\n\n    private fun loadUserInfo(userNotification: UserNotification) {\n        dbRepository.loadUserInfo(userNotification.userID) { result: Result<UserInfo> ->\n            onResult(updatedUserInfo, result)\n        }\n    }\n\n    private fun updateNotification(otherUserInfo: UserInfo, removeOnly: Boolean) {\n        val userNotification = userNotificationsList.value?.find {\n            it.userID == otherUserInfo.id\n        }\n\n        if (userNotification != null) {\n            if (!removeOnly) {\n                dbRepository.updateNewFriend(UserFriend(myUserID), UserFriend(otherUserInfo.id))\n                val newChat = Chat().apply {\n                    info.id = convertTwoUserIDs(myUserID, otherUserInfo.id)\n                    lastMessage = Message(seen = true, text = \"Say hello!\")\n                }\n                dbRepository.updateNewChat(newChat)\n            }\n            dbRepository.removeNotification(myUserID, otherUserInfo.id)\n            dbRepository.removeSentRequest(otherUserInfo.id, myUserID)\n\n            usersInfoList.removeItem(otherUserInfo)\n            userNotificationsList.removeItem(userNotification)\n        }\n    }\n\n    fun acceptNotificationPressed(userInfo: UserInfo) {\n        updateNotification(userInfo, false)\n    }\n\n    fun declineNotificationPressed(userInfo: UserInfo) {\n        updateNotification(userInfo, true)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/profile/ProfileFragment.kt",
    "content": "package com.fredrikbogg.android_chat_app.ui.profile\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.viewModels\nimport androidx.navigation.fragment.findNavController\nimport com.fredrikbogg.android_chat_app.App\nimport com.fredrikbogg.android_chat_app.databinding.FragmentProfileBinding\nimport com.fredrikbogg.android_chat_app.data.EventObserver\nimport com.fredrikbogg.android_chat_app.util.showSnackBar\nimport com.fredrikbogg.android_chat_app.ui.main.MainActivity\nimport com.fredrikbogg.android_chat_app.util.forceHideKeyboard\n\n\nclass ProfileFragment : Fragment() {\n\n    companion object {\n        const val ARGS_KEY_USER_ID = \"bundle_user_id\"\n    }\n\n    private val viewModel: ProfileViewModel by viewModels {\n        ProfileViewModelFactory(App.myUserID, requireArguments().getString(ARGS_KEY_USER_ID)!!)\n    }\n\n    private lateinit var viewDataBinding: FragmentProfileBinding\n\n    override fun onCreateView(\n        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?\n    ): View? {\n        viewDataBinding = FragmentProfileBinding.inflate(inflater, container, false)\n            .apply { viewmodel = viewModel }\n        viewDataBinding.lifecycleOwner = this.viewLifecycleOwner\n        setHasOptionsMenu(true)\n        return viewDataBinding.root\n    }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        super.onActivityCreated(savedInstanceState)\n        setupObservers()\n    }\n\n    override fun onOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            android.R.id.home -> {\n                findNavController().popBackStack()\n                return true\n            }\n        }\n        return super.onOptionsItemSelected(item)\n    }\n\n    private fun setupObservers() {\n        viewModel.dataLoading.observe(viewLifecycleOwner,\n            EventObserver { (activity as MainActivity).showGlobalProgressBar(it) })\n\n        viewModel.snackBarText.observe(viewLifecycleOwner,\n            EventObserver { text ->\n                view?.showSnackBar(text)\n                view?.forceHideKeyboard()\n            })\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/profile/ProfileViewModel.kt",
    "content": "package com.fredrikbogg.android_chat_app.ui.profile\n\nimport androidx.lifecycle.*\nimport com.fredrikbogg.android_chat_app.data.db.entity.*\nimport com.fredrikbogg.android_chat_app.data.db.remote.FirebaseReferenceValueObserver\nimport com.fredrikbogg.android_chat_app.data.db.repository.DatabaseRepository\nimport com.fredrikbogg.android_chat_app.ui.DefaultViewModel\nimport com.fredrikbogg.android_chat_app.data.Result\nimport com.fredrikbogg.android_chat_app.util.convertTwoUserIDs\n\n\nclass ProfileViewModelFactory(private val myUserID: String, private val otherUserID: String) :\n    ViewModelProvider.Factory {\n    override fun <T : ViewModel?> create(modelClass: Class<T>): T {\n        return ProfileViewModel(myUserID, otherUserID) as T\n    }\n}\n\nenum class LayoutState {\n    IS_FRIEND, NOT_FRIEND, ACCEPT_DECLINE, REQUEST_SENT\n}\n\nclass ProfileViewModel(private val myUserID: String, private val userID: String) :\n    DefaultViewModel() {\n\n    private val repository: DatabaseRepository = DatabaseRepository()\n    private val firebaseReferenceObserver = FirebaseReferenceValueObserver()\n    private val _myUser: MutableLiveData<User> = MutableLiveData()\n    private val _otherUser: MutableLiveData<User> = MutableLiveData()\n\n    val otherUser: LiveData<User> = _otherUser\n    val layoutState = MediatorLiveData<LayoutState>()\n\n    init {\n        layoutState.addSource(_myUser) { updateLayoutState(it, _otherUser.value) }\n        setupProfile()\n    }\n\n    override fun onCleared() {\n        super.onCleared()\n        firebaseReferenceObserver.clear()\n    }\n\n    private fun updateLayoutState(myUser: User?, otherUser: User?) {\n        if (myUser != null && otherUser != null) {\n            layoutState.value = when {\n                myUser.friends[otherUser.info.id] != null -> LayoutState.IS_FRIEND\n                myUser.notifications[otherUser.info.id] != null -> LayoutState.ACCEPT_DECLINE\n                myUser.sentRequests[otherUser.info.id] != null -> LayoutState.REQUEST_SENT\n                else -> LayoutState.NOT_FRIEND\n            }\n        }\n    }\n\n    private fun setupProfile() {\n        repository.loadUser(userID) { result: Result<User> ->\n            onResult(_otherUser, result)\n            if (result is Result.Success) {\n                repository.loadAndObserveUser(myUserID, firebaseReferenceObserver) { result2: Result<User> ->\n                    onResult(_myUser, result2)\n                }\n            }\n        }\n    }\n\n    fun addFriendPressed() {\n        repository.updateNewSentRequest(myUserID, UserRequest(_otherUser.value!!.info.id))\n        repository.updateNewNotification(_otherUser.value!!.info.id, UserNotification(myUserID))\n    }\n\n    fun removeFriendPressed() {\n        repository.removeFriend(myUserID, _otherUser.value!!.info.id)\n        repository.removeChat(convertTwoUserIDs(myUserID, _otherUser.value!!.info.id))\n        repository.removeMessages(convertTwoUserIDs(myUserID, _otherUser.value!!.info.id))\n    }\n\n    fun acceptFriendRequestPressed() {\n        repository.updateNewFriend(UserFriend(myUserID), UserFriend(_otherUser.value!!.info.id))\n\n        val newChat = Chat().apply {\n            info.id = convertTwoUserIDs(myUserID, _otherUser.value!!.info.id)\n            lastMessage = Message(seen = true, text = \"Say hello!\")\n        }\n\n        repository.updateNewChat(newChat)\n        repository.removeNotification(myUserID, _otherUser.value!!.info.id)\n        repository.removeSentRequest(_otherUser.value!!.info.id, myUserID)\n    }\n\n    fun declineFriendRequestPressed() {\n        repository.removeSentRequest(myUserID, _otherUser.value!!.info.id)\n        repository.removeNotification(myUserID, _otherUser.value!!.info.id)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/settings/SettingsFragment.kt",
    "content": "package com.fredrikbogg.android_chat_app.ui.settings\n\nimport android.app.Activity.RESULT_OK\nimport android.content.Context\nimport android.content.Intent\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.EditText\nimport androidx.appcompat.app.AlertDialog\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.viewModels\nimport androidx.navigation.fragment.findNavController\nimport com.fredrikbogg.android_chat_app.App\nimport com.fredrikbogg.android_chat_app.R\nimport com.fredrikbogg.android_chat_app.databinding.FragmentSettingsBinding\nimport com.fredrikbogg.android_chat_app.data.EventObserver\nimport com.fredrikbogg.android_chat_app.util.SharedPreferencesUtil\nimport com.fredrikbogg.android_chat_app.util.convertFileToByteArray\n\n\nclass SettingsFragment : Fragment() {\n\n    private val viewModel: SettingsViewModel by viewModels { SettingsViewModelFactory(App.myUserID) }\n\n    private lateinit var viewDataBinding: FragmentSettingsBinding\n    private val selectImageIntentRequestCode = 1\n\n    override fun onCreateView(\n        inflater: LayoutInflater, container: ViewGroup?,\n        savedInstanceState: Bundle?\n    ): View? {\n        viewDataBinding = FragmentSettingsBinding.inflate(inflater, container, false)\n            .apply { viewmodel = viewModel }\n        viewDataBinding.lifecycleOwner = this.viewLifecycleOwner\n        setHasOptionsMenu(true)\n\n        return viewDataBinding.root\n    }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        super.onActivityCreated(savedInstanceState)\n        setupObservers()\n    }\n\n    override fun onOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            android.R.id.home -> {\n                findNavController().popBackStack()\n                return true\n            }\n        }\n        return super.onOptionsItemSelected(item)\n    }\n\n    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {\n        super.onActivityResult(requestCode, resultCode, data)\n        if (resultCode == RESULT_OK && requestCode == selectImageIntentRequestCode) {\n            data?.data?.let { uri ->\n                convertFileToByteArray(requireContext(), uri).let {\n                    viewModel.changeUserImage(it)\n                }\n            }\n        }\n    }\n\n    private fun setupObservers() {\n        viewModel.editStatusEvent.observe(viewLifecycleOwner,\n            EventObserver { showEditStatusDialog() })\n\n        viewModel.editImageEvent.observe(viewLifecycleOwner,\n            EventObserver { startSelectImageIntent() })\n\n        viewModel.logoutEvent.observe(viewLifecycleOwner,\n            EventObserver {\n                SharedPreferencesUtil.removeUserID(requireContext())\n                navigateToStart()\n            })\n    }\n\n    private fun showEditStatusDialog() {\n        val input = EditText(requireActivity() as Context)\n        AlertDialog.Builder(requireActivity()).apply {\n            setTitle(\"Status:\")\n            setView(input)\n            setPositiveButton(\"Ok\") { _, _ ->\n                val textInput = input.text.toString()\n                if (!textInput.isBlank() && textInput.length <= 40) {\n                    viewModel.changeUserStatus(textInput)\n                }\n            }\n            setNegativeButton(\"Cancel\") { _, _ -> }\n            show()\n        }\n    }\n\n    private fun startSelectImageIntent() {\n        val selectImageIntent = Intent(Intent.ACTION_GET_CONTENT)\n        selectImageIntent.type = \"image/*\"\n        startActivityForResult(selectImageIntent, selectImageIntentRequestCode)\n    }\n\n    private fun navigateToStart() {\n        findNavController().navigate(R.id.action_navigation_settings_to_startFragment)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/settings/SettingsViewModel.kt",
    "content": "package com.fredrikbogg.android_chat_app.ui.settings\n\nimport android.net.Uri\nimport androidx.lifecycle.LiveData\nimport androidx.lifecycle.MutableLiveData\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.ViewModelProvider\nimport com.fredrikbogg.android_chat_app.data.db.entity.UserInfo\nimport com.fredrikbogg.android_chat_app.data.db.remote.FirebaseReferenceValueObserver\nimport com.fredrikbogg.android_chat_app.data.db.repository.AuthRepository\nimport com.fredrikbogg.android_chat_app.data.db.repository.DatabaseRepository\nimport com.fredrikbogg.android_chat_app.data.db.repository.StorageRepository\nimport com.fredrikbogg.android_chat_app.ui.DefaultViewModel\nimport com.fredrikbogg.android_chat_app.data.Event\nimport com.fredrikbogg.android_chat_app.data.Result\n\nclass SettingsViewModelFactory(private val userID: String) : ViewModelProvider.Factory {\n    override fun <T : ViewModel?> create(modelClass: Class<T>): T {\n        return SettingsViewModel(userID) as T\n    }\n}\n\nclass SettingsViewModel(private val userID: String) : DefaultViewModel() {\n\n    private val dbRepository: DatabaseRepository = DatabaseRepository()\n    private val storageRepository = StorageRepository()\n    private val authRepository = AuthRepository()\n\n    private val _userInfo: MutableLiveData<UserInfo> = MutableLiveData()\n    val userInfo: LiveData<UserInfo> = _userInfo\n\n    private val _editStatusEvent = MutableLiveData<Event<Unit>>()\n    val editStatusEvent: LiveData<Event<Unit>> = _editStatusEvent\n\n    private val _editImageEvent = MutableLiveData<Event<Unit>>()\n    val editImageEvent: LiveData<Event<Unit>> = _editImageEvent\n\n    private val _logoutEvent = MutableLiveData<Event<Unit>>()\n    val logoutEvent: LiveData<Event<Unit>> = _logoutEvent\n\n    private val firebaseReferenceObserver = FirebaseReferenceValueObserver()\n\n    init {\n        loadAndObserveUserInfo()\n    }\n\n    override fun onCleared() {\n        super.onCleared()\n        firebaseReferenceObserver.clear()\n    }\n\n    private fun loadAndObserveUserInfo() {\n        dbRepository.loadAndObserveUserInfo(userID, firebaseReferenceObserver)\n        { result: Result<UserInfo> -> onResult(_userInfo, result) }\n    }\n\n    fun changeUserStatus(status: String) {\n        dbRepository.updateUserStatus(userID, status)\n    }\n\n    fun changeUserImage(byteArray: ByteArray) {\n        storageRepository.updateUserProfileImage(userID, byteArray) { result: Result<Uri> ->\n            onResult(null, result)\n            if (result is Result.Success) {\n                dbRepository.updateUserProfileImageUrl(userID, result.data.toString())\n            }\n        }\n    }\n\n    fun changeUserImagePressed() {\n        _editImageEvent.value = Event(Unit)\n    }\n\n    fun changeUserStatusPressed() {\n        _editStatusEvent.value = Event(Unit)\n    }\n\n    fun logoutUserPressed() {\n        authRepository.logoutUser()\n        _logoutEvent.value = Event(Unit)\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/start/StartFragment.kt",
    "content": "package com.fredrikbogg.android_chat_app.ui.start\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.viewModels\nimport androidx.navigation.fragment.findNavController\nimport com.fredrikbogg.android_chat_app.R\nimport com.fredrikbogg.android_chat_app.databinding.FragmentStartBinding\nimport com.fredrikbogg.android_chat_app.data.EventObserver\nimport com.fredrikbogg.android_chat_app.util.SharedPreferencesUtil\n\nclass StartFragment : Fragment() {\n\n    private val viewModel by viewModels<StartViewModel>()\n    private lateinit var viewDataBinding: FragmentStartBinding\n\n    override fun onCreateView(\n        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?\n    ): View? {\n        viewDataBinding =\n            FragmentStartBinding.inflate(inflater, container, false).apply { viewmodel = viewModel }\n        viewDataBinding.lifecycleOwner = this.viewLifecycleOwner\n        setHasOptionsMenu(false)\n        return viewDataBinding.root\n    }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        super.onActivityCreated(savedInstanceState)\n        setupObservers()\n\n        if (userIsAlreadyLoggedIn()) {\n            navigateDirectlyToChats()\n        }\n    }\n\n    private fun userIsAlreadyLoggedIn(): Boolean {\n        return SharedPreferencesUtil.getUserID(requireContext()) != null\n    }\n\n    private fun setupObservers() {\n        viewModel.loginEvent.observe(viewLifecycleOwner, EventObserver { navigateToLogin() })\n        viewModel.createAccountEvent.observe(\n            viewLifecycleOwner, EventObserver { navigateToCreateAccount() })\n    }\n\n    private fun navigateDirectlyToChats() {\n        findNavController().navigate(R.id.action_startFragment_to_navigation_chats)\n    }\n\n    private fun navigateToLogin() {\n        findNavController().navigate(R.id.action_startFragment_to_loginFragment)\n    }\n\n    private fun navigateToCreateAccount() {\n        findNavController().navigate(R.id.action_startFragment_to_createAccountFragment)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/start/StartViewModel.kt",
    "content": "package com.fredrikbogg.android_chat_app.ui.start\n\nimport androidx.lifecycle.LiveData\nimport androidx.lifecycle.MutableLiveData\nimport androidx.lifecycle.ViewModel\nimport com.fredrikbogg.android_chat_app.data.Event\n\nclass StartViewModel : ViewModel() {\n\n    private val _loginEvent = MutableLiveData<Event<Unit>>()\n    private val _createAccountEvent = MutableLiveData<Event<Unit>>()\n\n    val loginEvent: LiveData<Event<Unit>> = _loginEvent\n    val createAccountEvent: LiveData<Event<Unit>> = _createAccountEvent\n\n    fun goToLoginPressed() {\n        _loginEvent.value = Event(Unit)\n    }\n\n    fun goToCreateAccountPressed() {\n        _createAccountEvent.value = Event(Unit)\n    }\n}\n\n\n"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/start/createAccount/CreateAccountFragment.kt",
    "content": "package com.fredrikbogg.android_chat_app.ui.start.createAccount\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.viewModels\nimport androidx.navigation.fragment.findNavController\nimport com.fredrikbogg.android_chat_app.data.EventObserver\nimport com.fredrikbogg.android_chat_app.R\nimport com.fredrikbogg.android_chat_app.databinding.FragmentCreateAccountBinding\nimport com.fredrikbogg.android_chat_app.ui.main.MainActivity\nimport com.fredrikbogg.android_chat_app.util.SharedPreferencesUtil\nimport com.fredrikbogg.android_chat_app.util.forceHideKeyboard\nimport com.fredrikbogg.android_chat_app.util.showSnackBar\n\nclass CreateAccountFragment : Fragment() {\n\n    private val viewModel by viewModels<CreateAccountViewModel>()\n    private lateinit var viewDataBinding: FragmentCreateAccountBinding\n\n    override fun onCreateView(\n        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?\n    ): View? {\n        viewDataBinding = FragmentCreateAccountBinding.inflate(inflater, container, false)\n            .apply { viewmodel = viewModel }\n        viewDataBinding.lifecycleOwner = this.viewLifecycleOwner\n        setHasOptionsMenu(true)\n        return viewDataBinding.root\n    }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        super.onActivityCreated(savedInstanceState)\n        setupObservers()\n    }\n\n    override fun onOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            android.R.id.home -> {\n                findNavController().popBackStack()\n                return true\n            }\n        }\n        return super.onOptionsItemSelected(item)\n    }\n\n    private fun setupObservers() {\n        viewModel.dataLoading.observe(viewLifecycleOwner,\n            EventObserver { (activity as MainActivity).showGlobalProgressBar(it) })\n\n        viewModel.snackBarText.observe(viewLifecycleOwner,\n            EventObserver { text ->\n                view?.showSnackBar(text)\n                view?.forceHideKeyboard()\n            })\n\n        viewModel.isCreatedEvent.observe(viewLifecycleOwner, EventObserver {\n            SharedPreferencesUtil.saveUserID(requireContext(), it.uid)\n            navigateToChats()\n        })\n    }\n\n    private fun navigateToChats() {\n        findNavController().navigate(R.id.action_createAccountFragment_to_navigation_chats)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/start/createAccount/CreateAccountViewModel.kt",
    "content": "package com.fredrikbogg.android_chat_app.ui.start.createAccount\n\nimport androidx.lifecycle.LiveData\nimport androidx.lifecycle.MutableLiveData\nimport com.fredrikbogg.android_chat_app.data.Event\nimport com.fredrikbogg.android_chat_app.data.Result\nimport com.fredrikbogg.android_chat_app.data.db.entity.User\nimport com.fredrikbogg.android_chat_app.data.db.repository.AuthRepository\nimport com.fredrikbogg.android_chat_app.data.db.repository.DatabaseRepository\nimport com.fredrikbogg.android_chat_app.data.model.CreateUser\nimport com.fredrikbogg.android_chat_app.ui.DefaultViewModel\nimport com.fredrikbogg.android_chat_app.util.isEmailValid\nimport com.fredrikbogg.android_chat_app.util.isTextValid\nimport com.google.firebase.auth.FirebaseUser\n\nclass CreateAccountViewModel : DefaultViewModel() {\n\n    private val dbRepository = DatabaseRepository()\n    private val authRepository = AuthRepository()\n    private val mIsCreatedEvent = MutableLiveData<Event<FirebaseUser>>()\n\n    val isCreatedEvent: LiveData<Event<FirebaseUser>> = mIsCreatedEvent\n    val displayNameText = MutableLiveData<String>() // Two way\n    val emailText = MutableLiveData<String>() // Two way\n    val passwordText = MutableLiveData<String>() // Two way\n    val isCreatingAccount = MutableLiveData<Boolean>()\n\n    private fun createAccount() {\n        isCreatingAccount.value = true\n        val createUser =\n            CreateUser(displayNameText.value!!, emailText.value!!, passwordText.value!!)\n\n        authRepository.createUser(createUser) { result: Result<FirebaseUser> ->\n            onResult(null, result)\n            if (result is Result.Success) {\n                mIsCreatedEvent.value = Event(result.data!!)\n                dbRepository.updateNewUser(User().apply {\n                    info.id = result.data.uid\n                    info.displayName = createUser.displayName\n                })\n            }\n            if (result is Result.Success || result is Result.Error) isCreatingAccount.value = false\n        }\n    }\n\n    fun createAccountPressed() {\n        if (!isTextValid(2, displayNameText.value)) {\n            mSnackBarText.value = Event(\"Display name is too short\")\n            return\n        }\n\n        if (!isEmailValid(emailText.value.toString())) {\n            mSnackBarText.value = Event(\"Invalid email format\")\n            return\n        }\n        if (!isTextValid(6, passwordText.value)) {\n            mSnackBarText.value = Event(\"Password is too short\")\n            return\n        }\n\n        createAccount()\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/start/login/LoginFragment.kt",
    "content": "package com.fredrikbogg.android_chat_app.ui.start.login\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.viewModels\nimport androidx.navigation.fragment.findNavController\nimport com.fredrikbogg.android_chat_app.R\nimport com.fredrikbogg.android_chat_app.databinding.FragmentLoginBinding\nimport com.fredrikbogg.android_chat_app.data.EventObserver\nimport com.fredrikbogg.android_chat_app.util.showSnackBar\nimport com.fredrikbogg.android_chat_app.ui.main.MainActivity\nimport com.fredrikbogg.android_chat_app.util.SharedPreferencesUtil\nimport com.fredrikbogg.android_chat_app.util.forceHideKeyboard\n\nclass LoginFragment : Fragment() {\n\n    private val viewModel by viewModels<LoginViewModel>()\n    private lateinit var viewDataBinding: FragmentLoginBinding\n\n    override fun onCreateView(\n        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?\n    ): View? {\n        viewDataBinding = FragmentLoginBinding.inflate(inflater, container, false)\n            .apply { viewmodel = viewModel }\n        viewDataBinding.lifecycleOwner = this.viewLifecycleOwner\n        setHasOptionsMenu(true)\n        return viewDataBinding.root\n    }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        super.onActivityCreated(savedInstanceState)\n        setupObservers()\n    }\n\n    override fun onOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            android.R.id.home -> {\n                findNavController().popBackStack()\n                return true\n            }\n        }\n        return super.onOptionsItemSelected(item)\n    }\n\n    private fun setupObservers() {\n        viewModel.dataLoading.observe(viewLifecycleOwner,\n            EventObserver { (activity as MainActivity).showGlobalProgressBar(it) })\n\n        viewModel.snackBarText.observe(viewLifecycleOwner,\n            EventObserver { text ->\n                view?.showSnackBar(text)\n                view?.forceHideKeyboard()\n            })\n\n        viewModel.isLoggedInEvent.observe(viewLifecycleOwner, EventObserver {\n            SharedPreferencesUtil.saveUserID(requireContext(), it.uid)\n            navigateToChats()\n        })\n    }\n\n    private fun navigateToChats() {\n        findNavController().navigate(R.id.action_loginFragment_to_navigation_chats)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/start/login/LoginViewModel.kt",
    "content": "package com.fredrikbogg.android_chat_app.ui.start.login\n\nimport androidx.lifecycle.LiveData\nimport androidx.lifecycle.MutableLiveData\nimport com.fredrikbogg.android_chat_app.data.model.Login\nimport com.fredrikbogg.android_chat_app.data.db.repository.AuthRepository\nimport com.fredrikbogg.android_chat_app.ui.DefaultViewModel\nimport com.fredrikbogg.android_chat_app.data.Event\nimport com.fredrikbogg.android_chat_app.data.Result\nimport com.fredrikbogg.android_chat_app.util.isEmailValid\nimport com.fredrikbogg.android_chat_app.util.isTextValid\nimport com.google.firebase.auth.FirebaseUser\n\nclass LoginViewModel : DefaultViewModel() {\n\n    private val authRepository = AuthRepository()\n    private val _isLoggedInEvent = MutableLiveData<Event<FirebaseUser>>()\n\n    val isLoggedInEvent: LiveData<Event<FirebaseUser>> = _isLoggedInEvent\n    val emailText = MutableLiveData<String>() // Two way\n    val passwordText = MutableLiveData<String>() // Two way\n    val isLoggingIn = MutableLiveData<Boolean>() // Two way\n\n    private fun login() {\n        isLoggingIn.value = true\n        val login = Login(emailText.value!!, passwordText.value!!)\n\n        authRepository.loginUser(login) { result: Result<FirebaseUser> ->\n            onResult(null, result)\n            if (result is Result.Success) _isLoggedInEvent.value = Event(result.data!!)\n            if (result is Result.Success || result is Result.Error) isLoggingIn.value = false\n        }\n    }\n\n    fun loginPressed() {\n        if (!isEmailValid(emailText.value.toString())) {\n            mSnackBarText.value = Event(\"Invalid email format\")\n            return\n        }\n        if (!isTextValid(6, passwordText.value)) {\n            mSnackBarText.value = Event(\"Password is too short\")\n            return\n        }\n\n        login()\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/users/UsersBindings.kt",
    "content": "package com.fredrikbogg.android_chat_app.ui.users\n\nimport androidx.databinding.BindingAdapter\nimport androidx.recyclerview.widget.RecyclerView\nimport com.fredrikbogg.android_chat_app.data.db.entity.User\n\n@BindingAdapter(\"bind_users_list\")\nfun bindUsersList(listView: RecyclerView, items: List<User>?) {\n    items?.let { (listView.adapter as UsersListAdapter).submitList(items) }\n}\n\n"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/users/UsersFragment.kt",
    "content": "package com.fredrikbogg.android_chat_app.ui.users\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.core.os.bundleOf\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.viewModels\nimport androidx.navigation.fragment.findNavController\nimport com.fredrikbogg.android_chat_app.App\nimport com.fredrikbogg.android_chat_app.R\nimport com.fredrikbogg.android_chat_app.databinding.FragmentUsersBinding\nimport com.fredrikbogg.android_chat_app.data.EventObserver\nimport com.fredrikbogg.android_chat_app.ui.profile.ProfileFragment\n\n\nclass UsersFragment : Fragment() {\n\n    private val viewModel: UsersViewModel by viewModels { UsersViewModelFactory(App.myUserID) }\n    private lateinit var viewDataBinding: FragmentUsersBinding\n    private lateinit var listAdapter: UsersListAdapter\n\n    override fun onCreateView(\n        inflater: LayoutInflater, container: ViewGroup?,\n        savedInstanceState: Bundle?\n    ): View? {\n        viewDataBinding =\n            FragmentUsersBinding.inflate(inflater, container, false).apply { viewmodel = viewModel }\n        viewDataBinding.lifecycleOwner = this.viewLifecycleOwner\n        return viewDataBinding.root\n    }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        super.onActivityCreated(savedInstanceState)\n        setupListAdapter()\n        setupObservers()\n    }\n\n    private fun setupListAdapter() {\n        val viewModel = viewDataBinding.viewmodel\n        if (viewModel != null) {\n            listAdapter = UsersListAdapter(viewModel)\n            viewDataBinding.usersRecyclerView.adapter = listAdapter\n        } else {\n            throw Exception(\"The viewmodel is not initialized\")\n        }\n    }\n\n    private fun setupObservers() {\n        viewModel.selectedUser.observe(viewLifecycleOwner, EventObserver { navigateToProfile(it.info.id) })\n    }\n\n    private fun navigateToProfile(userID: String) {\n        val bundle = bundleOf(ProfileFragment.ARGS_KEY_USER_ID to userID)\n        findNavController().navigate(R.id.action_navigation_users_to_profileFragment, bundle)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/users/UsersListAdapter.kt",
    "content": "package com.fredrikbogg.android_chat_app.ui.users\n\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.DiffUtil\nimport androidx.recyclerview.widget.ListAdapter\nimport androidx.recyclerview.widget.RecyclerView\nimport com.fredrikbogg.android_chat_app.data.db.entity.User\nimport com.fredrikbogg.android_chat_app.databinding.ListItemUserBinding\n\n\nclass UsersListAdapter internal constructor(private val viewModel: UsersViewModel) :\n    ListAdapter<User, UsersListAdapter.ViewHolder>(UserDiffCallback()) {\n\n    class ViewHolder(private val binding: ListItemUserBinding) :\n        RecyclerView.ViewHolder(binding.root) {\n        fun bind(viewModel: UsersViewModel, item: User) {\n            binding.viewmodel = viewModel\n            binding.user = item\n            binding.executePendingBindings()\n        }\n    }\n\n    override fun onBindViewHolder(holder: ViewHolder, position: Int) {\n        holder.bind(viewModel, getItem(position))\n    }\n\n    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {\n        val layoutInflater = LayoutInflater.from(parent.context)\n        val binding = ListItemUserBinding.inflate(layoutInflater, parent, false)\n        return ViewHolder(binding)\n    }\n}\n\nclass UserDiffCallback : DiffUtil.ItemCallback<User>() {\n    override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {\n        return oldItem == newItem\n    }\n\n    override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {\n        return oldItem.info.id == newItem.info.id\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/users/UsersViewModel.kt",
    "content": "package com.fredrikbogg.android_chat_app.ui.users\n\nimport androidx.lifecycle.*\nimport com.fredrikbogg.android_chat_app.data.db.entity.User\nimport com.fredrikbogg.android_chat_app.data.db.repository.DatabaseRepository\nimport com.fredrikbogg.android_chat_app.ui.DefaultViewModel\nimport com.fredrikbogg.android_chat_app.data.Event\nimport com.fredrikbogg.android_chat_app.data.Result\n\n\nclass UsersViewModelFactory(private val myUserID: String) :\n    ViewModelProvider.Factory {\n    override fun <T : ViewModel?> create(modelClass: Class<T>): T {\n        return UsersViewModel(myUserID) as T\n    }\n}\n\nclass UsersViewModel(private val myUserID: String) : DefaultViewModel() {\n    private val repository: DatabaseRepository = DatabaseRepository()\n\n    private val _selectedUser = MutableLiveData<Event<User>>()\n    var selectedUser: LiveData<Event<User>> = _selectedUser\n    private val updatedUsersList = MutableLiveData<MutableList<User>>()\n    val usersList = MediatorLiveData<List<User>>()\n\n    init {\n        usersList.addSource(updatedUsersList) { mutableList ->\n            usersList.value = updatedUsersList.value?.filter { it.info.id != myUserID }\n        }\n        loadUsers()\n    }\n\n    private fun loadUsers() {\n        repository.loadUsers { result: Result<MutableList<User>> ->\n            onResult(updatedUsersList, result)\n        }\n    }\n\n    fun selectUser(user: User) {\n        _selectedUser.value = Event(user)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/util/FileConverterUtil.kt",
    "content": "package com.fredrikbogg.android_chat_app.util\n\nimport android.content.Context\nimport android.graphics.Bitmap\nimport android.graphics.BitmapFactory\nimport android.net.Uri\nimport java.io.ByteArrayOutputStream\nimport java.io.InputStream\n\nfun convertFileToByteArray(context: Context, uri: Uri): ByteArray {\n    val inputStream: InputStream? = context.contentResolver.openInputStream(uri)\n    val bitmap = BitmapFactory.decodeStream(inputStream)\n    val byteArrayOutputStream = ByteArrayOutputStream()\n    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream)\n\n    return byteArrayOutputStream.toByteArray()\n}\n"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/util/FirebaseUtil.kt",
    "content": "package com.fredrikbogg.android_chat_app.util\n\nimport com.google.firebase.database.DataSnapshot\n\nfun <T> wrapSnapshotToClass(className: Class<T>, snap: DataSnapshot): T? {\n    return snap.getValue(className)\n}\n\nfun <T> wrapSnapshotToArrayList(className: Class<T>, snap: DataSnapshot): MutableList<T> {\n    val arrayList: MutableList<T> = arrayListOf()\n    for (child in snap.children) {\n        child.getValue(className)?.let { arrayList.add(it) }\n    }\n    return arrayList\n}\n\n// Always returns the same combined id when comparing the two users id's\nfun convertTwoUserIDs(userID1: String, userID2: String): String {\n    return if (userID1 < userID2) {\n        userID2 + userID1\n    } else {\n        userID1 + userID2\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/util/LiveDataExt.kt",
    "content": "package com.fredrikbogg.android_chat_app.util\n\nimport androidx.lifecycle.MutableLiveData\n\nfun <T> MutableLiveData<MutableList<T>>.addNewItem(item: T) {\n    val newList = mutableListOf<T>()\n    this.value?.let { newList.addAll(it) }\n    newList.add(item)\n    this.value = newList\n}\n\nfun <T> MutableLiveData<MutableList<T>>.updateItemAt(item: T, index: Int) {\n    val newList = mutableListOf<T>()\n    this.value?.let { newList.addAll(it) }\n    newList[index] = item\n    this.value = newList\n}\n\nfun <T> MutableLiveData<MutableList<T>>.removeItem(item: T) {\n    val newList = mutableListOf<T>()\n    this.value?.let { newList.addAll(it) }\n    newList.remove(item)\n    this.value = newList\n}\n"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/util/SharedPreferencesUtil.kt",
    "content": "package com.fredrikbogg.android_chat_app.util\n\nimport android.content.Context\nimport android.content.SharedPreferences\n\nobject SharedPreferencesUtil {\n    private const val PACKAGE_NAME = \"com.fredrikbogg.android_chat_app\"\n    private const val KEY_USER_ID = \"user_info\"\n\n    private fun getPrefs(context: Context): SharedPreferences {\n        return context.getSharedPreferences(PACKAGE_NAME, Context.MODE_PRIVATE)\n    }\n\n    fun getUserID(context: Context): String? {\n        return getPrefs(context).getString(KEY_USER_ID, null)\n    }\n\n    fun saveUserID(context: Context, userID: String) {\n        getPrefs(context).edit().putString(KEY_USER_ID, userID).apply()\n    }\n\n    fun removeUserID(context: Context) {\n        getPrefs(context).edit().remove(KEY_USER_ID).apply()\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/util/TextUtil.kt",
    "content": "package com.fredrikbogg.android_chat_app.util\n\nfun isEmailValid(email: CharSequence): Boolean {\n    return android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches()\n}\n\nfun isTextValid(minLength: Int, text: String?): Boolean {\n    if (text.isNullOrBlank() || text.length < minLength) {\n        return false\n    }\n    return true\n}\n\n"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/util/ViewExt.kt",
    "content": "package com.fredrikbogg.android_chat_app.util\n\nimport android.content.Context\nimport android.view.View\nimport android.view.inputmethod.InputMethodManager\nimport com.fredrikbogg.android_chat_app.R\nimport com.google.android.material.snackbar.Snackbar\n\nfun View.forceHideKeyboard() {\n    val inputManager: InputMethodManager =\n        this.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager\n    inputManager.hideSoftInputFromWindow(this.windowToken, InputMethodManager.HIDE_NOT_ALWAYS)\n}\n\nfun View.showSnackBar(text: String) {\n    Snackbar.make(this.rootView.findViewById(R.id.container), text, Snackbar.LENGTH_SHORT).show()\n}"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_chat_bubble_24.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\"\n    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M20,2H4c-1.1,0 -2,0.9 -2,2v18l4,-4h14c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_error_24.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\"\n    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_notifications_24.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\"\n    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.89,2 2,2zM18,16v-5c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_people_24.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\"\n    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M16,11c1.66,0 2.99,-1.34 2.99,-3S17.66,5 16,5c-1.66,0 -3,1.34 -3,3s1.34,3 3,3zM8,11c1.66,0 2.99,-1.34 2.99,-3S9.66,5 8,5C6.34,5 5,6.34 5,8s1.34,3 3,3zM8,13c-2.33,0 -7,1.17 -7,3.5L1,19h14v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5zM16,13c-0.29,0 -0.62,0.02 -0.97,0.05 1.16,0.84 1.97,1.97 1.97,3.45L17,19h6v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_person_24.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\"\n    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_settings_24.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\"\n    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/round_circle_online_green.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"oval\">\n\n    <solid android:color=\"@android:color/holo_green_light\" />\n    <stroke android:color=\"@android:color/white\" android:width=\"1.5dp\" />\n\n    <size\n        android:width=\"20dp\"\n        android:height=\"20dp\" />\n</shape>\n"
  },
  {
    "path": "app/src/main/res/drawable/round_circle_primary.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"oval\">\n\n    <solid android:color=\"?attr/colorPrimary\" />\n\n    <size\n        android:width=\"20dp\"\n        android:height=\"20dp\" />\n</shape>\n"
  },
  {
    "path": "app/src/main/res/drawable-v24/rounded_rectangle_primary.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n\n    <solid android:color=\"?attr/colorPrimary\" />\n    <corners android:radius=\"10dp\" />\n</shape>\n"
  },
  {
    "path": "app/src/main/res/drawable-v24/rounded_rectangle_secondary.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n\n    <solid android:color=\"#F1F1F1\" />\n\n    <corners android:radius=\"5dp\" />\n</shape>\n"
  },
  {
    "path": "app/src/main/res/font/nunito.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<font-family xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        app:fontProviderAuthority=\"com.google.android.gms.fonts\"\n        app:fontProviderPackage=\"com.google.android.gms\"\n        app:fontProviderQuery=\"Nunito\"\n        app:fontProviderCerts=\"@array/com_google_android_gms_fonts_certs\">\n</font-family>\n"
  },
  {
    "path": "app/src/main/res/font/nunito_bold.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<font-family xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        app:fontProviderAuthority=\"com.google.android.gms.fonts\"\n        app:fontProviderPackage=\"com.google.android.gms\"\n        app:fontProviderQuery=\"name=Nunito&amp;weight=700\"\n        app:fontProviderCerts=\"@array/com_google_android_gms_fonts_certs\">\n</font-family>\n"
  },
  {
    "path": "app/src/main/res/font/nunito_extrabold.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<font-family xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        app:fontProviderAuthority=\"com.google.android.gms.fonts\"\n        app:fontProviderPackage=\"com.google.android.gms\"\n        app:fontProviderQuery=\"name=Nunito&amp;weight=800\"\n        app:fontProviderCerts=\"@array/com_google_android_gms_fonts_certs\">\n</font-family>\n"
  },
  {
    "path": "app/src/main/res/font/nunito_semibold.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<font-family xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        app:fontProviderAuthority=\"com.google.android.gms.fonts\"\n        app:fontProviderPackage=\"com.google.android.gms\"\n        app:fontProviderQuery=\"name=Nunito&amp;weight=600\"\n        app:fontProviderCerts=\"@array/com_google_android_gms_fonts_certs\">\n</font-family>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n\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/container\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <include\n        android:id=\"@+id/main_toolbar\"\n        layout=\"@layout/toolbar_main\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" />\n\n    <fragment\n        android:id=\"@+id/nav_host_fragment\"\n        android:name=\"androidx.navigation.fragment.NavHostFragment\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_marginTop=\"?attr/actionBarSize\"\n        app:defaultNavHost=\"true\"\n        app:layout_constraintBottom_toTopOf=\"@id/nav_view\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:navGraph=\"@navigation/mobile_navigation\"\n        tools:ignore=\"FragmentTagUsage\" />\n\n    <com.google.android.material.bottomnavigation.BottomNavigationView\n        android:id=\"@+id/nav_view\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"0dp\"\n        android:layout_marginEnd=\"0dp\"\n        android:background=\"?android:attr/windowBackground\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:menu=\"@menu/bottom_nav_menu\" />\n\n    <ProgressBar\n        android:id=\"@+id/main_progressBar\"\n        style=\"?android:attr/progressBarStyle\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:indeterminate=\"true\"\n        android:visibility=\"invisible\"\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:visibility=\"visible\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_chat.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<layout 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\n    <data>\n\n        <variable\n            name=\"viewmodel\"\n            type=\"com.fredrikbogg.android_chat_app.ui.chat.ChatViewModel\" />\n    </data>\n\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:id=\"@+id/textContentLayout\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n        <androidx.recyclerview.widget.RecyclerView\n            android:id=\"@+id/messagesRecyclerView\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:focusable=\"true\"\n            android:focusableInTouchMode=\"true\"\n            app:bind_disable_item_animator=\"@{true}\"\n            app:bind_messages_list=\"@{viewmodel.messagesList}\"\n            app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n            app:layout_constraintBottom_toTopOf=\"@id/layoutChatbox\"\n            app:layout_constraintLeft_toLeftOf=\"parent\"\n            app:layout_constraintRight_toRightOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\"\n            tools:listitem=\"@layout/list_item_message_received\" />\n\n        <View\n            android:layout_width=\"0dp\"\n            android:layout_height=\"2dp\"\n            android:layout_marginBottom=\"0dp\"\n            android:background=\"#dfdfdf\"\n            app:layout_constraintBottom_toTopOf=\"@+id/layoutChatbox\"\n            app:layout_constraintLeft_toLeftOf=\"parent\"\n            app:layout_constraintRight_toRightOf=\"parent\" />\n\n        <LinearLayout\n            android:id=\"@+id/layoutChatbox\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"50dp\"\n            android:background=\"@android:color/white\"\n            android:minHeight=\"48dp\"\n            android:orientation=\"horizontal\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintLeft_toLeftOf=\"parent\"\n            app:layout_constraintRight_toRightOf=\"parent\">\n\n            <EditText\n                android:id=\"@+id/editTextMessage\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center\"\n                android:layout_marginLeft=\"16dp\"\n                android:layout_marginRight=\"16dp\"\n                android:layout_weight=\"1\"\n                android:background=\"@android:color/transparent\"\n                android:hint=\"@string/enter_message\"\n                android:importantForAutofill=\"no\"\n                android:inputType=\"text\"\n                android:maxLines=\"6\"\n                android:text=\"@={viewmodel.newMessageText}\" />\n\n            <Button\n                android:id=\"@+id/sendBtn\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginEnd=\"8dp\"\n                android:clickable=\"true\"\n                android:onClick=\"@{() -> viewmodel.sendMessagePressed()}\"\n                android:text=\"@string/send\"\n                android:textSize=\"14sp\" />\n        </LinearLayout>\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_chats.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<layout 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\n    <data>\n\n        <variable\n            name=\"viewmodel\"\n            type=\"com.fredrikbogg.android_chat_app.ui.chats.ChatsViewModel\" />\n\n    </data>\n\n    <androidx.coordinatorlayout.widget.CoordinatorLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_marginBottom=\"?attr/actionBarSize\">\n\n        <androidx.constraintlayout.widget.ConstraintLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\">\n\n            <androidx.recyclerview.widget.RecyclerView\n                android:id=\"@+id/chatsRecyclerView\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                app:bind_chats_list=\"@{viewmodel.chatsList}\"\n                app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\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:listitem=\"@layout/list_item_user\" />\n\n        </androidx.constraintlayout.widget.ConstraintLayout>\n    </androidx.coordinatorlayout.widget.CoordinatorLayout>\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_create_account.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<layout 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\n    <data>\n\n        <variable\n            name=\"viewmodel\"\n            type=\"com.fredrikbogg.android_chat_app.ui.start.createAccount.CreateAccountViewModel\" />\n\n    </data>\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"fill_parent\">\n\n        <LinearLayout\n            android:id=\"@+id/textContentLayout\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"20dp\"\n            android:layout_marginTop=\"70dp\"\n            android:layout_marginEnd=\"20dp\"\n            android:orientation=\"vertical\"\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\n            <TextView\n                android:id=\"@+id/titleText\"\n                style=\"@style/BoldText\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginBottom=\"40dp\"\n                android:gravity=\"start\"\n                android:text=\"@string/create_a_new_account\"\n                android:textSize=\"30sp\"\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\n            <com.google.android.material.textfield.TextInputLayout\n                android:layout_width=\"fill_parent\"\n                android:layout_height=\"wrap_content\"\n                android:background=\"@android:color/transparent\"\n                app:boxBackgroundColor=\"@android:color/transparent\">\n\n                <EditText\n                    android:id=\"@+id/editTextDisplayName\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:ems=\"10\"\n                    android:hint=\"@string/display_name\"\n                    android:importantForAutofill=\"no\"\n                    android:inputType=\"textCapWords\"\n                    android:maxLength=\"25\"\n                    android:paddingStart=\"0dp\"\n                    android:paddingEnd=\"0dp\"\n                    android:text=\"@={viewmodel.displayNameText}\"\n                    tools:background=\"@android:color/transparent\" />\n            </com.google.android.material.textfield.TextInputLayout>\n\n            <com.google.android.material.textfield.TextInputLayout\n                android:layout_width=\"fill_parent\"\n                android:layout_height=\"wrap_content\"\n                android:background=\"@android:color/transparent\"\n                android:padding=\"0dp\"\n                app:boxBackgroundColor=\"@android:color/transparent\">\n\n                <EditText\n                    android:id=\"@+id/editTextEmail\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:ems=\"10\"\n                    android:hint=\"@string/email\"\n                    android:importantForAutofill=\"no\"\n                    android:inputType=\"textEmailAddress\"\n                    android:maxLength=\"25\"\n                    android:paddingStart=\"0dp\"\n                    android:paddingEnd=\"0dp\"\n                    android:text=\"@={viewmodel.emailText}\"\n                    tools:background=\"@android:color/transparent\" />\n            </com.google.android.material.textfield.TextInputLayout>\n\n            <com.google.android.material.textfield.TextInputLayout\n\n                android:layout_width=\"fill_parent\"\n                android:layout_height=\"wrap_content\"\n                android:background=\"@android:color/transparent\"\n                android:padding=\"0dp\"\n                app:boxBackgroundColor=\"@android:color/transparent\">\n\n                <EditText\n                    android:id=\"@+id/editTextPassword\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:ems=\"10\"\n                    android:hint=\"@string/password\"\n                    android:importantForAutofill=\"no\"\n                    android:inputType=\"textPassword\"\n                    android:maxLength=\"25\"\n                    android:paddingStart=\"0dp\"\n                    android:paddingEnd=\"0dp\"\n                    android:text=\"@={viewmodel.passwordText}\"\n                    tools:background=\"@android:color/transparent\" />\n            </com.google.android.material.textfield.TextInputLayout>\n\n        </LinearLayout>\n\n        <Button\n            android:id=\"@+id/loginButton\"\n            android:layout_width=\"300dp\"\n            android:layout_height=\"60dp\"\n            android:layout_marginTop=\"98dp\"\n            android:backgroundTint=\"@color/colorAccent\"\n            android:enabled=\"@{!viewmodel.isCreatingAccount()}\"\n            android:onClick=\"@{() -> viewmodel.createAccountPressed()}\"\n            android:text=\"@string/create\"\n            android:textSize=\"16sp\"\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/guideline50\"\n            tools:enabled=\"@{true}\" />\n\n        <androidx.constraintlayout.widget.Guideline\n            android:id=\"@+id/guideline50\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\"\n            app:layout_constraintGuide_percent=\".5\" />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_login.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<layout 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\n    <data>\n\n        <variable\n            name=\"viewmodel\"\n            type=\"com.fredrikbogg.android_chat_app.ui.start.login.LoginViewModel\" />\n\n    </data>\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"fill_parent\">\n\n        <LinearLayout\n            android:id=\"@+id/textContentLayout\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"20dp\"\n            android:layout_marginTop=\"70dp\"\n            android:layout_marginEnd=\"20dp\"\n            android:orientation=\"vertical\"\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\n            <TextView\n                android:id=\"@+id/titleText\"\n                style=\"@style/BoldText\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginBottom=\"40dp\"\n                android:gravity=\"start\"\n                android:text=\"@string/login_to_your_account\"\n                android:textSize=\"30sp\"\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\n            <com.google.android.material.textfield.TextInputLayout\n                android:layout_width=\"fill_parent\"\n                android:layout_height=\"wrap_content\"\n                android:background=\"@android:color/transparent\"\n                app:boxBackgroundColor=\"@android:color/transparent\">\n\n                <EditText\n                    android:id=\"@+id/editTextEmail\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:ems=\"10\"\n                    android:hint=\"@string/email\"\n                    android:importantForAutofill=\"no\"\n                    android:inputType=\"textEmailAddress\"\n                    android:maxLength=\"25\"\n                    android:paddingStart=\"0dp\"\n                    android:paddingEnd=\"0dp\"\n                    android:text=\"@={viewmodel.emailText}\"\n                    tools:background=\"@android:color/transparent\" />\n            </com.google.android.material.textfield.TextInputLayout>\n\n            <com.google.android.material.textfield.TextInputLayout\n                android:id=\"@+id/editTextPasswordInputLayout\"\n                android:layout_width=\"fill_parent\"\n                android:layout_height=\"wrap_content\"\n                android:background=\"@android:color/transparent\"\n                android:padding=\"0dp\"\n                app:boxBackgroundColor=\"@android:color/transparent\">\n\n                <EditText\n                    android:id=\"@+id/editTextPassword\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginTop=\"20dp\"\n                    android:ems=\"10\"\n                    android:hint=\"@string/password\"\n                    android:importantForAutofill=\"no\"\n                    android:inputType=\"textPassword\"\n                    android:maxLength=\"25\"\n                    android:paddingStart=\"0dp\"\n                    android:paddingEnd=\"0dp\"\n                    android:text=\"@={viewmodel.passwordText}\"\n                    tools:background=\"@android:color/transparent\" />\n            </com.google.android.material.textfield.TextInputLayout>\n\n        </LinearLayout>\n\n        <Button\n            android:id=\"@+id/loginButton\"\n            android:layout_width=\"300dp\"\n            android:layout_height=\"60dp\"\n            android:layout_marginTop=\"98dp\"\n            android:enabled=\"@{!viewmodel.isLoggingIn()}\"\n            android:onClick=\"@{() -> viewmodel.loginPressed()}\"\n            android:text=\"@string/login\"\n            android:textSize=\"16sp\"\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/guideline50\"\n            tools:enabled=\"@{true}\" />\n\n        <androidx.constraintlayout.widget.Guideline\n            android:id=\"@+id/guideline50\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\"\n            app:layout_constraintGuide_percent=\".5\" />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_notifications.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<layout 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\n    <data>\n\n        <variable\n            name=\"viewmodel\"\n            type=\"com.fredrikbogg.android_chat_app.ui.notifications.NotificationsViewModel\" />\n\n    </data>\n\n    <androidx.coordinatorlayout.widget.CoordinatorLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_marginBottom=\"?attr/actionBarSize\">\n\n        <androidx.constraintlayout.widget.ConstraintLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\">\n\n            <androidx.recyclerview.widget.RecyclerView\n                android:id=\"@+id/usersRecyclerView\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                app:bind_notifications_list=\"@{viewmodel.usersInfoList}\"\n                app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\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:listitem=\"@layout/list_item_notification\" />\n\n        </androidx.constraintlayout.widget.ConstraintLayout>\n    </androidx.coordinatorlayout.widget.CoordinatorLayout>\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_profile.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n\n<layout 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\n    <data>\n\n        <import type=\"android.view.View\" />\n\n        <import type=\"com.fredrikbogg.android_chat_app.ui.chats.ChatsViewModel\" />\n\n        <variable\n            name=\"viewmodel\"\n            type=\"com.fredrikbogg.android_chat_app.ui.profile.ProfileViewModel\" />\n\n    </data>\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"fill_parent\">\n\n        <androidx.constraintlayout.widget.Guideline\n            android:id=\"@+id/guideline60\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\"\n            app:layout_constraintGuide_percent=\".6\" />\n\n        <ImageView\n            android:id=\"@+id/blurredUserImage\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"170dp\"\n            android:contentDescription=\"@string/user_image\"\n            android:scaleType=\"centerCrop\"\n            app:bind_image_url_blur=\"@{viewmodel.otherUser.info.profileImageUrl}\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\"\n            tools:srcCompat=\"@tools:sample/avatars\" />\n\n        <com.google.android.material.card.MaterialCardView\n            android:id=\"@+id/userImageCardView\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            app:cardCornerRadius=\"60dp\"\n            app:cardPreventCornerOverlap=\"true\"\n            app:layout_constraintBottom_toBottomOf=\"@id/blurredUserImage\"\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/blurredUserImage\"\n            app:strokeColor=\"@android:color/white\"\n            app:strokeWidth=\"2dp\">\n\n            <ImageView\n                android:layout_width=\"120dp\"\n                android:layout_height=\"120dp\"\n                android:layout_margin=\"0dp\"\n                android:contentDescription=\"@string/user_image\"\n                android:scaleType=\"centerCrop\"\n                app:bind_image_url=\"@{viewmodel.otherUser.info.profileImageUrl}\"\n                tools:src=\"@tools:sample/avatars[0]\" />\n        </com.google.android.material.card.MaterialCardView>\n\n        <TextView\n            android:id=\"@+id/nameText\"\n            style=\"@style/BoldText\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"8dp\"\n            android:gravity=\"center\"\n            android:text=\"@{viewmodel.otherUser.info.displayName}\"\n            android:textSize=\"36sp\"\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/userImageCardView\"\n            tools:text=\"Name\" />\n\n        <TextView\n            android:id=\"@+id/statusText\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"12dp\"\n            android:maxLength=\"40\"\n            style=\"@style/MessageSeen\"\n            android:singleLine=\"true\"\n            android:text=\"@{viewmodel.otherUser.info.status}\"\n            android:textSize=\"18sp\"\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/nameText\"\n            tools:text=\"Status\" />\n\n        <androidx.constraintlayout.widget.ConstraintLayout\n            android:id=\"@+id/statesLayout\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"0dp\"\n            android:layout_marginStart=\"42dp\"\n            android:layout_marginTop=\"42dp\"\n            android:layout_marginEnd=\"42dp\"\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/guideline60\">\n\n            <Button\n                android:id=\"@+id/addFriendButton\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"55dp\"\n                android:onClick=\"@{() -> viewmodel.addFriendPressed()}\"\n                android:text=\"@string/add_friend\"\n                android:textSize=\"16sp\"\n                android:visibility=\"@{viewmodel.layoutState == viewmodel.layoutState.NOT_FRIEND? View.VISIBLE : View.GONE}\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                tools:visibility=\"visible\" />\n\n            <Button\n                android:id=\"@+id/removeFriendButton\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"55dp\"\n                android:onClick=\"@{() -> viewmodel.removeFriendPressed()}\"\n                android:text=\"@string/remove_friend\"\n                android:textSize=\"16sp\"\n                android:visibility=\"@{viewmodel.layoutState == viewmodel.layoutState.IS_FRIEND? View.VISIBLE : View.GONE}\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                tools:visibility=\"gone\" />\n\n            <Button\n                android:id=\"@+id/requestSentButton\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"55dp\"\n                android:enabled=\"false\"\n                android:text=\"@string/request_sent\"\n                android:textSize=\"16sp\"\n                android:visibility=\"@{viewmodel.layoutState == viewmodel.layoutState.REQUEST_SENT? View.VISIBLE : View.GONE}\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                tools:visibility=\"gone\" />\n\n            <Button\n                android:id=\"@+id/acceptRequestButton\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"55dp\"\n                android:onClick=\"@{() -> viewmodel.acceptFriendRequestPressed()}\"\n                android:text=\"@string/accept_friend_request\"\n                android:textSize=\"16sp\"\n                android:visibility=\"@{viewmodel.layoutState == viewmodel.layoutState.ACCEPT_DECLINE? View.VISIBLE : View.GONE}\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                tools:visibility=\"gone\" />\n\n            <Button\n                android:id=\"@+id/declineRequestButton\"\n                android:layout_width=\"match_parent\"\n                android:backgroundTint=\"@color/textError\"\n                android:layout_height=\"55dp\"\n                android:layout_marginTop=\"5dp\"\n                android:onClick=\"@{() -> viewmodel.declineFriendRequestPressed()}\"\n                android:text=\"@string/decline_friend_request\"\n                android:textSize=\"16sp\"\n                android:visibility=\"@{viewmodel.layoutState == viewmodel.layoutState.ACCEPT_DECLINE? View.VISIBLE : View.GONE}\"\n                app:layout_constraintTop_toBottomOf=\"@id/acceptRequestButton\"\n                tools:visibility=\"gone\" />\n\n        </androidx.constraintlayout.widget.ConstraintLayout>\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_settings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<layout 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\n    <data>\n\n        <variable\n            name=\"viewmodel\"\n            type=\"com.fredrikbogg.android_chat_app.ui.settings.SettingsViewModel\" />\n\n    </data>\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_marginBottom=\"?attr/actionBarSize\">\n\n        <Button\n            android:id=\"@+id/logoutButton\"\n            style=\"@style/VeryBoldText\"\n            android:layout_width=\"120dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:layout_marginBottom=\"42dp\"\n            android:backgroundTint=\"@color/textError\"\n            android:onClick=\"@{() -> viewmodel.logoutUserPressed()}\"\n            android:text=\"@string/logout\"\n            android:textColor=\"@android:color/white\"\n            android:textSize=\"14sp\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\" />\n\n        <ImageView\n            android:id=\"@+id/blurredUserImage\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"170dp\"\n            android:contentDescription=\"@string/user_image\"\n            android:scaleType=\"centerCrop\"\n            app:bind_image_url_blur=\"@{viewmodel.userInfo.profileImageUrl}\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\"\n            tools:srcCompat=\"@tools:sample/avatars\" />\n\n        <com.google.android.material.card.MaterialCardView\n            android:id=\"@+id/userImageCardView\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            app:cardCornerRadius=\"60dp\"\n            app:cardPreventCornerOverlap=\"true\"\n            app:layout_constraintBottom_toBottomOf=\"@id/blurredUserImage\"\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/blurredUserImage\"\n            app:strokeColor=\"@android:color/white\"\n            app:strokeWidth=\"2dp\">\n\n            <ImageView\n                android:layout_width=\"120dp\"\n                android:layout_height=\"120dp\"\n                android:layout_margin=\"0dp\"\n                android:contentDescription=\"@string/user_image\"\n                android:scaleType=\"centerCrop\"\n                app:bind_image_url=\"@{viewmodel.userInfo.profileImageUrl}\"\n                tools:src=\"@tools:sample/avatars[0]\" />\n        </com.google.android.material.card.MaterialCardView>\n\n        <LinearLayout\n            android:id=\"@+id/contentLayout\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:layout_marginTop=\"8dp\"\n            android:orientation=\"vertical\"\n            app:layout_constraintBottom_toTopOf=\"@id/logoutButton\"\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/userImageCardView\">\n\n            <TextView\n                android:id=\"@+id/nameText\"\n                style=\"@style/BoldText\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:gravity=\"center\"\n                android:text=\"@{viewmodel.userInfo.displayName}\"\n                android:textSize=\"36sp\"\n                tools:text=\"Name\" />\n\n            <TextView\n                android:id=\"@+id/statusText\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"12dp\"\n                android:gravity=\"center\"\n                style=\"@style/MessageSeen\"\n                android:text=\"@{viewmodel.userInfo.status}\"\n                android:textSize=\"16sp\"\n                tools:text=\"This is a status message\" />\n\n            <Button\n                android:id=\"@+id/changeImageButton\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"55dp\"\n                android:layout_marginStart=\"24dp\"\n                android:layout_marginTop=\"62dp\"\n                android:layout_marginEnd=\"24dp\"\n                android:onClick=\"@{() -> viewmodel.changeUserImagePressed()}\"\n                android:text=\"@string/change_image\"\n                android:textSize=\"14sp\" />\n\n            <Button\n                android:id=\"@+id/changeStatusButton\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"55dp\"\n                android:layout_marginStart=\"24dp\"\n                android:layout_marginTop=\"12dp\"\n                android:layout_marginEnd=\"24dp\"\n                android:onClick=\"@{() -> viewmodel.changeUserStatusPressed()}\"\n                android:text=\"@string/change_status\"\n                android:textSize=\"14sp\" />\n        </LinearLayout>\n\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_start.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<layout 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\n    <data>\n\n        <variable\n            name=\"viewmodel\"\n            type=\"com.fredrikbogg.android_chat_app.ui.start.StartViewModel\" />\n\n    </data>\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"fill_parent\">\n\n        <TextView\n            android:id=\"@+id/textView2\"\n            style=\"@style/BoldText\"\n            android:layout_width=\"280dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"35dp\"\n            android:gravity=\"center_horizontal\"\n            android:text=\"@string/welcome_to_quick_chat\"\n            android:textSize=\"36sp\"\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/imageView\" />\n\n        <Button\n            android:id=\"@+id/loginButton\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"55dp\"\n            android:layout_marginStart=\"20dp\"\n            android:layout_marginTop=\"50dp\"\n            android:layout_marginEnd=\"20dp\"\n            android:onClick=\"@{() -> viewmodel.goToLoginPressed()}\"\n            android:text=\"@string/login\"\n            android:textSize=\"16sp\"\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/guideline50\" />\n\n        <Button\n            android:id=\"@+id/createAccountButton\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"55dp\"\n            android:layout_marginStart=\"20dp\"\n            android:layout_marginTop=\"10dp\"\n            android:layout_marginEnd=\"20dp\"\n            android:backgroundTint=\"@color/colorAccent\"\n            android:onClick=\"@{() -> viewmodel.goToCreateAccountPressed()}\"\n            android:text=\"@string/create_account\"\n            android:textSize=\"16sp\"\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/loginButton\" />\n\n        <androidx.constraintlayout.widget.Guideline\n            android:id=\"@+id/guideline50\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\"\n            app:layout_constraintGuide_percent=\".5\" />\n\n        <ImageView\n            android:id=\"@+id/imageView\"\n            android:layout_width=\"70dp\"\n            android:layout_height=\"70dp\"\n            android:layout_marginTop=\"40dp\"\n            android:src=\"@drawable/chat_box\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintHorizontal_bias=\"0.498\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\"\n            tools:layout_editor_absoluteY=\"73dp\"\n            android:contentDescription=\"@string/chat_icon\" />\n\n        <TextView\n            android:id=\"@+id/textView\"\n            style=\"@style/BoldText\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"20dp\"\n            android:alpha=\".8\"\n            android:gravity=\"center\"\n            android:text=\"@string/login_or_create_an_account_to_get_started\"\n            android:textSize=\"16sp\"\n            android:textStyle=\"italic\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toBottomOf=\"@id/createAccountButton\" />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_users.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<layout 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\n    <data>\n\n        <variable\n            name=\"viewmodel\"\n            type=\"com.fredrikbogg.android_chat_app.ui.users.UsersViewModel\" />\n\n    </data>\n\n    <androidx.coordinatorlayout.widget.CoordinatorLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_marginBottom=\"?attr/actionBarSize\">\n\n        <androidx.constraintlayout.widget.ConstraintLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\">\n\n            <androidx.recyclerview.widget.RecyclerView\n                android:id=\"@+id/usersRecyclerView\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                app:bind_users_list=\"@{viewmodel.usersList}\"\n                app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\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:listitem=\"@layout/list_item_user\" />\n\n        </androidx.constraintlayout.widget.ConstraintLayout>\n    </androidx.coordinatorlayout.widget.CoordinatorLayout>\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/list_item_chat.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<layout 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\n    <data>\n\n        <import type=\"android.view.View\" />\n\n        <variable\n            name=\"viewmodel\"\n            type=\"com.fredrikbogg.android_chat_app.ui.chats.ChatsViewModel\" />\n\n        <variable\n            name=\"chatwithuserinfo\"\n            type=\"com.fredrikbogg.android_chat_app.data.model.ChatWithUserInfo\" />\n\n    </data>\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"12dp\"\n        android:layout_marginTop=\"16dp\"\n        android:layout_marginEnd=\"12dp\"\n        android:clickable=\"true\"\n        android:focusable=\"true\"\n        android:onClick=\"@{() -> viewmodel.selectChatWithUserInfoPressed(chatwithuserinfo)}\"\n        app:bind_message=\"@{chatwithuserinfo.MChat.lastMessage}\"\n        app:bind_message_textView=\"@{messageText}\"\n        app:bind_message_view=\"@{notSeenView}\"\n        app:bind_myUserID=\"@{viewmodel.myUserID}\">\n\n        <androidx.cardview.widget.CardView\n            android:id=\"@+id/imageCardView\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            app:cardCornerRadius=\"29dp\"\n            app:cardElevation=\"0dp\"\n            app:cardPreventCornerOverlap=\"false\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\">\n\n            <ImageView\n                android:id=\"@+id/userProfileImage\"\n                android:layout_width=\"58dp\"\n                android:layout_height=\"58dp\"\n                android:layout_margin=\"0dp\"\n                android:contentDescription=\"@string/user_image\"\n                android:scaleType=\"centerCrop\"\n                app:bind_image_url=\"@{chatwithuserinfo.MUserInfo.profileImageUrl}\"\n                tools:src=\"@tools:sample/avatars[0]\" />\n\n        </androidx.cardview.widget.CardView>\n\n        <View\n            android:id=\"@+id/onlineView\"\n            android:layout_width=\"12dp\"\n            android:layout_height=\"12dp\"\n            android:background=\"@drawable/round_circle_online_green\"\n            android:visibility=\"@{chatwithuserinfo.MUserInfo.online == true? View.VISIBLE : View.INVISIBLE}\"\n            app:layout_constraintBottom_toBottomOf=\"@id/imageCardView\"\n            app:layout_constraintRight_toRightOf=\"@id/imageCardView\"\n            tools:visibility=\"visible\" />\n\n        <androidx.constraintlayout.widget.ConstraintLayout\n            android:layout_width=\"0dp\"\n            android:layout_height=\"0dp\"\n            android:layout_marginStart=\"12dp\"\n            android:layout_marginTop=\"3dp\"\n            android:layout_marginBottom=\"3dp\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintLeft_toRightOf=\"@id/imageCardView\"\n            app:layout_constraintRight_toRightOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\">\n\n            <TextView\n                android:id=\"@+id/displayNameText\"\n                style=\"@style/BoldText\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:singleLine=\"true\"\n                android:text=\"@{chatwithuserinfo.MUserInfo.displayName}\"\n                android:textSize=\"18sp\"\n                app:layout_constraintLeft_toLeftOf=\"parent\"\n                app:layout_constraintRight_toLeftOf=\"@id/timeText\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                tools:text=\"Display name\" />\n\n            <TextView\n                android:id=\"@+id/messageText\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:singleLine=\"true\"\n                android:textSize=\"14sp\"\n                app:bind_chat_message_text=\"@{chatwithuserinfo.MChat.lastMessage}\"\n                app:bind_chat_message_text_viewModel=\"@{viewmodel}\"\n                app:layout_constraintBottom_toBottomOf=\"parent\"\n                app:layout_constraintLeft_toLeftOf=\"parent\"\n                app:layout_constraintRight_toLeftOf=\"@id/notSeenLayout\"\n                tools:text=\"Message\"\n                tools:textAppearance=\"@style/MessageNotSeen\" />\n\n            <TextView\n                android:id=\"@+id/timeText\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:textSize=\"12sp\"\n                app:bind_epochTimeMsToDate_with_days_ago=\"@{chatwithuserinfo.MChat.lastMessage.epochTimeMs}\"\n                app:layout_constraintRight_toRightOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                tools:text=\"11:00 AM\" />\n\n            <androidx.constraintlayout.widget.ConstraintLayout\n                android:id=\"@+id/notSeenLayout\"\n                android:layout_width=\"54dp\"\n                android:layout_height=\"23dp\"\n                app:layout_constraintBottom_toBottomOf=\"parent\"\n                app:layout_constraintRight_toRightOf=\"parent\">\n\n                <View\n                    android:id=\"@+id/notSeenView\"\n                    android:layout_width=\"8dp\"\n                    android:layout_height=\"8dp\"\n                    android:background=\"@drawable/round_circle_primary\"\n                    android:visibility=\"invisible\"\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:visibility=\"visible\" />\n            </androidx.constraintlayout.widget.ConstraintLayout>\n        </androidx.constraintlayout.widget.ConstraintLayout>\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n</layout>\n"
  },
  {
    "path": "app/src/main/res/layout/list_item_message_received.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<layout 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\n    <data>\n\n        <variable\n            name=\"viewmodel\"\n            type=\"com.fredrikbogg.android_chat_app.ui.chat.ChatViewModel\" />\n\n        <variable\n            name=\"message\"\n            type=\"com.fredrikbogg.android_chat_app.data.db.entity.Message\" />\n\n    </data>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"10dp\"\n        android:layout_marginBottom=\"10dp\"\n        android:orientation=\"vertical\">\n\n        <TextView\n            android:id=\"@+id/timeText\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"36dp\"\n            android:gravity=\"center\"\n            android:textSize=\"12sp\"\n            app:bind_epochTimeMsToDate=\"@{message.epochTimeMs}\"\n            app:bind_message=\"@{message}\"\n            app:bind_message_viewModel=\"@{viewmodel}\"\n            tools:text=\"11:40\" />\n\n        <TextView\n            android:id=\"@+id/messageText\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"start\"\n            android:layout_marginStart=\"12dp\"\n            android:background=\"@drawable/rounded_rectangle_secondary\"\n            android:maxWidth=\"240dp\"\n            android:paddingLeft=\"12dp\"\n            android:paddingTop=\"6dp\"\n            android:paddingRight=\"12dp\"\n            android:paddingBottom=\"6dp\"\n            android:text=\"@{message.text}\"\n            android:textColor=\"@android:color/black\"\n            android:textSize=\"16sp\"\n            tools:text=\"This is a message\" />\n    </LinearLayout>\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/list_item_message_sent.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n\n<layout 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\n    <data>\n\n        <variable\n            name=\"viewmodel\"\n            type=\"com.fredrikbogg.android_chat_app.ui.chat.ChatViewModel\" />\n\n        <variable\n            name=\"message\"\n            type=\"com.fredrikbogg.android_chat_app.data.db.entity.Message\" />\n    </data>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"10dp\"\n        android:layout_marginBottom=\"10dp\"\n        android:orientation=\"vertical\">\n\n        <TextView\n            android:id=\"@+id/timeText\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"36dp\"\n            android:gravity=\"center\"\n            android:textSize=\"12sp\"\n            app:bind_epochTimeMsToDate=\"@{message.epochTimeMs}\"\n            app:bind_message=\"@{message}\"\n            app:bind_message_viewModel=\"@{viewmodel}\"\n            tools:text=\"11:40\" />\n\n        <TextView\n            android:id=\"@+id/messageText\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"end\"\n            android:layout_marginEnd=\"12dp\"\n            android:background=\"@drawable/rounded_rectangle_primary\"\n            android:maxWidth=\"240dp\"\n            android:paddingLeft=\"12dp\"\n            android:paddingTop=\"6dp\"\n            android:paddingRight=\"12dp\"\n            android:paddingBottom=\"6dp\"\n            android:text=\"@{message.text}\"\n            android:textColor=\"@android:color/white\"\n            android:textSize=\"16sp\"\n            tools:text=\"This is a message\" />\n    </LinearLayout>\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/list_item_notification.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<layout 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\n    <data>\n\n        <variable\n            name=\"viewmodel\"\n            type=\"com.fredrikbogg.android_chat_app.ui.notifications.NotificationsViewModel\" />\n\n        <variable\n            name=\"userinfo\"\n            type=\"com.fredrikbogg.android_chat_app.data.db.entity.UserInfo\" />\n\n    </data>\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"12dp\"\n        android:layout_marginTop=\"16dp\"\n        android:layout_marginEnd=\"12dp\">\n\n        <androidx.cardview.widget.CardView\n            android:id=\"@+id/chatCardView\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            app:cardCornerRadius=\"29dp\"\n            app:cardPreventCornerOverlap=\"false\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\">\n\n            <ImageView\n                android:id=\"@+id/userProfileImage\"\n                android:layout_width=\"58dp\"\n                android:layout_height=\"58dp\"\n                android:layout_margin=\"0dp\"\n                android:contentDescription=\"@string/user_image\"\n                android:scaleType=\"centerCrop\"\n                app:bind_image_url=\"@{userinfo.profileImageUrl}\"\n                tools:src=\"@tools:sample/avatars[0]\" />\n        </androidx.cardview.widget.CardView>\n\n        <TextView\n            android:id=\"@+id/displayNameText\"\n            style=\"@style/BoldText\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"16dp\"\n            android:singleLine=\"true\"\n            android:text=\"@{userinfo.displayName}\"\n            android:textSize=\"18sp\"\n            app:layout_constraintLeft_toRightOf=\"@id/chatCardView\"\n            app:layout_constraintRight_toRightOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"@id/chatCardView\"\n            tools:text=\"Display name\" />\n\n        <TextView\n            android:id=\"@+id/requestText\"\n            style=\"@style/BoldText\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"16dp\"\n            android:layout_marginBottom=\"7dp\"\n            android:text=\"@string/new_friend_request\"\n\n            android:textSize=\"14sp\"\n            app:layout_constraintBottom_toBottomOf=\"@id/chatCardView\"\n            app:layout_constraintLeft_toRightOf=\"@id/chatCardView\" />\n\n        <Button\n            android:id=\"@+id/acceptButton\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"45dp\"\n            android:layout_marginTop=\"10dp\"\n            android:onClick=\"@{() -> viewmodel.acceptNotificationPressed(userinfo)}\"\n            android:text=\"@string/accept\"\n            app:layout_constraintLeft_toLeftOf=\"@id/requestText\"\n            app:layout_constraintTop_toBottomOf=\"@id/requestText\" />\n\n        <Button\n            android:id=\"@+id/declineButton\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"45dp\"\n            android:layout_marginStart=\"24dp\"\n            android:backgroundTint=\"@color/textError\"\n            android:onClick=\"@{() -> viewmodel.declineNotificationPressed(userinfo)}\"\n            android:text=\"@string/decline\"\n            app:layout_constraintLeft_toRightOf=\"@id/acceptButton\"\n            app:layout_constraintTop_toTopOf=\"@id/acceptButton\" />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n</layout>\n"
  },
  {
    "path": "app/src/main/res/layout/list_item_user.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<layout 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\n    <data>\n\n        <variable\n            name=\"viewmodel\"\n            type=\"com.fredrikbogg.android_chat_app.ui.users.UsersViewModel\" />\n\n        <variable\n            name=\"user\"\n            type=\"com.fredrikbogg.android_chat_app.data.db.entity.User\" />\n\n    </data>\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"12dp\"\n        android:layout_marginTop=\"16dp\"\n        android:layout_marginEnd=\"12dp\"\n        android:clickable=\"true\"\n        android:focusable=\"true\"\n        android:onClick=\"@{() -> viewmodel.selectUser(user)}\">\n\n        <androidx.cardview.widget.CardView\n            android:id=\"@+id/imageCardView\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            app:cardCornerRadius=\"29dp\"\n            app:cardPreventCornerOverlap=\"false\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\">\n\n            <ImageView\n                android:id=\"@+id/userProfileImage\"\n                android:layout_width=\"58dp\"\n                android:layout_height=\"58dp\"\n                android:layout_margin=\"0dp\"\n                android:contentDescription=\"@string/user_image\"\n                android:scaleType=\"centerCrop\"\n                app:bind_image_url=\"@{user.info.profileImageUrl}\"\n                tools:src=\"@tools:sample/avatars[0]\" />\n        </androidx.cardview.widget.CardView>\n\n        <TextView\n            android:id=\"@+id/displayNameText\"\n            style=\"@style/BoldText\"\n            android:layout_width=\"0dp\"\n            android:layout_marginTop=\"3dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"16dp\"\n            android:singleLine=\"true\"\n            android:text=\"@{user.info.displayName}\"\n            android:textSize=\"18sp\"\n            app:layout_constraintLeft_toRightOf=\"@id/imageCardView\"\n            app:layout_constraintRight_toRightOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"@id/imageCardView\"\n            tools:text=\"Display name\" />\n\n        <TextView\n            android:id=\"@+id/statusText\"\n            style=\"@style/MessageSeen\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"3dp\"\n            android:layout_marginStart=\"16dp\"\n            android:singleLine=\"true\"\n            android:text=\"@{user.info.status}\"\n            android:textSize=\"14sp\"\n            app:layout_constraintBottom_toBottomOf=\"@id/imageCardView\"\n            app:layout_constraintLeft_toRightOf=\"@id/imageCardView\"\n            app:layout_constraintRight_toRightOf=\"parent\"\n            tools:text=\"Status\" />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n</layout>\n"
  },
  {
    "path": "app/src/main/res/layout/toolbar_addon_chat.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout 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\n    <data>\n\n        <import type=\"android.view.View\" />\n\n        <variable\n            name=\"viewmodel\"\n            type=\"com.fredrikbogg.android_chat_app.ui.chat.ChatViewModel\" />\n    </data>\n\n    <RelativeLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"@color/colorPrimary\"\n        android:minHeight=\"?attr/actionBarSize\"\n        tools:layout_height=\"?attr/actionBarSize\">\n\n\n        <androidx.constraintlayout.widget.ConstraintLayout\n            android:id=\"@+id/imageContentLayout\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentStart=\"true\"\n            android:layout_centerInParent=\"true\">\n\n            <androidx.cardview.widget.CardView\n                android:id=\"@+id/imageCardView\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                app:cardCornerRadius=\"22dp\"\n                app:cardElevation=\"0dp\"\n                app:cardPreventCornerOverlap=\"false\"\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\n                <ImageView\n                    android:id=\"@+id/userProfileImage\"\n                    android:layout_width=\"44dp\"\n                    android:layout_height=\"44dp\"\n                    android:layout_margin=\"0dp\"\n                    android:contentDescription=\"@string/user_image\"\n                    android:scaleType=\"centerCrop\"\n                    app:bind_image_url=\"@{viewmodel.otherUser.profileImageUrl}\"\n                    tools:src=\"@drawable/ic_baseline_person_24\" />\n            </androidx.cardview.widget.CardView>\n\n            <View\n                android:id=\"@+id/onlineView\"\n                android:layout_width=\"11dp\"\n                android:layout_height=\"11dp\"\n                android:layout_marginStart=\"35dp\"\n                android:background=\"@drawable/round_circle_online_green\"\n                android:visibility=\"@{viewmodel.otherUser.online == true? View.VISIBLE : View.INVISIBLE}\"\n                app:layout_constraintBottom_toBottomOf=\"@id/imageCardView\"\n                app:layout_constraintRight_toRightOf=\"@id/imageCardView\"\n                tools:visibility=\"visible\" />\n\n        </androidx.constraintlayout.widget.ConstraintLayout>\n\n        <LinearLayout\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentTop=\"true\"\n            android:layout_alignParentEnd=\"true\"\n            android:layout_centerInParent=\"true\"\n            android:layout_marginStart=\"16dp\"\n            android:layout_toEndOf=\"@id/imageContentLayout\"\n            android:orientation=\"vertical\">\n\n            <TextView\n                android:id=\"@+id/otherUserNameText\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n\n                android:maxLines=\"1\"\n                android:text=\"@{viewmodel.otherUser.displayName}\"\n                android:textColor=\"@android:color/white\"\n                android:textSize=\"18sp\"\n                tools:text=\"John\" />\n\n            <TextView\n                android:id=\"@+id/onlineStatusText\"\n                android:layout_width=\"wrap_content\"\n             \n                android:layout_height=\"wrap_content\"\n                android:alpha=\".7\"\n                android:maxLines=\"1\"\n                android:text=\"@{viewmodel.otherUser.online == true? `Online` : `Offline`}\"\n                android:textColor=\"@android:color/white\"\n                android:textSize=\"14sp\"\n                tools:text=\"Online\" />\n        </LinearLayout>\n\n\n    </RelativeLayout>\n\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/toolbar_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n\n<androidx.appcompat.widget.Toolbar xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@color/colorPrimary\"\n    android:minHeight=\"?attr/actionBarSize\"\n    android:theme=\"@style/ThemeOverlay.MaterialComponents.Dark\"\n    tools:ignore=\"Overdraw\">\n\n</androidx.appcompat.widget.Toolbar>\n\n"
  },
  {
    "path": "app/src/main/res/menu/bottom_nav_menu.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/navigation_chats\"\n        android:icon=\"@drawable/ic_baseline_chat_bubble_24\"\n        android:title=\"@string/title_chats\" />\n    <item\n        android:id=\"@+id/navigation_notifications\"\n        android:icon=\"@drawable/ic_baseline_notifications_24\"\n        android:title=\"@string/title_notifications\" />\n    <item\n        android:id=\"@+id/navigation_users\"\n        android:icon=\"@drawable/ic_baseline_people_24\"\n        android:title=\"@string/title_users\" />\n    <item\n        android:id=\"@+id/navigation_settings\"\n        android:icon=\"@drawable/ic_baseline_settings_24\"\n        android:title=\"@string/title_settings\" />\n</menu>"
  },
  {
    "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=\"@color/ic_launcher_background\"/>\n    <foreground android:drawable=\"@mipmap/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=\"@color/ic_launcher_background\"/>\n    <foreground android:drawable=\"@mipmap/ic_launcher_foreground\"/>\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/navigation/mobile_navigation.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<navigation 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    app:startDestination=\"@+id/startFragment\">\n\n    <fragment\n        android:id=\"@+id/navigation_chats\"\n        android:name=\"com.fredrikbogg.android_chat_app.ui.chats.ChatsFragment\"\n        android:label=\"@string/title_chats\"\n        tools:layout=\"@layout/fragment_chats\">\n        <action\n            android:id=\"@+id/action_navigation_chats_to_chatFragment\"\n            app:destination=\"@id/chatFragment\" />\n    </fragment>\n    <fragment\n        android:id=\"@+id/navigation_notifications\"\n        android:name=\"com.fredrikbogg.android_chat_app.ui.notifications.NotificationsFragment\"\n        android:label=\"@string/title_notifications\"\n        tools:layout=\"@layout/fragment_notifications\" />\n    <fragment\n        android:id=\"@+id/navigation_users\"\n        android:name=\"com.fredrikbogg.android_chat_app.ui.users.UsersFragment\"\n        android:label=\"@string/title_users\"\n        tools:layout=\"@layout/fragment_users\">\n        <action\n            android:id=\"@+id/action_navigation_users_to_profileFragment\"\n            app:destination=\"@id/profileFragment\" />\n    </fragment>\n    <fragment\n        android:id=\"@+id/navigation_settings\"\n        android:name=\"com.fredrikbogg.android_chat_app.ui.settings.SettingsFragment\"\n        android:label=\"@string/title_settings\"\n        tools:layout=\"@layout/fragment_settings\" >\n        <action\n            android:id=\"@+id/action_navigation_settings_to_startFragment\"\n            app:destination=\"@id/startFragment\"\n            app:popUpTo=\"@id/startFragment\"\n            app:popUpToInclusive=\"true\" />\n    </fragment>\n    <fragment\n        android:id=\"@+id/profileFragment\"\n        android:name=\"com.fredrikbogg.android_chat_app.ui.profile.ProfileFragment\"\n        android:label=\"@string/title_profile\"\n        tools:layout=\"@layout/fragment_profile\" />\n    <fragment\n        android:id=\"@+id/chatFragment\"\n        android:name=\"com.fredrikbogg.android_chat_app.ui.chat.ChatFragment\"\n        android:label=\"@string/title_chat\" />\n    <fragment\n        android:id=\"@+id/startFragment\"\n        android:name=\"com.fredrikbogg.android_chat_app.ui.start.StartFragment\"\n        android:label=\"Start\">\n        <action\n            android:id=\"@+id/action_startFragment_to_createAccountFragment\"\n            app:destination=\"@id/createAccountFragment\" />\n        <action\n            android:id=\"@+id/action_startFragment_to_loginFragment\"\n            app:destination=\"@id/loginFragment\" />\n        <action\n            android:id=\"@+id/action_startFragment_to_navigation_chats\"\n            app:destination=\"@id/navigation_chats\"\n            app:launchSingleTop=\"true\"\n            app:popUpTo=\"@id/startFragment\" />\n    </fragment>\n    <fragment\n        android:id=\"@+id/loginFragment\"\n        android:name=\"com.fredrikbogg.android_chat_app.ui.start.login.LoginFragment\"\n        android:label=\"Login\"\n        tools:layout=\"@layout/fragment_login\">\n        <action\n            android:id=\"@+id/action_loginFragment_to_navigation_chats\"\n            app:destination=\"@id/navigation_chats\"\n            app:launchSingleTop=\"true\"\n            app:popUpTo=\"@id/startFragment\" />\n    </fragment>\n    <fragment\n        android:id=\"@+id/createAccountFragment\"\n        android:name=\"com.fredrikbogg.android_chat_app.ui.start.createAccount.CreateAccountFragment\"\n        android:label=\"Create Account\"\n        tools:layout=\"@layout/fragment_create_account\">\n        <action\n            android:id=\"@+id/action_createAccountFragment_to_navigation_chats\"\n            app:destination=\"@id/navigation_chats\"\n            app:launchSingleTop=\"true\"\n            app:popUpTo=\"@id/startFragment\" />\n    </fragment>\n</navigation>"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#2D9CDB</color>\n    <color name=\"colorPrimaryDark\">@color/colorPrimary</color>\n    <color name=\"colorAccent\">#F3D231</color>\n    <color name=\"textError\">#ff6262</color>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/dimens.xml",
    "content": "<resources>\n    <!-- Default screen margins, per the Android Design guidelines. -->\n</resources>"
  },
  {
    "path": "app/src/main/res/values/font_certs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <array name=\"com_google_android_gms_fonts_certs\">\n        <item>@array/com_google_android_gms_fonts_certs_dev</item>\n        <item>@array/com_google_android_gms_fonts_certs_prod</item>\n    </array>\n    <string-array name=\"com_google_android_gms_fonts_certs_dev\">\n        <item>\n            MIIEqDCCA5CgAwIBAgIJANWFuGx90071MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAeFw0wODA0MTUyMzM2NTZaFw0zNTA5MDEyMzM2NTZaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBANbOLggKv+IxTdGNs8/TGFy0PTP6DHThvbbR24kT9ixcOd9W+EaBPWW+wPPKQmsHxajtWjmQwWfna8mZuSeJS48LIgAZlKkpFeVyxW0qMBujb8X8ETrWy550NaFtI6t9+u7hZeTfHwqNvacKhp1RbE6dBRGWynwMVX8XW8N1+UjFaq6GCJukT4qmpN2afb8sCjUigq0GuMwYXrFVee74bQgLHWGJwPmvmLHC69EH6kWr22ijx4OKXlSIx2xT1AsSHee70w5iDBiK4aph27yH3TxkXy9V89TDdexAcKk/cVHYNnDBapcavl7y0RiQ4biu8ymM8Ga/nmzhRKya6G0cGw8CAQOjgfwwgfkwHQYDVR0OBBYEFI0cxb6VTEM8YYY6FbBMvAPyT+CyMIHJBgNVHSMEgcEwgb6AFI0cxb6VTEM8YYY6FbBMvAPyT+CyoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJANWFuGx90071MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABnTDPEF+3iSP0wNfdIjIz1AlnrPzgAIHVvXxunW7SBrDhEglQZBbKJEk5kT0mtKoOD1JMrSu1xuTKEBahWRbqHsXclaXjoBADb0kkjVEJu/Lh5hgYZnOjvlba8Ld7HCKePCVePoTJBdI4fvugnL8TsgK05aIskyY0hKI9L8KfqfGTl1lzOv2KoWD0KWwtAWPoGChZxmQ+nBli+gwYMzM1vAkP+aayLe0a1EQimlOalO762r0GXO0ks+UeXde2Z4e+8S/pf7pITEI/tP+MxJTALw9QUWEv9lKTk+jkbqxbsh8nfBUapfKqYn0eidpwq2AzVp3juYl7//fKnaPhJD9gs=\n        </item>\n    </string-array>\n    <string-array name=\"com_google_android_gms_fonts_certs_prod\">\n        <item>\n            MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAQOjgdkwgdYwHQYDVR0OBBYEFMd9jMIhF1Ylmn/Tgt9r45jk14alMIGmBgNVHSMEgZ4wgZuAFMd9jMIhF1Ylmn/Tgt9r45jk14aloXikdjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLR29vZ2xlIEluYy4xEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMTB0FuZHJvaWSCCQDC4IdGZEowjTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBAUAA4IBAQBt0lLO74UwLDYKqs6Tm8/yzKkEu116FmH4rkaymUIE0P9KaMftGlMexFlaYjzmB2OxZyl6euNXEsQH8gjwyxCUKRJNexBiGcCEyj6z+a1fuHHvkiaai+KL8W1EyNmgjmyy8AW7P+LLlkR+ho5zEHatRbM/YAnqGcFh5iZBqpknHf1SKMXFh4dd239FJ1jWYfbMDMy3NS5CTMQ2XFI1MvcyUTdZPErjQfTbQe3aDQsQcafEQPD+nqActifKZ0Np0IS9L9kR/wbNvyz6ENwPiTrjV2KRkEjH78ZMcUQXg0L3BYHJ3lc69Vs5Ddf9uUGGMYldX3WfMBEmh/9iFBDAaTCK\n        </item>\n    </string-array>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"ic_launcher_background\">#FFFFFF</color>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/preloaded_fonts.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <array name=\"preloaded_fonts\" translatable=\"false\">\n        <item>@font/nunito</item>\n        <item>@font/nunito_bold</item>\n        <item>@font/nunito_extrabold</item>\n        <item>@font/nunito_semibold</item>\n    </array>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">Chat App</string>\n    <string name=\"title_chats\">Chats</string>\n    <string name=\"title_notifications\">Notifications</string>\n    <string name=\"title_users\">Users</string>\n    <string name=\"title_settings\">Settings</string>\n    <string name=\"title_profile\">Profile</string>\n    <string name=\"title_chat\">Chat</string>\n    <string name=\"user_image\">User image</string>\n    <string name=\"accept\">Accept</string>\n    <string name=\"decline\">Decline</string>\n    <string name=\"enter_message\">Enter message</string>\n    <string name=\"send\">SEND</string>\n    <string name=\"create_a_new_account\">Create a new account</string>\n    <string name=\"display_name\">Display name</string>\n    <string name=\"email\">Email</string>\n    <string name=\"password\">Password</string>\n    <string name=\"create\">Create</string>\n    <string name=\"login_to_your_account\">Login to your account</string>\n    <string name=\"add_friend\">Add Friend</string>\n    <string name=\"remove_friend\">Remove Friend</string>\n    <string name=\"accept_friend_request\">Accept Friend Request</string>\n    <string name=\"request_sent\">Request sent</string>\n    <string name=\"decline_friend_request\">Decline Friend Request</string>\n    <string name=\"change_image\">Change Image</string>\n    <string name=\"logout\">Logout</string>\n    <string name=\"change_status\">Change Status</string>\n    <string name=\"create_account\">Create account</string>\n    <string name=\"new_friend_request\">New friend request</string>\n    <string name=\"login\">login</string>\n    <string name=\"welcome_to_quick_chat\">Welcome to Quick Chat</string>\n    <string name=\"login_or_create_an_account_to_get_started\">Login or create an account to get started</string>\n    <string name=\"chat_icon\">Chat icon</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <style name=\"AppTheme\" parent=\"Theme.MaterialComponents.Light.NoActionBar\">\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n        <item name=\"fontFamily\">@font/nunito</item>\n        <item name=\"android:navigationBarColor\">@android:color/black</item>\n    </style>\n\n    <style name=\"MessageNotSeen\" parent=\"@style/TextAppearance.AppCompat\">\n        <item name=\"android:textColor\">?attr/colorPrimary</item>\n        <item name=\"android:fontFamily\">@font/nunito_bold</item>\n        <item name=\"android:textStyle\">bold</item>\n    </style>\n\n    <style name=\"MessageSeen\" parent=\"@style/TextAppearance.AppCompat\">\n        <item name=\"android:fontFamily\">@font/nunito</item>\n     <item name=\"android:textColor\">#808080</item>\n    </style>\n\n    <style name=\"BoldText\" parent=\"@style/TextAppearance.AppCompat\">\n        <item name=\"android:fontFamily\">@font/nunito_bold</item>\n    </style>\n\n    <style name=\"VeryBoldText\" parent=\"@style/TextAppearance.AppCompat\">\n        <item name=\"android:textStyle\">bold</item>\n    </style>\n</resources>"
  },
  {
    "path": "app/src/test/java/com/fredrikbogg/android_chat_app/ExampleUnitTest.kt",
    "content": "package com.fredrikbogg.android_chat_app\n\nimport org.junit.Test\n\nimport org.junit.Assert.*\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\nclass ExampleUnitTest {\n    @Test\n    fun addition_isCorrect() {\n        assertEquals(4, 2 + 2)\n    }\n}"
  },
  {
    "path": "build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\nbuildscript {\n    ext.kotlin_version = \"1.4.0\"\n    repositories {\n        google()\n        jcenter()\n    }\n    dependencies {\n        classpath \"com.android.tools.build:gradle:4.0.1\"\n        classpath \"org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version\"\n        classpath 'com.google.gms:google-services:4.3.3'\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    repositories {\n        google()\n        jcenter()\n    }\n}\n\ntask clean(type: Delete) {\n    delete rootProject.buildDir\n}"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Wed Aug 05 11:42:06 CEST 2020\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-6.1.1-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=-Xmx2048m\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\n# AndroidX package structure to make it clearer which packages are bundled with the\n# Android operating system, and which are packaged with your app\"s APK\n# https://developer.android.com/topic/libraries/support-library/androidx-rn\nandroid.useAndroidX=true\n# Automatically convert third-party libraries to use AndroidX\nandroid.enableJetifier=true\n# Kotlin code style for this project: \"official\" or \"obsolete\":\nkotlin.code.style=official"
  },
  {
    "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'\nrootProject.name = \"Android-Chat-App\""
  }
]