Full Code of dgewe/Chat-App-Android for AI

master cff2f947a449 cached
118 files
198.1 KB
51.7k tokens
1 requests
Download .txt
Showing preview only (231K chars total). Download the full file or copy to clipboard to get everything.
Repository: dgewe/Chat-App-Android
Branch: master
Commit: cff2f947a449
Files: 118
Total size: 198.1 KB

Directory structure:
gitextract_1onv52u2/

├── .gitattributes
├── .gitignore
├── .idea/
│   ├── .name
│   ├── codeStyles/
│   │   ├── Project.xml
│   │   └── codeStyleConfig.xml
│   ├── jarRepositories.xml
│   ├── misc.xml
│   ├── render.experimental.xml
│   └── runConfigurations.xml
├── LICENSE
├── README.md
├── app/
│   ├── .gitignore
│   ├── build.gradle
│   ├── google-services.json
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── com/
│       │           └── fredrikbogg/
│       │               └── android_chat_app/
│       │                   └── ExampleInstrumentedTest.kt
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   ├── java/
│       │   │   └── com/
│       │   │       └── fredrikbogg/
│       │   │           └── android_chat_app/
│       │   │               ├── App.kt
│       │   │               ├── data/
│       │   │               │   ├── Event.kt
│       │   │               │   ├── Result.kt
│       │   │               │   ├── db/
│       │   │               │   │   ├── entity/
│       │   │               │   │   │   ├── Chat.kt
│       │   │               │   │   │   ├── Message.kt
│       │   │               │   │   │   └── User.kt
│       │   │               │   │   ├── remote/
│       │   │               │   │   │   ├── FirebaseAuthSource.kt
│       │   │               │   │   │   ├── FirebaseDatabaseSource.kt
│       │   │               │   │   │   └── FirebaseStorageSource.kt
│       │   │               │   │   └── repository/
│       │   │               │   │       ├── AuthRepository.kt
│       │   │               │   │       ├── DatabaseRepository.kt
│       │   │               │   │       └── StorageRepository.kt
│       │   │               │   └── model/
│       │   │               │       ├── ChatWithUserInfo.kt
│       │   │               │       ├── CreateUser.kt
│       │   │               │       └── Login.kt
│       │   │               ├── ui/
│       │   │               │   ├── DefaultBindings.kt
│       │   │               │   ├── DefaultViewModel.kt
│       │   │               │   ├── chat/
│       │   │               │   │   ├── ChatFragment.kt
│       │   │               │   │   ├── ChatViewModel.kt
│       │   │               │   │   ├── MessagesBindings.kt
│       │   │               │   │   └── MessagesListAdapter.kt
│       │   │               │   ├── chats/
│       │   │               │   │   ├── ChatsBindings.kt
│       │   │               │   │   ├── ChatsFragment.kt
│       │   │               │   │   ├── ChatsListAdapter.kt
│       │   │               │   │   └── ChatsViewModel.kt
│       │   │               │   ├── main/
│       │   │               │   │   ├── MainActivity.kt
│       │   │               │   │   └── MainViewModel.kt
│       │   │               │   ├── notifications/
│       │   │               │   │   ├── NotificationsBindings.kt
│       │   │               │   │   ├── NotificationsFragment.kt
│       │   │               │   │   ├── NotificationsListAdapter.kt
│       │   │               │   │   └── NotificationsViewModel.kt
│       │   │               │   ├── profile/
│       │   │               │   │   ├── ProfileFragment.kt
│       │   │               │   │   └── ProfileViewModel.kt
│       │   │               │   ├── settings/
│       │   │               │   │   ├── SettingsFragment.kt
│       │   │               │   │   └── SettingsViewModel.kt
│       │   │               │   ├── start/
│       │   │               │   │   ├── StartFragment.kt
│       │   │               │   │   ├── StartViewModel.kt
│       │   │               │   │   ├── createAccount/
│       │   │               │   │   │   ├── CreateAccountFragment.kt
│       │   │               │   │   │   └── CreateAccountViewModel.kt
│       │   │               │   │   └── login/
│       │   │               │   │       ├── LoginFragment.kt
│       │   │               │   │       └── LoginViewModel.kt
│       │   │               │   └── users/
│       │   │               │       ├── UsersBindings.kt
│       │   │               │       ├── UsersFragment.kt
│       │   │               │       ├── UsersListAdapter.kt
│       │   │               │       └── UsersViewModel.kt
│       │   │               └── util/
│       │   │                   ├── FileConverterUtil.kt
│       │   │                   ├── FirebaseUtil.kt
│       │   │                   ├── LiveDataExt.kt
│       │   │                   ├── SharedPreferencesUtil.kt
│       │   │                   ├── TextUtil.kt
│       │   │                   └── ViewExt.kt
│       │   └── res/
│       │       ├── drawable/
│       │       │   ├── ic_baseline_chat_bubble_24.xml
│       │       │   ├── ic_baseline_error_24.xml
│       │       │   ├── ic_baseline_notifications_24.xml
│       │       │   ├── ic_baseline_people_24.xml
│       │       │   ├── ic_baseline_person_24.xml
│       │       │   ├── ic_baseline_settings_24.xml
│       │       │   ├── round_circle_online_green.xml
│       │       │   └── round_circle_primary.xml
│       │       ├── drawable-v24/
│       │       │   ├── rounded_rectangle_primary.xml
│       │       │   └── rounded_rectangle_secondary.xml
│       │       ├── font/
│       │       │   ├── nunito.xml
│       │       │   ├── nunito_bold.xml
│       │       │   ├── nunito_extrabold.xml
│       │       │   └── nunito_semibold.xml
│       │       ├── layout/
│       │       │   ├── activity_main.xml
│       │       │   ├── fragment_chat.xml
│       │       │   ├── fragment_chats.xml
│       │       │   ├── fragment_create_account.xml
│       │       │   ├── fragment_login.xml
│       │       │   ├── fragment_notifications.xml
│       │       │   ├── fragment_profile.xml
│       │       │   ├── fragment_settings.xml
│       │       │   ├── fragment_start.xml
│       │       │   ├── fragment_users.xml
│       │       │   ├── list_item_chat.xml
│       │       │   ├── list_item_message_received.xml
│       │       │   ├── list_item_message_sent.xml
│       │       │   ├── list_item_notification.xml
│       │       │   ├── list_item_user.xml
│       │       │   ├── toolbar_addon_chat.xml
│       │       │   └── toolbar_main.xml
│       │       ├── menu/
│       │       │   └── bottom_nav_menu.xml
│       │       ├── mipmap-anydpi-v26/
│       │       │   ├── ic_launcher.xml
│       │       │   └── ic_launcher_round.xml
│       │       ├── navigation/
│       │       │   └── mobile_navigation.xml
│       │       └── values/
│       │           ├── colors.xml
│       │           ├── dimens.xml
│       │           ├── font_certs.xml
│       │           ├── ic_launcher_background.xml
│       │           ├── preloaded_fonts.xml
│       │           ├── strings.xml
│       │           └── styles.xml
│       └── test/
│           └── java/
│               └── com/
│                   └── fredrikbogg/
│                       └── android_chat_app/
│                           └── ExampleUnitTest.kt
├── build.gradle
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitattributes
================================================
# Auto detect text files and perform LF normalization
* text=auto


================================================
FILE: .gitignore
================================================
# Built application files
*.apk
*.aar
*.ap_
*.aab

# Files for the ART/Dalvik VM
*.dex

# Java class files
*.class

# Generated files
bin/
gen/
out/
#  Uncomment the following line in case you need and you don't have the release build type files in your app
# release/

# Gradle files
.gradle/
build/

# Local configuration file (sdk path, etc)
local.properties

# Proguard folder generated by Eclipse
proguard/

# Log Files
*.log

# Android Studio Navigation editor temp files
.navigation/

# Android Studio captures folder
captures/

# IntelliJ
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/assetWizardSettings.xml
.idea/dictionaries
.idea/libraries
# Android Studio 3 in .gitignore file.
.idea/caches
.idea/modules.xml
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
.idea/navEditor.xml

# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.
#*.jks
#*.keystore

# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
.cxx/

# Google Services (e.g. APIs or Firebase)
# google-services.json

# Freeline
freeline.py
freeline/
freeline_project_description.json

# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md

# Version control
vcs.xml

# lint
lint/intermediates/
lint/generated/
lint/outputs/
lint/tmp/
# lint/reports/


================================================
FILE: .idea/.name
================================================
Android-Chat-App

================================================
FILE: .idea/codeStyles/Project.xml
================================================
<component name="ProjectCodeStyleConfiguration">
  <code_scheme name="Project" version="173">
    <JetCodeStyleSettings>
      <option name="PACKAGES_TO_USE_STAR_IMPORTS">
        <value>
          <package name="java.util" alias="false" withSubpackages="false" />
          <package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
          <package name="io.ktor" alias="false" withSubpackages="true" />
        </value>
      </option>
      <option name="PACKAGES_IMPORT_LAYOUT">
        <value>
          <package name="" alias="false" withSubpackages="true" />
          <package name="java" alias="false" withSubpackages="true" />
          <package name="javax" alias="false" withSubpackages="true" />
          <package name="kotlin" alias="false" withSubpackages="true" />
          <package name="" alias="true" withSubpackages="true" />
        </value>
      </option>
      <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
    </JetCodeStyleSettings>
    <codeStyleSettings language="XML">
      <indentOptions>
        <option name="CONTINUATION_INDENT_SIZE" value="4" />
      </indentOptions>
      <arrangement>
        <rules>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>xmlns:android</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>^$</XML_NAMESPACE>
                </AND>
              </match>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>xmlns:.*</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>^$</XML_NAMESPACE>
                </AND>
              </match>
              <order>BY_NAME</order>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*:id</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
                </AND>
              </match>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*:name</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
                </AND>
              </match>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>name</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>^$</XML_NAMESPACE>
                </AND>
              </match>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>style</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>^$</XML_NAMESPACE>
                </AND>
              </match>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>^$</XML_NAMESPACE>
                </AND>
              </match>
              <order>BY_NAME</order>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
                </AND>
              </match>
              <order>ANDROID_ATTRIBUTE_ORDER</order>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>.*</XML_NAMESPACE>
                </AND>
              </match>
              <order>BY_NAME</order>
            </rule>
          </section>
        </rules>
      </arrangement>
    </codeStyleSettings>
    <codeStyleSettings language="kotlin">
      <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
    </codeStyleSettings>
  </code_scheme>
</component>

================================================
FILE: .idea/codeStyles/codeStyleConfig.xml
================================================
<component name="ProjectCodeStyleConfiguration">
  <state>
    <option name="USE_PER_PROJECT_SETTINGS" value="true" />
  </state>
</component>

================================================
FILE: .idea/jarRepositories.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="RemoteRepositoriesConfiguration">
    <remote-repository>
      <option name="id" value="central" />
      <option name="name" value="Maven Central repository" />
      <option name="url" value="https://repo1.maven.org/maven2" />
    </remote-repository>
    <remote-repository>
      <option name="id" value="jboss.community" />
      <option name="name" value="JBoss Community repository" />
      <option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
    </remote-repository>
    <remote-repository>
      <option name="id" value="BintrayJCenter" />
      <option name="name" value="BintrayJCenter" />
      <option name="url" value="https://jcenter.bintray.com/" />
    </remote-repository>
    <remote-repository>
      <option name="id" value="Google" />
      <option name="name" value="Google" />
      <option name="url" value="https://dl.google.com/dl/android/maven2/" />
    </remote-repository>
  </component>
</project>

================================================
FILE: .idea/misc.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
    <output url="file://$PROJECT_DIR$/build/classes" />
  </component>
  <component name="ProjectType">
    <option name="id" value="Android" />
  </component>
</project>

================================================
FILE: .idea/render.experimental.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="RenderSettings">
    <option name="showDecorations" value="true" />
  </component>
</project>

================================================
FILE: .idea/runConfigurations.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="RunConfigurationProducerService">
    <option name="ignoredProducers">
      <set>
        <option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
        <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
        <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
      </set>
    </option>
  </component>
</project>

================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2020 Fredrik Bogg

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Chat App Android
![HeaderImage](github_images/header.png)

## Introduction
This is a demo application built with the goal to create a fun and challenging application based on the MVVM architectural pattern.

See below for more information.

## Technologies & Architecture 

#### Technologies
Android, Kotlin

#### Architecture
Model-View-ViewModel (MVVM)

#### Firebase
* Authentication
* Realtime Database
* Storage

#### Architecture Components
[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), 
[Navigation](https://developer.android.com/guide/navigation/)

## Features

**Start:** Login/create account

**Chats:** List of chats, online status, update on change

**Notifications:** Accept/decline friend requests, notifications symbol

**Users:** List of users

**Settings:** Change image, change status, logout

**Chat:** Send and show messages sorted by timestamp, online status, custom toolbar, update on change

**Profile:** Add/remove friend, accept/decline friend request

**General:** Auto login, bottom navigation, error messages with snackbar, progress bar

## Screenshots

### Start | Login | Create Account

<p align = "left" >
  <img width="250" height="500" src="github_images/start.png">
  <img width="250" height="500"  src="github_images/login.png"> 
  <img width="250" height="500" src="github_images/create.png"> 
</p>

### Chats | Notifications | Users

<p align = "left" >
  <img width="250" height="500" src="github_images/chats.png">
  <img width="250" height="500"  src="github_images/notifications.png"> 
  <img width="250" height="500" src="github_images/users.png"> 
</p>

### Settings | Chat | Profile

<p align = "left" >
  <img width="250" height="500" src="github_images/settings.png">
  <img width="250" height="500"  src="github_images/chat.png"> 
  <img width="250" height="500" src="github_images/profile.png"> 
</p>

### Firebase
<p align = "left" >
  <img  width="378" height="332" src="github_images/db.png"> 
</p>

## Setup
#### Requirements
* Basic knowledge about Android Studio
* Basic knowledge about Firebase

#### Firebase
 * Setup Authentication and use the Sign-in method 'Email/Password'
 * Setup Realtime Database
 * Setup Storage
 * Replace the file [google-services.json](app/google-services.json)
 * <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.
 * <b>Note:</b> When updating the google-services.json file then make sure to invalidate the caches as well as doing a clean + rebuild.

#### Project
1. Download and open the project in Android Studio
2. Connect your Android phone or use the emulator to start the application


================================================
FILE: app/.gitignore
================================================
/build

================================================
FILE: app/build.gradle
================================================
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'kotlin-kapt'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.3"

    defaultConfig {
        applicationId "com.fredrikbogg.android_chat_app"
        minSdkVersion 26
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

    buildFeatures {
        dataBinding = true
        viewBinding = true
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.3.1'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.2.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'androidx.legacy:legacy-support-v4:1.0.0'

    //Navigation, lifecycle
    implementation 'androidx.navigation:navigation-fragment:2.3.0'
    implementation 'androidx.navigation:navigation-ui:2.3.0'
    implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
    implementation 'androidx.navigation:navigation-fragment-ktx:2.3.0'
    implementation 'androidx.navigation:navigation-ui-ktx:2.3.0'
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'

    // Firebase
    implementation 'com.google.firebase:firebase-database:19.3.1'
    implementation 'com.google.firebase:firebase-auth:19.3.2'
    implementation 'com.google.firebase:firebase-storage:19.1.1'

    // Picasso
    implementation 'com.squareup.picasso:picasso:2.71828'
    implementation 'jp.wasabeef:picasso-transformations:2.2.1'

    testImplementation 'junit:junit:4.13'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}


================================================
FILE: app/google-services.json
================================================
-EDIT THIS-

================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

================================================
FILE: app/src/androidTest/java/com/fredrikbogg/android_chat_app/ExampleInstrumentedTest.kt
================================================
package com.fredrikbogg.android_chat_app

import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4

import org.junit.Test
import org.junit.runner.RunWith

import org.junit.Assert.*

/**
 * Instrumented test, which will execute on an Android device.
 *
 * See [testing documentation](http://d.android.com/tools/testing).
 */
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
    @Test
    fun useAppContext() {
        // Context of the app under test.
        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
        assertEquals("com.fredrikbogg.android_chat_app", appContext.packageName)
    }
}

================================================
FILE: app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.fredrikbogg.android_chat_app">

    <uses-permission android:name="android.permission.INTERNET" />
    <application
        android:name="com.fredrikbogg.android_chat_app.App"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name="com.fredrikbogg.android_chat_app.ui.main.MainActivity"
            android:label="@string/app_name"
            android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <meta-data
            android:name="preloaded_fonts"
            android:resource="@array/preloaded_fonts" />
    </application>

</manifest>

================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/App.kt
================================================
package com.fredrikbogg.android_chat_app

import android.app.Application
import com.fredrikbogg.android_chat_app.util.SharedPreferencesUtil


class App : Application() {

    override fun onCreate() {
        super.onCreate()
        application = this
    }

    companion object {
        lateinit var application: Application
            private set

        var myUserID: String = ""
            get() {
                field = SharedPreferencesUtil.getUserID(application.applicationContext).orEmpty()
                return field
            }
            private set
    }
}


================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/data/Event.kt
================================================
package com.fredrikbogg.android_chat_app.data

import androidx.lifecycle.Observer


open class Event<out T>(private val content: T) {
    private var isHandled = false

    fun getContentIfNotHandled(): T? {
        return if (isHandled) {
            null
        } else {
            isHandled = true
            content
        }
    }
}

class EventObserver<T>(private val onEventUnhandledContent: (T) -> Unit) : Observer<Event<T>> {
    override fun onChanged(event: Event<T>?) {
        event?.getContentIfNotHandled()?.let { onEventUnhandledContent(it) }
    }
}


================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/data/Result.kt
================================================
package com.fredrikbogg.android_chat_app.data


sealed class Result<out R> {
    data class Success<out T>(val data: T? = null, val msg: String? = null) : Result<T>()
    class Error(val msg: String? = null) : Result<Nothing>()
    object Loading : Result<Nothing>()
}


================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/data/db/entity/Chat.kt
================================================
package com.fredrikbogg.android_chat_app.data.db.entity

import com.google.firebase.database.PropertyName


data class Chat(
    @get:PropertyName("lastMessage") @set:PropertyName("lastMessage") var lastMessage: Message = Message(),
    @get:PropertyName("info") @set:PropertyName("info") var info: ChatInfo = ChatInfo()
)

data class ChatInfo(
    @get:PropertyName("id") @set:PropertyName("id") var id: String = ""
)

================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/data/db/entity/Message.kt
================================================
package com.fredrikbogg.android_chat_app.data.db.entity

import com.google.firebase.database.PropertyName
import java.util.*


data class Message(
    @get:PropertyName("senderID") @set:PropertyName("senderID") var senderID: String = "",
    @get:PropertyName("text") @set:PropertyName("text") var text: String = "",
    @get:PropertyName("epochTimeMs") @set:PropertyName("epochTimeMs") var epochTimeMs: Long = Date().time,
    @get:PropertyName("seen") @set:PropertyName("seen") var seen: Boolean = false
)

================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/data/db/entity/User.kt
================================================
package com.fredrikbogg.android_chat_app.data.db.entity

import com.google.firebase.database.PropertyName


data class User(
    @get:PropertyName("info") @set:PropertyName("info") var info: UserInfo = UserInfo(),
    @get:PropertyName("friends") @set:PropertyName("friends") var friends: HashMap<String, UserFriend> = HashMap(),
    @get:PropertyName("notifications") @set:PropertyName("notifications") var notifications: HashMap<String, UserNotification> = HashMap(),
    @get:PropertyName("sentRequests") @set:PropertyName("sentRequests") var sentRequests: HashMap<String, UserRequest> = HashMap()
)

data class UserFriend(
    @get:PropertyName("userID") @set:PropertyName("userID") var userID: String = ""
)

data class UserInfo(
    @get:PropertyName("id") @set:PropertyName("id") var id: String = "",
    @get:PropertyName("displayName") @set:PropertyName("displayName") var displayName: String = "",
    @get:PropertyName("status") @set:PropertyName("status") var status: String = "No status",
    @get:PropertyName("profileImageUrl") @set:PropertyName("profileImageUrl") var profileImageUrl: String = "",
    @get:PropertyName("online") @set:PropertyName("online") var online: Boolean = false
)

data class UserNotification(
    @get:PropertyName("userID") @set:PropertyName("userID") var userID: String = ""
)

data class UserRequest(
    @get:PropertyName("userID") @set:PropertyName("userID") var userID: String = ""
)



================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/data/db/remote/FirebaseAuthSource.kt
================================================
package com.fredrikbogg.android_chat_app.data.db.remote

import com.fredrikbogg.android_chat_app.data.model.CreateUser
import com.fredrikbogg.android_chat_app.data.model.Login
import com.fredrikbogg.android_chat_app.data.Result
import com.google.android.gms.tasks.Task
import com.google.firebase.auth.AuthResult
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseUser

class FirebaseAuthStateObserver {

    private var authListener: FirebaseAuth.AuthStateListener? = null
    private var instance: FirebaseAuth? = null

    fun start(valueEventListener: FirebaseAuth.AuthStateListener, instance: FirebaseAuth) {
        this.authListener = valueEventListener
        this.instance = instance
        this.instance!!.addAuthStateListener(authListener!!)
    }

    fun clear() {
        authListener?.let { instance?.removeAuthStateListener(it) }
    }
}

class FirebaseAuthSource {

    companion object {
        val authInstance = FirebaseAuth.getInstance()
    }

    private fun attachAuthObserver(b: ((Result<FirebaseUser>) -> Unit)): FirebaseAuth.AuthStateListener {
        return FirebaseAuth.AuthStateListener {
            if (it.currentUser == null) {
                b.invoke(Result.Error("No user"))
            } else { b.invoke(Result.Success(it.currentUser)) }
        }
    }

    fun loginWithEmailAndPassword(login: Login): Task<AuthResult> {
        return authInstance.signInWithEmailAndPassword(login.email, login.password)
    }

    fun createUser(createUser: CreateUser): Task<AuthResult> {
        return authInstance.createUserWithEmailAndPassword(createUser.email, createUser.password)
    }

    fun logout() {
        authInstance.signOut()
    }

    fun attachAuthStateObserver(firebaseAuthStateObserver: FirebaseAuthStateObserver, b: ((Result<FirebaseUser>) -> Unit)) {
        val listener = attachAuthObserver(b)
        firebaseAuthStateObserver.start(listener, authInstance)
    }
}



================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/data/db/remote/FirebaseDatabaseSource.kt
================================================
package com.fredrikbogg.android_chat_app.data.db.remote

import com.fredrikbogg.android_chat_app.data.Result
import com.fredrikbogg.android_chat_app.data.db.entity.*
import com.fredrikbogg.android_chat_app.util.wrapSnapshotToArrayList
import com.fredrikbogg.android_chat_app.util.wrapSnapshotToClass
import com.google.android.gms.tasks.Task
import com.google.android.gms.tasks.TaskCompletionSource
import com.google.firebase.database.*

class FirebaseReferenceConnectedObserver {

    private var valueEventListener: ValueEventListener? = null
    private var dbRef: DatabaseReference? = null
    private var userRef: DatabaseReference? = null

    fun start(userID: String) {
        this.userRef = FirebaseDataSource.dbInstance.reference.child("users/$userID/info/online")
        this.valueEventListener = getEventListener(userID)
        this.dbRef = FirebaseDataSource.dbInstance.getReference(".info/connected").apply { addValueEventListener(valueEventListener!!) }
    }

    private fun getEventListener(userID: String): ValueEventListener {
        return (object : ValueEventListener {
            override fun onDataChange(snapshot: DataSnapshot) {
                val connected = snapshot.getValue(Boolean::class.java) ?: false
                if (connected) {
                    FirebaseDataSource.dbInstance.reference.child("users/$userID/info/online").setValue(true)
                    userRef?.onDisconnect()?.setValue(false)
                }
            }

            override fun onCancelled(error: DatabaseError) {}
        })
    }

    fun clear() {
        valueEventListener?.let { dbRef?.removeEventListener(it) }
        userRef?.setValue(false)
        valueEventListener = null
        dbRef = null
        userRef = null
    }
}

class FirebaseReferenceValueObserver {
    private var valueEventListener: ValueEventListener? = null
    private var dbRef: DatabaseReference? = null

    fun start(valueEventListener: ValueEventListener, reference: DatabaseReference) {
        reference.addValueEventListener(valueEventListener)
        this.valueEventListener = valueEventListener
        this.dbRef = reference
    }

    fun clear() {
        valueEventListener?.let { dbRef?.removeEventListener(it) }
        valueEventListener = null
        dbRef = null
    }
}

class FirebaseReferenceChildObserver {
    private var valueEventListener: ChildEventListener? = null
    private var dbRef: DatabaseReference? = null
    private var isObserving: Boolean = false

    fun start(valueEventListener: ChildEventListener, reference: DatabaseReference) {
        isObserving = true
        reference.addChildEventListener(valueEventListener)
        this.valueEventListener = valueEventListener
        this.dbRef = reference
    }

    fun clear() {
        valueEventListener?.let { dbRef?.removeEventListener(it) }
        isObserving = false
        valueEventListener = null
        dbRef = null
    }

    fun isObserving(): Boolean {
        return isObserving
    }
}

// Task based
class FirebaseDataSource {

    companion object {
        val dbInstance = FirebaseDatabase.getInstance()
    }

    //region Private

    private fun refToPath(path: String): DatabaseReference {
        return dbInstance.reference.child(path)
    }

    private fun attachValueListenerToTaskCompletion(src: TaskCompletionSource<DataSnapshot>): ValueEventListener {
        return (object : ValueEventListener {
            override fun onCancelled(error: DatabaseError) { src.setException(Exception(error.message)) }

            override fun onDataChange(snapshot: DataSnapshot) { src.setResult(snapshot) }
        })
    }

    private fun <T> attachValueListenerToBlock(resultClassName: Class<T>, b: ((Result<T>) -> Unit)): ValueEventListener {
        return (object : ValueEventListener {
            override fun onCancelled(error: DatabaseError) { b.invoke(Result.Error(error.message)) }

            override fun onDataChange(snapshot: DataSnapshot) {
                if (wrapSnapshotToClass(resultClassName, snapshot) == null) {
                    b.invoke(Result.Error(msg = snapshot.key))
                } else {
                    b.invoke(Result.Success(wrapSnapshotToClass(resultClassName, snapshot)))
                }
            }
        })
    }

    private fun <T> attachValueListenerToBlockWithList(resultClassName: Class<T>, b: ((Result<MutableList<T>>) -> Unit)): ValueEventListener {
        return (object : ValueEventListener {
            override fun onCancelled(error: DatabaseError) { b.invoke(Result.Error(error.message)) }
            override fun onDataChange(snapshot: DataSnapshot) {
                b.invoke(Result.Success(wrapSnapshotToArrayList(resultClassName, snapshot)))
            }
        })
    }

    private fun <T> attachChildListenerToBlock(resultClassName: Class<T>, b: ((Result<T>) -> Unit)): ChildEventListener {
        return (object : ChildEventListener {
            override fun onChildAdded(snapshot: DataSnapshot, previousChildName: String?) {
                b.invoke(Result.Success(wrapSnapshotToClass(resultClassName, snapshot)))
            }

            override fun onCancelled(error: DatabaseError) { b.invoke(Result.Error(error.message)) }

            override fun onChildMoved(snapshot: DataSnapshot, previousChildName: String?) {}

            override fun onChildChanged(snapshot: DataSnapshot, previousChildName: String?) {}

            override fun onChildRemoved(snapshot: DataSnapshot) {}
        })
    }

    //endregion

    //region Update

    fun updateUserProfileImageUrl(userID: String, url: String) {
        refToPath("users/$userID/info/profileImageUrl").setValue(url)
    }

    fun updateUserStatus(userID: String, status: String) {
        refToPath("users/$userID/info/status").setValue(status)
    }

    fun updateLastMessage(chatID: String, message: Message) {
        refToPath("chats/$chatID/lastMessage").setValue(message)
    }

    fun updateNewFriend(myUser: UserFriend, otherUser: UserFriend) {
        refToPath("users/${myUser.userID}/friends/${otherUser.userID}").setValue(otherUser)
        refToPath("users/${otherUser.userID}/friends/${myUser.userID}").setValue(myUser)
    }

    fun updateNewSentRequest(userID: String, userRequest: UserRequest) {
        refToPath("users/${userID}/sentRequests/${userRequest.userID}").setValue(userRequest)
    }

    fun updateNewNotification(otherUserID: String, userNotification: UserNotification) {
        refToPath("users/${otherUserID}/notifications/${userNotification.userID}").setValue(userNotification)
    }

    fun updateNewUser(user: User) {
        refToPath("users/${user.info.id}").setValue(user)
    }

    fun updateNewChat(chat: Chat) {
        refToPath("chats/${chat.info.id}").setValue(chat)
    }

    fun pushNewMessage(messagesID: String, message: Message) {
        refToPath("messages/$messagesID").push().setValue(message)
    }

    //endregion

    //region Remove

    fun removeNotification(userID: String, notificationID: String) {
        refToPath("users/${userID}/notifications/$notificationID").setValue(null)
    }

    fun removeFriend(userID: String, friendID: String) {
        refToPath("users/${userID}/friends/$friendID").setValue(null)
        refToPath("users/${friendID}/friends/$userID").setValue(null)
    }

    fun removeSentRequest(userID: String, sentRequestID: String) {
        refToPath("users/${userID}/sentRequests/$sentRequestID").setValue(null)
    }

    fun removeChat(chatID: String) {
        refToPath("chats/$chatID").setValue(null)
    }

    fun removeMessages(messagesID: String) {
        refToPath("messages/$messagesID").setValue(null)
    }

    //endregion

    //region Load
    fun loadUserTask(userID: String): Task<DataSnapshot> {
        val src = TaskCompletionSource<DataSnapshot>()
        val listener = attachValueListenerToTaskCompletion(src)
        refToPath("users/$userID").addListenerForSingleValueEvent(listener)
        return src.task
    }

    fun loadUserInfoTask(userID: String): Task<DataSnapshot> {
        val src = TaskCompletionSource<DataSnapshot>()
        val listener = attachValueListenerToTaskCompletion(src)
        refToPath("users/$userID/info").addListenerForSingleValueEvent(listener)
        return src.task
    }

    fun loadUsersTask(): Task<DataSnapshot> {
        val src = TaskCompletionSource<DataSnapshot>()
        val listener = attachValueListenerToTaskCompletion(src)
        refToPath("users").addListenerForSingleValueEvent(listener)
        return src.task
    }

    fun loadFriendsTask(userID: String): Task<DataSnapshot> {
        val src = TaskCompletionSource<DataSnapshot>()
        val listener = attachValueListenerToTaskCompletion(src)
        refToPath("users/$userID/friends").addListenerForSingleValueEvent(listener)
        return src.task
    }

    fun loadChatTask(chatID: String): Task<DataSnapshot> {
        val src = TaskCompletionSource<DataSnapshot>()
        val listener = attachValueListenerToTaskCompletion(src)
        refToPath("chats/$chatID").addListenerForSingleValueEvent(listener)
        return src.task
    }

    fun loadNotificationsTask(userID: String): Task<DataSnapshot> {
        val src = TaskCompletionSource<DataSnapshot>()
        val listener = attachValueListenerToTaskCompletion(src)
        refToPath("users/$userID/notifications").addListenerForSingleValueEvent(listener)
        return src.task
    }

    //endregion

    //region Value Observers

    fun <T> attachUserObserver(resultClassName: Class<T>, userID: String, refObs: FirebaseReferenceValueObserver, b: ((Result<T>) -> Unit)) {
        val listener = attachValueListenerToBlock(resultClassName, b)
        refObs.start(listener, refToPath("users/$userID"))
    }

    fun <T> attachUserInfoObserver(resultClassName: Class<T>, userID: String, refObs: FirebaseReferenceValueObserver, b: ((Result<T>) -> Unit)) {
        val listener = attachValueListenerToBlock(resultClassName, b)
        refObs.start(listener, refToPath("users/$userID/info"))
    }

    fun <T> attachUserNotificationsObserver(resultClassName: Class<T>, userID: String, firebaseReferenceValueObserver: FirebaseReferenceValueObserver,
        b: ((Result<MutableList<T>>) -> Unit)
    ) {
        val listener = attachValueListenerToBlockWithList(resultClassName, b)
        firebaseReferenceValueObserver.start(listener, refToPath("users/$userID/notifications"))
    }

    fun <T> attachMessagesObserver(resultClassName: Class<T>, messagesID: String, refObs: FirebaseReferenceChildObserver, b: ((Result<T>) -> Unit)) {
        val listener = attachChildListenerToBlock(resultClassName, b)
        refObs.start(listener, refToPath("messages/$messagesID"))
    }

    fun <T> attachChatObserver(resultClassName: Class<T>, chatID: String, refObs: FirebaseReferenceValueObserver, b: ((Result<T>) -> Unit)) {
        val listener = attachValueListenerToBlock(resultClassName, b)
        refObs.start(listener, refToPath("chats/$chatID"))
    }

    //endregion
}


================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/data/db/remote/FirebaseStorageSource.kt
================================================
package com.fredrikbogg.android_chat_app.data.db.remote

import android.net.Uri
import com.google.android.gms.tasks.Task
import com.google.firebase.storage.FirebaseStorage

// Task based
class FirebaseStorageSource {
    private val storageInstance = FirebaseStorage.getInstance()

    fun uploadUserImage(userID: String, bArr: ByteArray): Task<Uri> {
        val path = "user_photos/$userID/profile_image"
        val ref = storageInstance.reference.child(path)

        return ref.putBytes(bArr).continueWithTask {
            ref.downloadUrl
        }
    }
}

================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/data/db/repository/AuthRepository.kt
================================================
package com.fredrikbogg.android_chat_app.data.db.repository

import com.fredrikbogg.android_chat_app.data.model.CreateUser
import com.fredrikbogg.android_chat_app.data.model.Login
import com.fredrikbogg.android_chat_app.data.db.remote.FirebaseAuthSource
import com.fredrikbogg.android_chat_app.data.db.remote.FirebaseAuthStateObserver
import com.fredrikbogg.android_chat_app.data.Result
import com.google.firebase.auth.FirebaseUser

class AuthRepository{
    private val firebaseAuthService = FirebaseAuthSource()

    fun observeAuthState(stateObserver: FirebaseAuthStateObserver, b: ((Result<FirebaseUser>) -> Unit)){
        firebaseAuthService.attachAuthStateObserver(stateObserver,b)
    }

    fun loginUser(login: Login, b: ((Result<FirebaseUser>) -> Unit)) {
        b.invoke(Result.Loading)
        firebaseAuthService.loginWithEmailAndPassword(login).addOnSuccessListener {
            b.invoke(Result.Success(it.user))
        }.addOnFailureListener {
            b.invoke(Result.Error(msg = it.message))
        }
    }

    fun createUser(createUser: CreateUser, b: ((Result<FirebaseUser>) -> Unit)) {
        b.invoke(Result.Loading)
        firebaseAuthService.createUser(createUser).addOnSuccessListener {
            b.invoke(Result.Success(it.user))
        }.addOnFailureListener {
            b.invoke(Result.Error(msg = it.message))
        }
    }

    fun logoutUser() {
        firebaseAuthService.logout()
    }
}

================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/data/db/repository/DatabaseRepository.kt
================================================
package com.fredrikbogg.android_chat_app.data.db.repository

import com.fredrikbogg.android_chat_app.data.db.entity.*
import com.fredrikbogg.android_chat_app.data.db.remote.FirebaseDataSource
import com.fredrikbogg.android_chat_app.data.db.remote.FirebaseReferenceChildObserver
import com.fredrikbogg.android_chat_app.data.db.remote.FirebaseReferenceValueObserver
import com.fredrikbogg.android_chat_app.data.Result
import com.fredrikbogg.android_chat_app.util.wrapSnapshotToArrayList
import com.fredrikbogg.android_chat_app.util.wrapSnapshotToClass


class DatabaseRepository {
    private val firebaseDatabaseService = FirebaseDataSource()

    //region Update
    fun updateUserStatus(userID: String, status: String) {
        firebaseDatabaseService.updateUserStatus(userID, status)
    }

    fun updateNewMessage(messagesID: String, message: Message) {
        firebaseDatabaseService.pushNewMessage(messagesID, message)
    }

    fun updateNewUser(user: User) {
        firebaseDatabaseService.updateNewUser(user)
    }

    fun updateNewFriend(myUser: UserFriend, otherUser: UserFriend) {
        firebaseDatabaseService.updateNewFriend(myUser, otherUser)
    }

    fun updateNewSentRequest(userID: String, userRequest: UserRequest) {
        firebaseDatabaseService.updateNewSentRequest(userID, userRequest)
    }

    fun updateNewNotification(otherUserID: String, userNotification: UserNotification) {
        firebaseDatabaseService.updateNewNotification(otherUserID, userNotification)
    }

    fun updateChatLastMessage(chatID: String, message: Message) {
        firebaseDatabaseService.updateLastMessage(chatID, message)
    }

    fun updateNewChat(chat: Chat){
        firebaseDatabaseService.updateNewChat(chat)
    }

    fun updateUserProfileImageUrl(userID: String, url: String){
        firebaseDatabaseService.updateUserProfileImageUrl(userID, url)
    }

    //endregion

    //region Remove
    fun removeNotification(userID: String, notificationID: String) {
        firebaseDatabaseService.removeNotification(userID, notificationID)
    }

    fun removeFriend(userID: String, friendID: String) {
        firebaseDatabaseService.removeFriend(userID, friendID)
    }

    fun removeSentRequest(otherUserID: String, myUserID: String) {
        firebaseDatabaseService.removeSentRequest(otherUserID, myUserID)
    }

    fun removeChat(chatID: String) {
        firebaseDatabaseService.removeChat(chatID)
    }

    fun removeMessages(messagesID: String){
        firebaseDatabaseService.removeMessages(messagesID)
    }

    //endregion

    //region Load Single

    fun loadUser(userID: String, b: ((Result<User>) -> Unit)) {
        firebaseDatabaseService.loadUserTask(userID).addOnSuccessListener {
            b.invoke(Result.Success(wrapSnapshotToClass(User::class.java, it)))
        }.addOnFailureListener { b.invoke(Result.Error(it.message)) }
    }

    fun loadUserInfo(userID: String, b: ((Result<UserInfo>) -> Unit)) {
        firebaseDatabaseService.loadUserInfoTask(userID).addOnSuccessListener {
            b.invoke(Result.Success(wrapSnapshotToClass(UserInfo::class.java, it)))
        }.addOnFailureListener { b.invoke(Result.Error(it.message)) }
    }

    fun loadChat(chatID: String, b: ((Result<Chat>) -> Unit)) {
        firebaseDatabaseService.loadChatTask(chatID).addOnSuccessListener {
            b.invoke(Result.Success(wrapSnapshotToClass(Chat::class.java, it)))
        }.addOnFailureListener { b.invoke(Result.Error(it.message)) }
    }

    //endregion

    //region Load List

    fun loadUsers(b: ((Result<MutableList<User>>) -> Unit)) {
        b.invoke(Result.Loading)
        firebaseDatabaseService.loadUsersTask().addOnSuccessListener {
            val usersList = wrapSnapshotToArrayList(User::class.java, it)
            b.invoke(Result.Success(usersList))
        }.addOnFailureListener { b.invoke(Result.Error(it.message)) }
    }

    fun loadFriends(userID: String, b: ((Result<List<UserFriend>>) -> Unit)) {
        b.invoke(Result.Loading)
        firebaseDatabaseService.loadFriendsTask(userID).addOnSuccessListener {
            val friendsList = wrapSnapshotToArrayList(UserFriend::class.java, it)
            b.invoke(Result.Success(friendsList))
        }.addOnFailureListener { b.invoke(Result.Error(it.message)) }
    }

    fun loadNotifications(userID: String, b: ((Result<MutableList<UserNotification>>) -> Unit)) {
        b.invoke(Result.Loading)
        firebaseDatabaseService.loadNotificationsTask(userID).addOnSuccessListener {
            val notificationsList = wrapSnapshotToArrayList(UserNotification::class.java, it)
            b.invoke(Result.Success(notificationsList))
        }.addOnFailureListener { b.invoke(Result.Error(it.message)) }
    }

    //endregion

    //#region Load and Observe

    fun loadAndObserveUser(userID: String, observer: FirebaseReferenceValueObserver, b: ((Result<User>) -> Unit)) {
        firebaseDatabaseService.attachUserObserver(User::class.java, userID, observer, b)
    }

    fun loadAndObserveUserInfo(userID: String, observer: FirebaseReferenceValueObserver, b: ((Result<UserInfo>) -> Unit)) {
        firebaseDatabaseService.attachUserInfoObserver(UserInfo::class.java, userID, observer, b)
    }

    fun loadAndObserveUserNotifications(userID: String, observer: FirebaseReferenceValueObserver, b: ((Result<MutableList<UserNotification>>) -> Unit)){
        firebaseDatabaseService.attachUserNotificationsObserver(UserNotification::class.java, userID, observer, b)
    }

    fun loadAndObserveMessagesAdded(messagesID: String, observer: FirebaseReferenceChildObserver, b: ((Result<Message>) -> Unit)) {
        firebaseDatabaseService.attachMessagesObserver(Message::class.java, messagesID, observer, b)
    }

    fun loadAndObserveChat(chatID: String, observer: FirebaseReferenceValueObserver, b: ((Result<Chat>) -> Unit)) {
        firebaseDatabaseService.attachChatObserver(Chat::class.java, chatID, observer, b)
    }

    //endregion
}



================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/data/db/repository/StorageRepository.kt
================================================
package com.fredrikbogg.android_chat_app.data.db.repository

import android.net.Uri
import com.fredrikbogg.android_chat_app.data.db.remote.FirebaseStorageSource
import com.fredrikbogg.android_chat_app.data.Result

class StorageRepository {
    private val firebaseStorageService = FirebaseStorageSource()

    fun updateUserProfileImage(userID: String, byteArray: ByteArray, b: (Result<Uri>) -> Unit) {
        b.invoke(Result.Loading)
        firebaseStorageService.uploadUserImage(userID, byteArray).addOnSuccessListener {
            b.invoke((Result.Success(it)))
        }.addOnFailureListener {
            b.invoke(Result.Error(it.message))
        }
    }
}

================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/data/model/ChatWithUserInfo.kt
================================================
package com.fredrikbogg.android_chat_app.data.model

import com.fredrikbogg.android_chat_app.data.db.entity.Chat
import com.fredrikbogg.android_chat_app.data.db.entity.UserInfo

data class ChatWithUserInfo(
    var mChat: Chat,
    var mUserInfo: UserInfo
)


================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/data/model/CreateUser.kt
================================================
package com.fredrikbogg.android_chat_app.data.model

data class CreateUser(
    var displayName: String = "",
    var email: String = "",
    var password: String = ""
)

================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/data/model/Login.kt
================================================
package com.fredrikbogg.android_chat_app.data.model

data class Login(
    var email: String = "",
    var password: String = ""
)

================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/ui/DefaultBindings.kt
================================================
package com.fredrikbogg.android_chat_app.ui

import android.annotation.SuppressLint
import android.widget.ImageView
import android.widget.TextView
import androidx.databinding.BindingAdapter
import androidx.recyclerview.widget.RecyclerView
import com.fredrikbogg.android_chat_app.R
import com.squareup.picasso.Picasso
import jp.wasabeef.picasso.transformations.BlurTransformation
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.TimeUnit


@BindingAdapter("bind_image_url_blur")
fun bindBlurImageWithPicasso(imageView: ImageView, url: String?) {
    if (!url.isNullOrBlank()) {
        Picasso.get().load(url).error(R.drawable.ic_baseline_error_24)
            .transform(BlurTransformation(imageView.context, 15, 1)).into(imageView)
    }
}

@BindingAdapter("bind_image_url")
fun bindImageWithPicasso(imageView: ImageView, url: String?) {
    when (url) {
        null -> Unit
        "" -> imageView.setBackgroundResource(R.drawable.ic_baseline_person_24)
        else -> Picasso.get().load(url).error(R.drawable.ic_baseline_error_24).into(imageView)
    }
}

@SuppressLint("SimpleDateFormat")
@BindingAdapter("bind_epochTimeMsToDate_with_days_ago")
fun TextView.bindEpochTimeMsToDateWithDaysAgo(epochTimeMs: Long) {
    val numOfDays = TimeUnit.MILLISECONDS.toDays(Date().time - epochTimeMs)

    this.text = when {
        numOfDays == 1.toLong() -> "Yesterday"
        numOfDays > 1.toLong() -> "$numOfDays days ago"
        else -> {
            val pat =
                SimpleDateFormat().toLocalizedPattern().replace("\\W?[YyMd]+\\W?".toRegex(), " ")
            val formatter = SimpleDateFormat(pat, Locale.getDefault())
            formatter.format(Date(epochTimeMs))
        }
    }
}

@SuppressLint("SimpleDateFormat")
@BindingAdapter("bind_epochTimeMsToDate")
fun TextView.bindEpochTimeMsToDate(epochTimeMs: Long) {
    if (epochTimeMs > 0) {
        val currentTimeMs = Date().time
        val numOfDays = TimeUnit.MILLISECONDS.toDays(currentTimeMs - epochTimeMs)

        val replacePattern = when {
            numOfDays >= 1.toLong() -> "Yy"
            else -> "YyMd"
        }
        val pat = SimpleDateFormat().toLocalizedPattern().replace("\\W?[$replacePattern]+\\W?".toRegex(), " ")
        val formatter = SimpleDateFormat(pat, Locale.getDefault())
        this.text = formatter.format(Date(epochTimeMs))
    }
}

@BindingAdapter("bind_disable_item_animator")
fun bindDisableRecyclerViewItemAnimator(recyclerView: RecyclerView, disable: Boolean) {
    if (disable) {
        recyclerView.itemAnimator = null
    }
}


================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/ui/DefaultViewModel.kt
================================================
package com.fredrikbogg.android_chat_app.ui

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.fredrikbogg.android_chat_app.data.Event
import com.fredrikbogg.android_chat_app.data.Result

abstract class DefaultViewModel : ViewModel() {
    protected val mSnackBarText = MutableLiveData<Event<String>>()
    val snackBarText: LiveData<Event<String>> = mSnackBarText

    private val mDataLoading = MutableLiveData<Event<Boolean>>()
    val dataLoading: LiveData<Event<Boolean>> = mDataLoading

    protected fun <T> onResult(mutableLiveData: MutableLiveData<T>? = null, result: Result<T>) {
        when (result) {
            is Result.Loading -> mDataLoading.value = Event(true)

            is Result.Error -> {
                mDataLoading.value = Event(false)
                result.msg?.let { mSnackBarText.value = Event(it) }
            }

            is Result.Success -> {
                mDataLoading.value = Event(false)
                result.data?.let { mutableLiveData?.value = it }
                result.msg?.let { mSnackBarText.value = Event(it) }
            }
        }
    }
}

================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/ui/chat/ChatFragment.kt
================================================
package com.fredrikbogg.android_chat_app.ui.chat

import android.os.Bundle
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.RecyclerView
import com.fredrikbogg.android_chat_app.databinding.FragmentChatBinding
import com.fredrikbogg.android_chat_app.databinding.ToolbarAddonChatBinding
import kotlinx.android.synthetic.main.fragment_chat.*


class ChatFragment : Fragment() {

    companion object {
        const val ARGS_KEY_USER_ID = "bundle_user_id"
        const val ARGS_KEY_OTHER_USER_ID = "bundle_other_user_id"
        const val ARGS_KEY_CHAT_ID = "bundle_other_chat_id"
    }

    private val viewModel: ChatViewModel by viewModels {
        ChatViewModelFactory(
            requireArguments().getString(ARGS_KEY_USER_ID)!!,
            requireArguments().getString(ARGS_KEY_OTHER_USER_ID)!!,
            requireArguments().getString(ARGS_KEY_CHAT_ID)!!
        )
    }

    private lateinit var viewDataBinding: FragmentChatBinding
    private lateinit var listAdapter: MessagesListAdapter
    private lateinit var listAdapterObserver: RecyclerView.AdapterDataObserver
    private lateinit var toolbarAddonChatBinding: ToolbarAddonChatBinding

    override fun onDestroy() {
        super.onDestroy()
        removeCustomToolbar()
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        viewDataBinding =
            FragmentChatBinding.inflate(inflater, container, false).apply { viewmodel = viewModel }
        viewDataBinding.lifecycleOwner = this.viewLifecycleOwner
        setHasOptionsMenu(true)

        toolbarAddonChatBinding =
            ToolbarAddonChatBinding.inflate(inflater, container, false)
                .apply { viewmodel = viewModel }
        toolbarAddonChatBinding.lifecycleOwner = this.viewLifecycleOwner

        return viewDataBinding.root
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        setupCustomToolbar()
        setupListAdapter()
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {
            android.R.id.home -> {
                findNavController().popBackStack()
                return true
            }
        }
        return super.onOptionsItemSelected(item)
    }

    private fun removeCustomToolbar() {
        val supportActionBar = (activity as AppCompatActivity?)!!.supportActionBar
        supportActionBar!!.setDisplayShowCustomEnabled(false)
        supportActionBar.customView = null
    }

    private fun setupCustomToolbar() {
        val supportActionBar = (activity as AppCompatActivity?)!!.supportActionBar
        supportActionBar!!.setDisplayShowCustomEnabled(true)
        supportActionBar.customView = toolbarAddonChatBinding.root
    }

    private fun setupListAdapter() {
        val viewModel = viewDataBinding.viewmodel
        if (viewModel != null) {
            listAdapterObserver = (object : RecyclerView.AdapterDataObserver() {
                override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
                    messagesRecyclerView.scrollToPosition(positionStart)
                }
            })
            listAdapter =
                MessagesListAdapter(viewModel, requireArguments().getString(ARGS_KEY_USER_ID)!!)
            listAdapter.registerAdapterDataObserver(listAdapterObserver)
            viewDataBinding.messagesRecyclerView.adapter = listAdapter
        } else {
            throw Exception("The viewmodel is not initialized")
        }
    }

    override fun onDestroyView() {
        super.onDestroyView()
        listAdapter.unregisterAdapterDataObserver(listAdapterObserver)
    }
}

================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/ui/chat/ChatViewModel.kt
================================================
package com.fredrikbogg.android_chat_app.ui.chat

import androidx.lifecycle.*
import com.fredrikbogg.android_chat_app.data.db.entity.Chat
import com.fredrikbogg.android_chat_app.data.db.entity.Message
import com.fredrikbogg.android_chat_app.data.db.entity.UserInfo
import com.fredrikbogg.android_chat_app.data.db.remote.FirebaseReferenceChildObserver
import com.fredrikbogg.android_chat_app.data.db.remote.FirebaseReferenceValueObserver
import com.fredrikbogg.android_chat_app.data.db.repository.DatabaseRepository
import com.fredrikbogg.android_chat_app.ui.DefaultViewModel
import com.fredrikbogg.android_chat_app.data.Result
import com.fredrikbogg.android_chat_app.util.addNewItem

class ChatViewModelFactory(private val myUserID: String, private val otherUserID: String, private val chatID: String) :
    ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return ChatViewModel(myUserID, otherUserID, chatID) as T
    }
}

class ChatViewModel(private val myUserID: String, private val otherUserID: String, private val chatID: String) : DefaultViewModel() {

    private val dbRepository: DatabaseRepository = DatabaseRepository()

    private val _otherUser: MutableLiveData<UserInfo> = MutableLiveData()
    private val _addedMessage = MutableLiveData<Message>()

    private val fbRefMessagesChildObserver = FirebaseReferenceChildObserver()
    private val fbRefUserInfoObserver = FirebaseReferenceValueObserver()

    val messagesList = MediatorLiveData<MutableList<Message>>()
    val newMessageText = MutableLiveData<String>()
    val otherUser: LiveData<UserInfo> = _otherUser

    init {
        setupChat()
        checkAndUpdateLastMessageSeen()
    }

    override fun onCleared() {
        super.onCleared()
        fbRefMessagesChildObserver.clear()
        fbRefUserInfoObserver.clear()
    }

    private fun checkAndUpdateLastMessageSeen() {
        dbRepository.loadChat(chatID) { result: Result<Chat> ->
            if (result is Result.Success && result.data != null) {
                result.data.lastMessage.let {
                    if (!it.seen && it.senderID != myUserID) {
                        it.seen = true
                        dbRepository.updateChatLastMessage(chatID, it)
                    }
                }
            }
        }
    }

    private fun setupChat() {
        dbRepository.loadAndObserveUserInfo(otherUserID, fbRefUserInfoObserver) { result: Result<UserInfo> ->
            onResult(_otherUser, result)
            if (result is Result.Success && !fbRefMessagesChildObserver.isObserving()) {
                loadAndObserveNewMessages()
            }
        }
    }

    private fun loadAndObserveNewMessages() {
        messagesList.addSource(_addedMessage) { messagesList.addNewItem(it) }

        dbRepository.loadAndObserveMessagesAdded(
            chatID,
            fbRefMessagesChildObserver
        ) { result: Result<Message> ->
            onResult(_addedMessage, result)
        }
    }

    fun sendMessagePressed() {
        if (!newMessageText.value.isNullOrBlank()) {
            val newMsg = Message(myUserID, newMessageText.value!!)
            dbRepository.updateNewMessage(chatID, newMsg)
            dbRepository.updateChatLastMessage(chatID, newMsg)
            newMessageText.value = null
        }
    }
}

================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/ui/chat/MessagesBindings.kt
================================================
package com.fredrikbogg.android_chat_app.ui.chat

import android.view.View
import androidx.databinding.BindingAdapter
import androidx.recyclerview.widget.RecyclerView
import com.fredrikbogg.android_chat_app.data.db.entity.Message
import kotlin.math.abs

@BindingAdapter("bind_messages_list")
fun bindMessagesList(listView: RecyclerView, items: List<Message>?) {
    items?.let {
        (listView.adapter as MessagesListAdapter).submitList(items)
        listView.scrollToPosition(items.size - 1)
    }
}

@BindingAdapter("bind_message", "bind_message_viewModel")
fun View.bindShouldMessageShowTimeText(message: Message, viewModel: ChatViewModel) {
    val halfHourInMilli = 1800000
    val index = viewModel.messagesList.value!!.indexOf(message)

    if (index == 0) {
        this.visibility = View.VISIBLE
    } else {
        val messageBefore = viewModel.messagesList.value!![index - 1]

        if (abs(messageBefore.epochTimeMs - message.epochTimeMs) > halfHourInMilli) {
            this.visibility = View.VISIBLE
        } else {
            this.visibility = View.GONE
        }
    }
}



================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/ui/chat/MessagesListAdapter.kt
================================================
package com.fredrikbogg.android_chat_app.ui.chat

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.fredrikbogg.android_chat_app.data.db.entity.Message
import com.fredrikbogg.android_chat_app.databinding.ListItemMessageReceivedBinding
import com.fredrikbogg.android_chat_app.databinding.ListItemMessageSentBinding

class MessagesListAdapter internal constructor(private val viewModel: ChatViewModel, private val userId: String) : ListAdapter<Message, RecyclerView.ViewHolder>(MessageDiffCallback()) {

    private val holderTypeMessageReceived = 1
    private val holderTypeMessageSent = 2

    class ReceivedViewHolder(private val binding: ListItemMessageReceivedBinding) :
        RecyclerView.ViewHolder(binding.root) {
        fun bind(viewModel: ChatViewModel, item: Message) {
            binding.viewmodel = viewModel
            binding.message = item
            binding.executePendingBindings()
        }
    }

    class SentViewHolder(private val binding: ListItemMessageSentBinding) :
        RecyclerView.ViewHolder(binding.root) {
        fun bind(viewModel: ChatViewModel, item: Message) {
            binding.viewmodel = viewModel
            binding.message = item
            binding.executePendingBindings()
        }
    }

    override fun getItemViewType(position: Int): Int {
        return if (getItem(position).senderID != userId) {
            holderTypeMessageReceived
        } else {
            holderTypeMessageSent
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (holder.itemViewType) {
            holderTypeMessageSent -> (holder as SentViewHolder).bind(
                viewModel,
                getItem(position)
            )
            holderTypeMessageReceived -> (holder as ReceivedViewHolder).bind(
                viewModel,
                getItem(position)
            )
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val layoutInflater = LayoutInflater.from(parent.context)

        return when (viewType) {
            holderTypeMessageSent -> {
                val binding = ListItemMessageSentBinding.inflate(layoutInflater, parent, false)
                SentViewHolder(binding)
            }
            holderTypeMessageReceived -> {
                val binding = ListItemMessageReceivedBinding.inflate(layoutInflater, parent, false)
                ReceivedViewHolder(binding)
            }
            else -> {
                throw Exception("Error reading holder type")
            }
        }
    }
}

class MessageDiffCallback : DiffUtil.ItemCallback<Message>() {
    override fun areItemsTheSame(oldItem: Message, newItem: Message): Boolean {
        return oldItem == newItem
    }

    override fun areContentsTheSame(oldItem: Message, newItem: Message): Boolean {
        return oldItem.epochTimeMs == newItem.epochTimeMs
    }
}


================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/ui/chats/ChatsBindings.kt
================================================
@file:Suppress("unused")

package com.fredrikbogg.android_chat_app.ui.chats

import android.view.View
import android.widget.TextView
import androidx.databinding.BindingAdapter
import androidx.recyclerview.widget.RecyclerView
import com.fredrikbogg.android_chat_app.R
import com.fredrikbogg.android_chat_app.data.model.ChatWithUserInfo
import com.fredrikbogg.android_chat_app.data.db.entity.Message

@BindingAdapter("bind_chats_list")
fun bindChatsList(listView: RecyclerView, items: List<ChatWithUserInfo>?) {
    items?.let { (listView.adapter as ChatsListAdapter).submitList(items) }
}

@BindingAdapter("bind_chat_message_text", "bind_chat_message_text_viewModel")
fun TextView.bindMessageYouToText(message: Message, viewModel: ChatsViewModel) {
    this.text = if (message.senderID == viewModel.myUserID) {
        "You: " + message.text
    } else {
        message.text
    }
}

@BindingAdapter("bind_message_view", "bind_message_textView", "bind_message", "bind_myUserID")
fun View.bindMessageSeen(view: View, textView: TextView, message: Message, myUserID: String) {
    if (message.senderID != myUserID && !message.seen) {
        view.visibility = View.VISIBLE
        textView.setTextAppearance(R.style.MessageNotSeen)
//        textView.alpha = 1f
    } else {
        view.visibility = View.INVISIBLE
        textView.setTextAppearance(R.style.MessageSeen)
//        textView.alpha = 1f
    }
}



================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/ui/chats/ChatsFragment.kt
================================================
package com.fredrikbogg.android_chat_app.ui.chats

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import com.fredrikbogg.android_chat_app.App
import com.fredrikbogg.android_chat_app.R
import com.fredrikbogg.android_chat_app.data.model.ChatWithUserInfo
import com.fredrikbogg.android_chat_app.databinding.FragmentChatsBinding
import com.fredrikbogg.android_chat_app.data.EventObserver
import com.fredrikbogg.android_chat_app.ui.chat.ChatFragment
import com.fredrikbogg.android_chat_app.util.convertTwoUserIDs

class ChatsFragment : Fragment() {

    private val viewModel: ChatsViewModel by viewModels { ChatsViewModelFactory(App.myUserID) }
    private lateinit var viewDataBinding: FragmentChatsBinding
    private lateinit var listAdapter: ChatsListAdapter

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
    ): View? {
        viewDataBinding =
            FragmentChatsBinding.inflate(inflater, container, false).apply { viewmodel = viewModel }
        viewDataBinding.lifecycleOwner = this.viewLifecycleOwner
        return viewDataBinding.root
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        setupListAdapter()
        setupObservers()
    }

    private fun setupListAdapter() {
        val viewModel = viewDataBinding.viewmodel
        if (viewModel != null) {
            listAdapter = ChatsListAdapter(viewModel)
            viewDataBinding.chatsRecyclerView.adapter = listAdapter
        } else {
            throw Exception("The viewmodel is not initialized")
        }
    }

    private fun setupObservers() {
        viewModel.selectedChat.observe(viewLifecycleOwner,
            EventObserver { navigateToChat(it) })
    }

    private fun navigateToChat(chatWithUserInfo: ChatWithUserInfo) {
        val bundle = bundleOf(
            ChatFragment.ARGS_KEY_USER_ID to App.myUserID,
            ChatFragment.ARGS_KEY_OTHER_USER_ID to chatWithUserInfo.mUserInfo.id,
            ChatFragment.ARGS_KEY_CHAT_ID to convertTwoUserIDs(App.myUserID, chatWithUserInfo.mUserInfo.id)
        )
        findNavController().navigate(R.id.action_navigation_chats_to_chatFragment, bundle)
    }
}

================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/ui/chats/ChatsListAdapter.kt
================================================
package com.fredrikbogg.android_chat_app.ui.chats

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.fredrikbogg.android_chat_app.data.model.ChatWithUserInfo
import com.fredrikbogg.android_chat_app.databinding.ListItemChatBinding

class ChatsListAdapter internal constructor(private val viewModel: ChatsViewModel) :
    ListAdapter<(ChatWithUserInfo), ChatsListAdapter.ViewHolder>(ChatDiffCallback()) {

    class ViewHolder(private val binding: ListItemChatBinding) :
        RecyclerView.ViewHolder(binding.root) {
        fun bind(viewModel: ChatsViewModel, item: ChatWithUserInfo) {
            binding.viewmodel = viewModel
            binding.chatwithuserinfo = item
            binding.executePendingBindings()
        }
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(viewModel, getItem(position))
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val layoutInflater = LayoutInflater.from(parent.context)
        val binding = ListItemChatBinding.inflate(layoutInflater, parent, false)
        return ViewHolder(binding)
    }
}

class ChatDiffCallback : DiffUtil.ItemCallback<ChatWithUserInfo>() {
    override fun areItemsTheSame(oldItem: ChatWithUserInfo, itemWithUserInfo: ChatWithUserInfo): Boolean {
        return oldItem == itemWithUserInfo
    }

    override fun areContentsTheSame(oldItem: ChatWithUserInfo, itemWithUserInfo: ChatWithUserInfo): Boolean {
        return oldItem.mChat.info.id == itemWithUserInfo.mChat.info.id
    }
}

================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/ui/chats/ChatsViewModel.kt
================================================
package com.fredrikbogg.android_chat_app.ui.chats

import androidx.lifecycle.*
import com.fredrikbogg.android_chat_app.data.Event
import com.fredrikbogg.android_chat_app.data.Result
import com.fredrikbogg.android_chat_app.data.db.entity.Chat
import com.fredrikbogg.android_chat_app.data.model.ChatWithUserInfo
import com.fredrikbogg.android_chat_app.data.db.entity.UserFriend
import com.fredrikbogg.android_chat_app.data.db.entity.UserInfo
import com.fredrikbogg.android_chat_app.data.db.remote.FirebaseReferenceValueObserver
import com.fredrikbogg.android_chat_app.data.db.repository.DatabaseRepository
import com.fredrikbogg.android_chat_app.ui.DefaultViewModel
import com.fredrikbogg.android_chat_app.util.addNewItem
import com.fredrikbogg.android_chat_app.util.convertTwoUserIDs
import com.fredrikbogg.android_chat_app.util.updateItemAt


class ChatsViewModelFactory(private val myUserID: String) :
    ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return ChatsViewModel(myUserID) as T
    }
}

class ChatsViewModel(val myUserID: String) : DefaultViewModel() {

    private val repository: DatabaseRepository = DatabaseRepository()
    private val firebaseReferenceObserverList = ArrayList<FirebaseReferenceValueObserver>()
    private val _updatedChatWithUserInfo = MutableLiveData<ChatWithUserInfo>()
    private val _selectedChat = MutableLiveData<Event<ChatWithUserInfo>>()

    var selectedChat: LiveData<Event<ChatWithUserInfo>> = _selectedChat
    val chatsList = MediatorLiveData<MutableList<ChatWithUserInfo>>()

    init {
        chatsList.addSource(_updatedChatWithUserInfo) { newChat ->
            val chat = chatsList.value?.find { it.mChat.info.id == newChat.mChat.info.id }
            if (chat == null) {
                chatsList.addNewItem(newChat)
            } else {
                chatsList.updateItemAt(newChat, chatsList.value!!.indexOf(chat))
            }
        }
        setupChats()
    }

    override fun onCleared() {
        super.onCleared()
        firebaseReferenceObserverList.forEach { it.clear() }
    }

    private fun setupChats() {
        loadFriends()
    }

    private fun loadFriends() {
        repository.loadFriends(myUserID) { result: Result<List<UserFriend>> ->
            onResult(null, result)
            if (result is Result.Success) result.data?.forEach { loadUserInfo(it) }
        }
    }

    private fun loadUserInfo(userFriend: UserFriend) {
        repository.loadUserInfo(userFriend.userID) { result: Result<UserInfo> ->
            onResult(null, result)
            if (result is Result.Success) result.data?.let { loadAndObserveChat(it) }
        }
    }

    private fun loadAndObserveChat(userInfo: UserInfo) {
        val observer = FirebaseReferenceValueObserver()
        firebaseReferenceObserverList.add(observer)
        repository.loadAndObserveChat(convertTwoUserIDs(myUserID, userInfo.id), observer) { result: Result<Chat> ->
            if (result is Result.Success) {
                _updatedChatWithUserInfo.value = result.data?.let { ChatWithUserInfo(it, userInfo) }
            } else if (result is Result.Error) {
                chatsList.value?.let {
                    val newList = mutableListOf<ChatWithUserInfo>().apply { addAll(it) }
                    newList.removeIf { it2 -> result.msg.toString().contains(it2.mUserInfo.id) }
                    chatsList.value = newList
                }
            }
        }
    }

    fun selectChatWithUserInfoPressed(chat: ChatWithUserInfo) {
        _selectedChat.value = Event(chat)
    }
}

================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/ui/main/MainActivity.kt
================================================
package com.fredrikbogg.android_chat_app.ui.main

import android.os.Bundle
import android.view.View
import android.widget.ProgressBar
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController
import com.fredrikbogg.android_chat_app.R
import com.fredrikbogg.android_chat_app.data.db.remote.FirebaseDataSource
import com.fredrikbogg.android_chat_app.util.forceHideKeyboard
import com.google.android.material.badge.BadgeDrawable
import com.google.android.material.bottomnavigation.BottomNavigationView


class MainActivity : AppCompatActivity() {

    private lateinit var navView: BottomNavigationView
    private lateinit var mainProgressBar: ProgressBar
    private lateinit var mainToolbar: Toolbar
    private lateinit var notificationsBadge: BadgeDrawable
    private val viewModel: MainViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        mainToolbar = findViewById(R.id.main_toolbar)
        navView = findViewById(R.id.nav_view)
        mainProgressBar = findViewById(R.id.main_progressBar)

        notificationsBadge =
            navView.getOrCreateBadge(R.id.navigation_notifications).apply { isVisible = false }

        setSupportActionBar(mainToolbar)

        val navController = findNavController(R.id.nav_host_fragment)
        navController.addOnDestinationChangedListener { _, destination, _ ->

            when (destination.id) {
                R.id.profileFragment -> navView.visibility = View.GONE
                R.id.chatFragment -> navView.visibility = View.GONE
                R.id.startFragment -> navView.visibility = View.GONE
                R.id.loginFragment -> navView.visibility = View.GONE
                R.id.createAccountFragment -> navView.visibility = View.GONE
                else -> navView.visibility = View.VISIBLE
            }
            showGlobalProgressBar(false)
            currentFocus?.rootView?.forceHideKeyboard()
        }

        val appBarConfiguration = AppBarConfiguration(
            setOf(
                R.id.navigation_chats,
                R.id.navigation_notifications,
                R.id.navigation_users,
                R.id.navigation_settings,
                R.id.startFragment
            )
        )

        setupActionBarWithNavController(navController, appBarConfiguration)
        navView.setupWithNavController(navController)
    }

    override fun onPause() {
        super.onPause()
        FirebaseDataSource.dbInstance.goOffline()
    }

    override fun onResume() {
        FirebaseDataSource.dbInstance.goOnline()
        setupViewModelObservers()
        super.onResume()
    }

    private fun setupViewModelObservers() {
        viewModel.userNotificationsList.observe(this, {
            if (it.size > 0) {
                notificationsBadge.number = it.size
                notificationsBadge.isVisible = true
            } else {
                notificationsBadge.isVisible = false
            }
        })
    }

    fun showGlobalProgressBar(show: Boolean) {
        if (show) mainProgressBar.visibility = View.VISIBLE
        else mainProgressBar.visibility = View.GONE
    }
}

================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/ui/main/MainViewModel.kt
================================================
package com.fredrikbogg.android_chat_app.ui.main

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.fredrikbogg.android_chat_app.App
import com.fredrikbogg.android_chat_app.data.db.entity.UserNotification
import com.fredrikbogg.android_chat_app.data.db.remote.FirebaseAuthStateObserver
import com.fredrikbogg.android_chat_app.data.db.remote.FirebaseReferenceConnectedObserver
import com.fredrikbogg.android_chat_app.data.db.remote.FirebaseReferenceValueObserver
import com.fredrikbogg.android_chat_app.data.db.repository.AuthRepository
import com.fredrikbogg.android_chat_app.data.db.repository.DatabaseRepository
import com.fredrikbogg.android_chat_app.data.Result
import com.google.firebase.auth.FirebaseUser


class MainViewModel : ViewModel() {

    private val dbRepository = DatabaseRepository()
    private val authRepository = AuthRepository()

    private val _userNotificationsList = MutableLiveData<MutableList<UserNotification>>()

    private val fbRefNotificationsObserver = FirebaseReferenceValueObserver()
    private val fbAuthStateObserver = FirebaseAuthStateObserver()
    private val fbRefConnectedObserver = FirebaseReferenceConnectedObserver()
    private var userID = App.myUserID

    var userNotificationsList: LiveData<MutableList<UserNotification>> = _userNotificationsList

    init {
       setupAuthObserver()
    }

    override fun onCleared() {
        super.onCleared()
        fbRefNotificationsObserver.clear()
        fbRefConnectedObserver.clear()
        fbAuthStateObserver.clear()
    }

    private fun setupAuthObserver(){
        authRepository.observeAuthState(fbAuthStateObserver) { result: Result<FirebaseUser> ->
            if (result is Result.Success) {
                userID = result.data!!.uid
                startObservingNotifications()
                fbRefConnectedObserver.start(userID)
            } else {
                fbRefConnectedObserver.clear()
                stopObservingNotifications()
            }
        }
    }

    private fun startObservingNotifications() {
        dbRepository.loadAndObserveUserNotifications(userID, fbRefNotificationsObserver) { result: Result<MutableList<UserNotification>> ->
            if (result is Result.Success) {
                _userNotificationsList.value = result.data
            }
        }
    }

    private fun stopObservingNotifications() {
        fbRefNotificationsObserver.clear()
    }
}


================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/ui/notifications/NotificationsBindings.kt
================================================
package com.fredrikbogg.android_chat_app.ui.notifications

import androidx.databinding.BindingAdapter
import androidx.recyclerview.widget.RecyclerView
import com.fredrikbogg.android_chat_app.data.db.entity.UserInfo

@BindingAdapter("bind_notifications_list")
fun bindNotificationsList(listView: RecyclerView, items: List<UserInfo>?) {
    items?.let { (listView.adapter as NotificationsListAdapter).submitList(items) }
}


================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/ui/notifications/NotificationsFragment.kt
================================================
package com.fredrikbogg.android_chat_app.ui.notifications

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import com.fredrikbogg.android_chat_app.App
import com.fredrikbogg.android_chat_app.databinding.FragmentNotificationsBinding

class NotificationsFragment : Fragment() {

    private val viewModel: NotificationsViewModel by viewModels { NotificationsViewModelFactory(App.myUserID) }
    private lateinit var viewDataBinding: FragmentNotificationsBinding
    private lateinit var listAdapter: NotificationsListAdapter

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        viewDataBinding = FragmentNotificationsBinding.inflate(inflater, container, false)
            .apply { viewmodel = viewModel }
        viewDataBinding.lifecycleOwner = this.viewLifecycleOwner
        return viewDataBinding.root
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        setupListAdapter()
    }

    private fun setupListAdapter() {
        val viewModel = viewDataBinding.viewmodel
        if (viewModel != null) {
            listAdapter = NotificationsListAdapter(viewModel)
            viewDataBinding.usersRecyclerView.adapter = listAdapter
        } else {
            throw Exception("The viewmodel is not initialized")
        }
    }
}

================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/ui/notifications/NotificationsListAdapter.kt
================================================
package com.fredrikbogg.android_chat_app.ui.notifications

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.fredrikbogg.android_chat_app.data.db.entity.UserInfo
import com.fredrikbogg.android_chat_app.databinding.ListItemNotificationBinding


class NotificationsListAdapter internal constructor(private val viewModel: NotificationsViewModel) :
    ListAdapter<UserInfo, NotificationsListAdapter.ViewHolder>(UserInfoDiffCallback()) {

    class ViewHolder(private val binding: ListItemNotificationBinding) :
        RecyclerView.ViewHolder(binding.root) {
        fun bind(viewModel: NotificationsViewModel, item: UserInfo) {
            binding.viewmodel = viewModel
            binding.userinfo = item
            binding.executePendingBindings()
        }
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(viewModel, getItem(position))
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val layoutInflater = LayoutInflater.from(parent.context)
        val binding = ListItemNotificationBinding.inflate(layoutInflater, parent, false)
        return ViewHolder(binding)
    }
}

class UserInfoDiffCallback : DiffUtil.ItemCallback<UserInfo>() {
    override fun areItemsTheSame(oldItem: UserInfo, newItem: UserInfo): Boolean {
        return oldItem == newItem
    }

    override fun areContentsTheSame(oldItem: UserInfo, newItem: UserInfo): Boolean {
        return oldItem.id == newItem.id
    }
}

================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/ui/notifications/NotificationsViewModel.kt
================================================
package com.fredrikbogg.android_chat_app.ui.notifications

import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.fredrikbogg.android_chat_app.data.db.entity.*
import com.fredrikbogg.android_chat_app.data.db.repository.DatabaseRepository
import com.fredrikbogg.android_chat_app.ui.DefaultViewModel
import com.fredrikbogg.android_chat_app.data.Result
import com.fredrikbogg.android_chat_app.util.addNewItem
import com.fredrikbogg.android_chat_app.util.removeItem
import com.fredrikbogg.android_chat_app.util.convertTwoUserIDs

class NotificationsViewModelFactory(private val myUserID: String) :
    ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return NotificationsViewModel(myUserID) as T
    }
}

class NotificationsViewModel(private val myUserID: String) : DefaultViewModel() {

    private val dbRepository: DatabaseRepository = DatabaseRepository()
    private val updatedUserInfo = MutableLiveData<UserInfo>()
    private val userNotificationsList = MutableLiveData<MutableList<UserNotification>>()

    val usersInfoList = MediatorLiveData<MutableList<UserInfo>>()

    init {
        usersInfoList.addSource(updatedUserInfo) { usersInfoList.addNewItem(it) }
        loadNotifications()
    }

    private fun loadNotifications() {
        dbRepository.loadNotifications(myUserID) { result: Result<MutableList<UserNotification>> ->
            onResult(userNotificationsList, result)
            if (result is Result.Success) result.data?.forEach { loadUserInfo(it) }
        }
    }

    private fun loadUserInfo(userNotification: UserNotification) {
        dbRepository.loadUserInfo(userNotification.userID) { result: Result<UserInfo> ->
            onResult(updatedUserInfo, result)
        }
    }

    private fun updateNotification(otherUserInfo: UserInfo, removeOnly: Boolean) {
        val userNotification = userNotificationsList.value?.find {
            it.userID == otherUserInfo.id
        }

        if (userNotification != null) {
            if (!removeOnly) {
                dbRepository.updateNewFriend(UserFriend(myUserID), UserFriend(otherUserInfo.id))
                val newChat = Chat().apply {
                    info.id = convertTwoUserIDs(myUserID, otherUserInfo.id)
                    lastMessage = Message(seen = true, text = "Say hello!")
                }
                dbRepository.updateNewChat(newChat)
            }
            dbRepository.removeNotification(myUserID, otherUserInfo.id)
            dbRepository.removeSentRequest(otherUserInfo.id, myUserID)

            usersInfoList.removeItem(otherUserInfo)
            userNotificationsList.removeItem(userNotification)
        }
    }

    fun acceptNotificationPressed(userInfo: UserInfo) {
        updateNotification(userInfo, false)
    }

    fun declineNotificationPressed(userInfo: UserInfo) {
        updateNotification(userInfo, true)
    }
}

================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/ui/profile/ProfileFragment.kt
================================================
package com.fredrikbogg.android_chat_app.ui.profile

import android.os.Bundle
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import com.fredrikbogg.android_chat_app.App
import com.fredrikbogg.android_chat_app.databinding.FragmentProfileBinding
import com.fredrikbogg.android_chat_app.data.EventObserver
import com.fredrikbogg.android_chat_app.util.showSnackBar
import com.fredrikbogg.android_chat_app.ui.main.MainActivity
import com.fredrikbogg.android_chat_app.util.forceHideKeyboard


class ProfileFragment : Fragment() {

    companion object {
        const val ARGS_KEY_USER_ID = "bundle_user_id"
    }

    private val viewModel: ProfileViewModel by viewModels {
        ProfileViewModelFactory(App.myUserID, requireArguments().getString(ARGS_KEY_USER_ID)!!)
    }

    private lateinit var viewDataBinding: FragmentProfileBinding

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
    ): View? {
        viewDataBinding = FragmentProfileBinding.inflate(inflater, container, false)
            .apply { viewmodel = viewModel }
        viewDataBinding.lifecycleOwner = this.viewLifecycleOwner
        setHasOptionsMenu(true)
        return viewDataBinding.root
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        setupObservers()
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {
            android.R.id.home -> {
                findNavController().popBackStack()
                return true
            }
        }
        return super.onOptionsItemSelected(item)
    }

    private fun setupObservers() {
        viewModel.dataLoading.observe(viewLifecycleOwner,
            EventObserver { (activity as MainActivity).showGlobalProgressBar(it) })

        viewModel.snackBarText.observe(viewLifecycleOwner,
            EventObserver { text ->
                view?.showSnackBar(text)
                view?.forceHideKeyboard()
            })
    }
}

================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/ui/profile/ProfileViewModel.kt
================================================
package com.fredrikbogg.android_chat_app.ui.profile

import androidx.lifecycle.*
import com.fredrikbogg.android_chat_app.data.db.entity.*
import com.fredrikbogg.android_chat_app.data.db.remote.FirebaseReferenceValueObserver
import com.fredrikbogg.android_chat_app.data.db.repository.DatabaseRepository
import com.fredrikbogg.android_chat_app.ui.DefaultViewModel
import com.fredrikbogg.android_chat_app.data.Result
import com.fredrikbogg.android_chat_app.util.convertTwoUserIDs


class ProfileViewModelFactory(private val myUserID: String, private val otherUserID: String) :
    ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return ProfileViewModel(myUserID, otherUserID) as T
    }
}

enum class LayoutState {
    IS_FRIEND, NOT_FRIEND, ACCEPT_DECLINE, REQUEST_SENT
}

class ProfileViewModel(private val myUserID: String, private val userID: String) :
    DefaultViewModel() {

    private val repository: DatabaseRepository = DatabaseRepository()
    private val firebaseReferenceObserver = FirebaseReferenceValueObserver()
    private val _myUser: MutableLiveData<User> = MutableLiveData()
    private val _otherUser: MutableLiveData<User> = MutableLiveData()

    val otherUser: LiveData<User> = _otherUser
    val layoutState = MediatorLiveData<LayoutState>()

    init {
        layoutState.addSource(_myUser) { updateLayoutState(it, _otherUser.value) }
        setupProfile()
    }

    override fun onCleared() {
        super.onCleared()
        firebaseReferenceObserver.clear()
    }

    private fun updateLayoutState(myUser: User?, otherUser: User?) {
        if (myUser != null && otherUser != null) {
            layoutState.value = when {
                myUser.friends[otherUser.info.id] != null -> LayoutState.IS_FRIEND
                myUser.notifications[otherUser.info.id] != null -> LayoutState.ACCEPT_DECLINE
                myUser.sentRequests[otherUser.info.id] != null -> LayoutState.REQUEST_SENT
                else -> LayoutState.NOT_FRIEND
            }
        }
    }

    private fun setupProfile() {
        repository.loadUser(userID) { result: Result<User> ->
            onResult(_otherUser, result)
            if (result is Result.Success) {
                repository.loadAndObserveUser(myUserID, firebaseReferenceObserver) { result2: Result<User> ->
                    onResult(_myUser, result2)
                }
            }
        }
    }

    fun addFriendPressed() {
        repository.updateNewSentRequest(myUserID, UserRequest(_otherUser.value!!.info.id))
        repository.updateNewNotification(_otherUser.value!!.info.id, UserNotification(myUserID))
    }

    fun removeFriendPressed() {
        repository.removeFriend(myUserID, _otherUser.value!!.info.id)
        repository.removeChat(convertTwoUserIDs(myUserID, _otherUser.value!!.info.id))
        repository.removeMessages(convertTwoUserIDs(myUserID, _otherUser.value!!.info.id))
    }

    fun acceptFriendRequestPressed() {
        repository.updateNewFriend(UserFriend(myUserID), UserFriend(_otherUser.value!!.info.id))

        val newChat = Chat().apply {
            info.id = convertTwoUserIDs(myUserID, _otherUser.value!!.info.id)
            lastMessage = Message(seen = true, text = "Say hello!")
        }

        repository.updateNewChat(newChat)
        repository.removeNotification(myUserID, _otherUser.value!!.info.id)
        repository.removeSentRequest(_otherUser.value!!.info.id, myUserID)
    }

    fun declineFriendRequestPressed() {
        repository.removeSentRequest(myUserID, _otherUser.value!!.info.id)
        repository.removeNotification(myUserID, _otherUser.value!!.info.id)
    }
}


================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/ui/settings/SettingsFragment.kt
================================================
package com.fredrikbogg.android_chat_app.ui.settings

import android.app.Activity.RESULT_OK
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import com.fredrikbogg.android_chat_app.App
import com.fredrikbogg.android_chat_app.R
import com.fredrikbogg.android_chat_app.databinding.FragmentSettingsBinding
import com.fredrikbogg.android_chat_app.data.EventObserver
import com.fredrikbogg.android_chat_app.util.SharedPreferencesUtil
import com.fredrikbogg.android_chat_app.util.convertFileToByteArray


class SettingsFragment : Fragment() {

    private val viewModel: SettingsViewModel by viewModels { SettingsViewModelFactory(App.myUserID) }

    private lateinit var viewDataBinding: FragmentSettingsBinding
    private val selectImageIntentRequestCode = 1

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        viewDataBinding = FragmentSettingsBinding.inflate(inflater, container, false)
            .apply { viewmodel = viewModel }
        viewDataBinding.lifecycleOwner = this.viewLifecycleOwner
        setHasOptionsMenu(true)

        return viewDataBinding.root
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        setupObservers()
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {
            android.R.id.home -> {
                findNavController().popBackStack()
                return true
            }
        }
        return super.onOptionsItemSelected(item)
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (resultCode == RESULT_OK && requestCode == selectImageIntentRequestCode) {
            data?.data?.let { uri ->
                convertFileToByteArray(requireContext(), uri).let {
                    viewModel.changeUserImage(it)
                }
            }
        }
    }

    private fun setupObservers() {
        viewModel.editStatusEvent.observe(viewLifecycleOwner,
            EventObserver { showEditStatusDialog() })

        viewModel.editImageEvent.observe(viewLifecycleOwner,
            EventObserver { startSelectImageIntent() })

        viewModel.logoutEvent.observe(viewLifecycleOwner,
            EventObserver {
                SharedPreferencesUtil.removeUserID(requireContext())
                navigateToStart()
            })
    }

    private fun showEditStatusDialog() {
        val input = EditText(requireActivity() as Context)
        AlertDialog.Builder(requireActivity()).apply {
            setTitle("Status:")
            setView(input)
            setPositiveButton("Ok") { _, _ ->
                val textInput = input.text.toString()
                if (!textInput.isBlank() && textInput.length <= 40) {
                    viewModel.changeUserStatus(textInput)
                }
            }
            setNegativeButton("Cancel") { _, _ -> }
            show()
        }
    }

    private fun startSelectImageIntent() {
        val selectImageIntent = Intent(Intent.ACTION_GET_CONTENT)
        selectImageIntent.type = "image/*"
        startActivityForResult(selectImageIntent, selectImageIntentRequestCode)
    }

    private fun navigateToStart() {
        findNavController().navigate(R.id.action_navigation_settings_to_startFragment)
    }
}

================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/ui/settings/SettingsViewModel.kt
================================================
package com.fredrikbogg.android_chat_app.ui.settings

import android.net.Uri
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.fredrikbogg.android_chat_app.data.db.entity.UserInfo
import com.fredrikbogg.android_chat_app.data.db.remote.FirebaseReferenceValueObserver
import com.fredrikbogg.android_chat_app.data.db.repository.AuthRepository
import com.fredrikbogg.android_chat_app.data.db.repository.DatabaseRepository
import com.fredrikbogg.android_chat_app.data.db.repository.StorageRepository
import com.fredrikbogg.android_chat_app.ui.DefaultViewModel
import com.fredrikbogg.android_chat_app.data.Event
import com.fredrikbogg.android_chat_app.data.Result

class SettingsViewModelFactory(private val userID: String) : ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return SettingsViewModel(userID) as T
    }
}

class SettingsViewModel(private val userID: String) : DefaultViewModel() {

    private val dbRepository: DatabaseRepository = DatabaseRepository()
    private val storageRepository = StorageRepository()
    private val authRepository = AuthRepository()

    private val _userInfo: MutableLiveData<UserInfo> = MutableLiveData()
    val userInfo: LiveData<UserInfo> = _userInfo

    private val _editStatusEvent = MutableLiveData<Event<Unit>>()
    val editStatusEvent: LiveData<Event<Unit>> = _editStatusEvent

    private val _editImageEvent = MutableLiveData<Event<Unit>>()
    val editImageEvent: LiveData<Event<Unit>> = _editImageEvent

    private val _logoutEvent = MutableLiveData<Event<Unit>>()
    val logoutEvent: LiveData<Event<Unit>> = _logoutEvent

    private val firebaseReferenceObserver = FirebaseReferenceValueObserver()

    init {
        loadAndObserveUserInfo()
    }

    override fun onCleared() {
        super.onCleared()
        firebaseReferenceObserver.clear()
    }

    private fun loadAndObserveUserInfo() {
        dbRepository.loadAndObserveUserInfo(userID, firebaseReferenceObserver)
        { result: Result<UserInfo> -> onResult(_userInfo, result) }
    }

    fun changeUserStatus(status: String) {
        dbRepository.updateUserStatus(userID, status)
    }

    fun changeUserImage(byteArray: ByteArray) {
        storageRepository.updateUserProfileImage(userID, byteArray) { result: Result<Uri> ->
            onResult(null, result)
            if (result is Result.Success) {
                dbRepository.updateUserProfileImageUrl(userID, result.data.toString())
            }
        }
    }

    fun changeUserImagePressed() {
        _editImageEvent.value = Event(Unit)
    }

    fun changeUserStatusPressed() {
        _editStatusEvent.value = Event(Unit)
    }

    fun logoutUserPressed() {
        authRepository.logoutUser()
        _logoutEvent.value = Event(Unit)
    }
}



================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/ui/start/StartFragment.kt
================================================
package com.fredrikbogg.android_chat_app.ui.start

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import com.fredrikbogg.android_chat_app.R
import com.fredrikbogg.android_chat_app.databinding.FragmentStartBinding
import com.fredrikbogg.android_chat_app.data.EventObserver
import com.fredrikbogg.android_chat_app.util.SharedPreferencesUtil

class StartFragment : Fragment() {

    private val viewModel by viewModels<StartViewModel>()
    private lateinit var viewDataBinding: FragmentStartBinding

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
    ): View? {
        viewDataBinding =
            FragmentStartBinding.inflate(inflater, container, false).apply { viewmodel = viewModel }
        viewDataBinding.lifecycleOwner = this.viewLifecycleOwner
        setHasOptionsMenu(false)
        return viewDataBinding.root
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        setupObservers()

        if (userIsAlreadyLoggedIn()) {
            navigateDirectlyToChats()
        }
    }

    private fun userIsAlreadyLoggedIn(): Boolean {
        return SharedPreferencesUtil.getUserID(requireContext()) != null
    }

    private fun setupObservers() {
        viewModel.loginEvent.observe(viewLifecycleOwner, EventObserver { navigateToLogin() })
        viewModel.createAccountEvent.observe(
            viewLifecycleOwner, EventObserver { navigateToCreateAccount() })
    }

    private fun navigateDirectlyToChats() {
        findNavController().navigate(R.id.action_startFragment_to_navigation_chats)
    }

    private fun navigateToLogin() {
        findNavController().navigate(R.id.action_startFragment_to_loginFragment)
    }

    private fun navigateToCreateAccount() {
        findNavController().navigate(R.id.action_startFragment_to_createAccountFragment)
    }
}

================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/ui/start/StartViewModel.kt
================================================
package com.fredrikbogg.android_chat_app.ui.start

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.fredrikbogg.android_chat_app.data.Event

class StartViewModel : ViewModel() {

    private val _loginEvent = MutableLiveData<Event<Unit>>()
    private val _createAccountEvent = MutableLiveData<Event<Unit>>()

    val loginEvent: LiveData<Event<Unit>> = _loginEvent
    val createAccountEvent: LiveData<Event<Unit>> = _createAccountEvent

    fun goToLoginPressed() {
        _loginEvent.value = Event(Unit)
    }

    fun goToCreateAccountPressed() {
        _createAccountEvent.value = Event(Unit)
    }
}




================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/ui/start/createAccount/CreateAccountFragment.kt
================================================
package com.fredrikbogg.android_chat_app.ui.start.createAccount

import android.os.Bundle
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import com.fredrikbogg.android_chat_app.data.EventObserver
import com.fredrikbogg.android_chat_app.R
import com.fredrikbogg.android_chat_app.databinding.FragmentCreateAccountBinding
import com.fredrikbogg.android_chat_app.ui.main.MainActivity
import com.fredrikbogg.android_chat_app.util.SharedPreferencesUtil
import com.fredrikbogg.android_chat_app.util.forceHideKeyboard
import com.fredrikbogg.android_chat_app.util.showSnackBar

class CreateAccountFragment : Fragment() {

    private val viewModel by viewModels<CreateAccountViewModel>()
    private lateinit var viewDataBinding: FragmentCreateAccountBinding

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
    ): View? {
        viewDataBinding = FragmentCreateAccountBinding.inflate(inflater, container, false)
            .apply { viewmodel = viewModel }
        viewDataBinding.lifecycleOwner = this.viewLifecycleOwner
        setHasOptionsMenu(true)
        return viewDataBinding.root
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        setupObservers()
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {
            android.R.id.home -> {
                findNavController().popBackStack()
                return true
            }
        }
        return super.onOptionsItemSelected(item)
    }

    private fun setupObservers() {
        viewModel.dataLoading.observe(viewLifecycleOwner,
            EventObserver { (activity as MainActivity).showGlobalProgressBar(it) })

        viewModel.snackBarText.observe(viewLifecycleOwner,
            EventObserver { text ->
                view?.showSnackBar(text)
                view?.forceHideKeyboard()
            })

        viewModel.isCreatedEvent.observe(viewLifecycleOwner, EventObserver {
            SharedPreferencesUtil.saveUserID(requireContext(), it.uid)
            navigateToChats()
        })
    }

    private fun navigateToChats() {
        findNavController().navigate(R.id.action_createAccountFragment_to_navigation_chats)
    }
}

================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/ui/start/createAccount/CreateAccountViewModel.kt
================================================
package com.fredrikbogg.android_chat_app.ui.start.createAccount

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.fredrikbogg.android_chat_app.data.Event
import com.fredrikbogg.android_chat_app.data.Result
import com.fredrikbogg.android_chat_app.data.db.entity.User
import com.fredrikbogg.android_chat_app.data.db.repository.AuthRepository
import com.fredrikbogg.android_chat_app.data.db.repository.DatabaseRepository
import com.fredrikbogg.android_chat_app.data.model.CreateUser
import com.fredrikbogg.android_chat_app.ui.DefaultViewModel
import com.fredrikbogg.android_chat_app.util.isEmailValid
import com.fredrikbogg.android_chat_app.util.isTextValid
import com.google.firebase.auth.FirebaseUser

class CreateAccountViewModel : DefaultViewModel() {

    private val dbRepository = DatabaseRepository()
    private val authRepository = AuthRepository()
    private val mIsCreatedEvent = MutableLiveData<Event<FirebaseUser>>()

    val isCreatedEvent: LiveData<Event<FirebaseUser>> = mIsCreatedEvent
    val displayNameText = MutableLiveData<String>() // Two way
    val emailText = MutableLiveData<String>() // Two way
    val passwordText = MutableLiveData<String>() // Two way
    val isCreatingAccount = MutableLiveData<Boolean>()

    private fun createAccount() {
        isCreatingAccount.value = true
        val createUser =
            CreateUser(displayNameText.value!!, emailText.value!!, passwordText.value!!)

        authRepository.createUser(createUser) { result: Result<FirebaseUser> ->
            onResult(null, result)
            if (result is Result.Success) {
                mIsCreatedEvent.value = Event(result.data!!)
                dbRepository.updateNewUser(User().apply {
                    info.id = result.data.uid
                    info.displayName = createUser.displayName
                })
            }
            if (result is Result.Success || result is Result.Error) isCreatingAccount.value = false
        }
    }

    fun createAccountPressed() {
        if (!isTextValid(2, displayNameText.value)) {
            mSnackBarText.value = Event("Display name is too short")
            return
        }

        if (!isEmailValid(emailText.value.toString())) {
            mSnackBarText.value = Event("Invalid email format")
            return
        }
        if (!isTextValid(6, passwordText.value)) {
            mSnackBarText.value = Event("Password is too short")
            return
        }

        createAccount()
    }
}

================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/ui/start/login/LoginFragment.kt
================================================
package com.fredrikbogg.android_chat_app.ui.start.login

import android.os.Bundle
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import com.fredrikbogg.android_chat_app.R
import com.fredrikbogg.android_chat_app.databinding.FragmentLoginBinding
import com.fredrikbogg.android_chat_app.data.EventObserver
import com.fredrikbogg.android_chat_app.util.showSnackBar
import com.fredrikbogg.android_chat_app.ui.main.MainActivity
import com.fredrikbogg.android_chat_app.util.SharedPreferencesUtil
import com.fredrikbogg.android_chat_app.util.forceHideKeyboard

class LoginFragment : Fragment() {

    private val viewModel by viewModels<LoginViewModel>()
    private lateinit var viewDataBinding: FragmentLoginBinding

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
    ): View? {
        viewDataBinding = FragmentLoginBinding.inflate(inflater, container, false)
            .apply { viewmodel = viewModel }
        viewDataBinding.lifecycleOwner = this.viewLifecycleOwner
        setHasOptionsMenu(true)
        return viewDataBinding.root
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        setupObservers()
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {
            android.R.id.home -> {
                findNavController().popBackStack()
                return true
            }
        }
        return super.onOptionsItemSelected(item)
    }

    private fun setupObservers() {
        viewModel.dataLoading.observe(viewLifecycleOwner,
            EventObserver { (activity as MainActivity).showGlobalProgressBar(it) })

        viewModel.snackBarText.observe(viewLifecycleOwner,
            EventObserver { text ->
                view?.showSnackBar(text)
                view?.forceHideKeyboard()
            })

        viewModel.isLoggedInEvent.observe(viewLifecycleOwner, EventObserver {
            SharedPreferencesUtil.saveUserID(requireContext(), it.uid)
            navigateToChats()
        })
    }

    private fun navigateToChats() {
        findNavController().navigate(R.id.action_loginFragment_to_navigation_chats)
    }
}

================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/ui/start/login/LoginViewModel.kt
================================================
package com.fredrikbogg.android_chat_app.ui.start.login

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.fredrikbogg.android_chat_app.data.model.Login
import com.fredrikbogg.android_chat_app.data.db.repository.AuthRepository
import com.fredrikbogg.android_chat_app.ui.DefaultViewModel
import com.fredrikbogg.android_chat_app.data.Event
import com.fredrikbogg.android_chat_app.data.Result
import com.fredrikbogg.android_chat_app.util.isEmailValid
import com.fredrikbogg.android_chat_app.util.isTextValid
import com.google.firebase.auth.FirebaseUser

class LoginViewModel : DefaultViewModel() {

    private val authRepository = AuthRepository()
    private val _isLoggedInEvent = MutableLiveData<Event<FirebaseUser>>()

    val isLoggedInEvent: LiveData<Event<FirebaseUser>> = _isLoggedInEvent
    val emailText = MutableLiveData<String>() // Two way
    val passwordText = MutableLiveData<String>() // Two way
    val isLoggingIn = MutableLiveData<Boolean>() // Two way

    private fun login() {
        isLoggingIn.value = true
        val login = Login(emailText.value!!, passwordText.value!!)

        authRepository.loginUser(login) { result: Result<FirebaseUser> ->
            onResult(null, result)
            if (result is Result.Success) _isLoggedInEvent.value = Event(result.data!!)
            if (result is Result.Success || result is Result.Error) isLoggingIn.value = false
        }
    }

    fun loginPressed() {
        if (!isEmailValid(emailText.value.toString())) {
            mSnackBarText.value = Event("Invalid email format")
            return
        }
        if (!isTextValid(6, passwordText.value)) {
            mSnackBarText.value = Event("Password is too short")
            return
        }

        login()
    }
}

================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/ui/users/UsersBindings.kt
================================================
package com.fredrikbogg.android_chat_app.ui.users

import androidx.databinding.BindingAdapter
import androidx.recyclerview.widget.RecyclerView
import com.fredrikbogg.android_chat_app.data.db.entity.User

@BindingAdapter("bind_users_list")
fun bindUsersList(listView: RecyclerView, items: List<User>?) {
    items?.let { (listView.adapter as UsersListAdapter).submitList(items) }
}



================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/ui/users/UsersFragment.kt
================================================
package com.fredrikbogg.android_chat_app.ui.users

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import com.fredrikbogg.android_chat_app.App
import com.fredrikbogg.android_chat_app.R
import com.fredrikbogg.android_chat_app.databinding.FragmentUsersBinding
import com.fredrikbogg.android_chat_app.data.EventObserver
import com.fredrikbogg.android_chat_app.ui.profile.ProfileFragment


class UsersFragment : Fragment() {

    private val viewModel: UsersViewModel by viewModels { UsersViewModelFactory(App.myUserID) }
    private lateinit var viewDataBinding: FragmentUsersBinding
    private lateinit var listAdapter: UsersListAdapter

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        viewDataBinding =
            FragmentUsersBinding.inflate(inflater, container, false).apply { viewmodel = viewModel }
        viewDataBinding.lifecycleOwner = this.viewLifecycleOwner
        return viewDataBinding.root
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        setupListAdapter()
        setupObservers()
    }

    private fun setupListAdapter() {
        val viewModel = viewDataBinding.viewmodel
        if (viewModel != null) {
            listAdapter = UsersListAdapter(viewModel)
            viewDataBinding.usersRecyclerView.adapter = listAdapter
        } else {
            throw Exception("The viewmodel is not initialized")
        }
    }

    private fun setupObservers() {
        viewModel.selectedUser.observe(viewLifecycleOwner, EventObserver { navigateToProfile(it.info.id) })
    }

    private fun navigateToProfile(userID: String) {
        val bundle = bundleOf(ProfileFragment.ARGS_KEY_USER_ID to userID)
        findNavController().navigate(R.id.action_navigation_users_to_profileFragment, bundle)
    }
}

================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/ui/users/UsersListAdapter.kt
================================================
package com.fredrikbogg.android_chat_app.ui.users

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.fredrikbogg.android_chat_app.data.db.entity.User
import com.fredrikbogg.android_chat_app.databinding.ListItemUserBinding


class UsersListAdapter internal constructor(private val viewModel: UsersViewModel) :
    ListAdapter<User, UsersListAdapter.ViewHolder>(UserDiffCallback()) {

    class ViewHolder(private val binding: ListItemUserBinding) :
        RecyclerView.ViewHolder(binding.root) {
        fun bind(viewModel: UsersViewModel, item: User) {
            binding.viewmodel = viewModel
            binding.user = item
            binding.executePendingBindings()
        }
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(viewModel, getItem(position))
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val layoutInflater = LayoutInflater.from(parent.context)
        val binding = ListItemUserBinding.inflate(layoutInflater, parent, false)
        return ViewHolder(binding)
    }
}

class UserDiffCallback : DiffUtil.ItemCallback<User>() {
    override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
        return oldItem == newItem
    }

    override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
        return oldItem.info.id == newItem.info.id
    }
}

================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/ui/users/UsersViewModel.kt
================================================
package com.fredrikbogg.android_chat_app.ui.users

import androidx.lifecycle.*
import com.fredrikbogg.android_chat_app.data.db.entity.User
import com.fredrikbogg.android_chat_app.data.db.repository.DatabaseRepository
import com.fredrikbogg.android_chat_app.ui.DefaultViewModel
import com.fredrikbogg.android_chat_app.data.Event
import com.fredrikbogg.android_chat_app.data.Result


class UsersViewModelFactory(private val myUserID: String) :
    ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return UsersViewModel(myUserID) as T
    }
}

class UsersViewModel(private val myUserID: String) : DefaultViewModel() {
    private val repository: DatabaseRepository = DatabaseRepository()

    private val _selectedUser = MutableLiveData<Event<User>>()
    var selectedUser: LiveData<Event<User>> = _selectedUser
    private val updatedUsersList = MutableLiveData<MutableList<User>>()
    val usersList = MediatorLiveData<List<User>>()

    init {
        usersList.addSource(updatedUsersList) { mutableList ->
            usersList.value = updatedUsersList.value?.filter { it.info.id != myUserID }
        }
        loadUsers()
    }

    private fun loadUsers() {
        repository.loadUsers { result: Result<MutableList<User>> ->
            onResult(updatedUsersList, result)
        }
    }

    fun selectUser(user: User) {
        _selectedUser.value = Event(user)
    }
}

================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/util/FileConverterUtil.kt
================================================
package com.fredrikbogg.android_chat_app.util

import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import java.io.ByteArrayOutputStream
import java.io.InputStream

fun convertFileToByteArray(context: Context, uri: Uri): ByteArray {
    val inputStream: InputStream? = context.contentResolver.openInputStream(uri)
    val bitmap = BitmapFactory.decodeStream(inputStream)
    val byteArrayOutputStream = ByteArrayOutputStream()
    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream)

    return byteArrayOutputStream.toByteArray()
}


================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/util/FirebaseUtil.kt
================================================
package com.fredrikbogg.android_chat_app.util

import com.google.firebase.database.DataSnapshot

fun <T> wrapSnapshotToClass(className: Class<T>, snap: DataSnapshot): T? {
    return snap.getValue(className)
}

fun <T> wrapSnapshotToArrayList(className: Class<T>, snap: DataSnapshot): MutableList<T> {
    val arrayList: MutableList<T> = arrayListOf()
    for (child in snap.children) {
        child.getValue(className)?.let { arrayList.add(it) }
    }
    return arrayList
}

// Always returns the same combined id when comparing the two users id's
fun convertTwoUserIDs(userID1: String, userID2: String): String {
    return if (userID1 < userID2) {
        userID2 + userID1
    } else {
        userID1 + userID2
    }
}

================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/util/LiveDataExt.kt
================================================
package com.fredrikbogg.android_chat_app.util

import androidx.lifecycle.MutableLiveData

fun <T> MutableLiveData<MutableList<T>>.addNewItem(item: T) {
    val newList = mutableListOf<T>()
    this.value?.let { newList.addAll(it) }
    newList.add(item)
    this.value = newList
}

fun <T> MutableLiveData<MutableList<T>>.updateItemAt(item: T, index: Int) {
    val newList = mutableListOf<T>()
    this.value?.let { newList.addAll(it) }
    newList[index] = item
    this.value = newList
}

fun <T> MutableLiveData<MutableList<T>>.removeItem(item: T) {
    val newList = mutableListOf<T>()
    this.value?.let { newList.addAll(it) }
    newList.remove(item)
    this.value = newList
}


================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/util/SharedPreferencesUtil.kt
================================================
package com.fredrikbogg.android_chat_app.util

import android.content.Context
import android.content.SharedPreferences

object SharedPreferencesUtil {
    private const val PACKAGE_NAME = "com.fredrikbogg.android_chat_app"
    private const val KEY_USER_ID = "user_info"

    private fun getPrefs(context: Context): SharedPreferences {
        return context.getSharedPreferences(PACKAGE_NAME, Context.MODE_PRIVATE)
    }

    fun getUserID(context: Context): String? {
        return getPrefs(context).getString(KEY_USER_ID, null)
    }

    fun saveUserID(context: Context, userID: String) {
        getPrefs(context).edit().putString(KEY_USER_ID, userID).apply()
    }

    fun removeUserID(context: Context) {
        getPrefs(context).edit().remove(KEY_USER_ID).apply()
    }
}

================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/util/TextUtil.kt
================================================
package com.fredrikbogg.android_chat_app.util

fun isEmailValid(email: CharSequence): Boolean {
    return android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches()
}

fun isTextValid(minLength: Int, text: String?): Boolean {
    if (text.isNullOrBlank() || text.length < minLength) {
        return false
    }
    return true
}



================================================
FILE: app/src/main/java/com/fredrikbogg/android_chat_app/util/ViewExt.kt
================================================
package com.fredrikbogg.android_chat_app.util

import android.content.Context
import android.view.View
import android.view.inputmethod.InputMethodManager
import com.fredrikbogg.android_chat_app.R
import com.google.android.material.snackbar.Snackbar

fun View.forceHideKeyboard() {
    val inputManager: InputMethodManager =
        this.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
    inputManager.hideSoftInputFromWindow(this.windowToken, InputMethodManager.HIDE_NOT_ALWAYS)
}

fun View.showSnackBar(text: String) {
    Snackbar.make(this.rootView.findViewById(R.id.container), text, Snackbar.LENGTH_SHORT).show()
}

================================================
FILE: app/src/main/res/drawable/ic_baseline_chat_bubble_24.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24"
    android:viewportHeight="24"
    android:tint="?attr/colorControlNormal">
  <path
      android:fillColor="@android:color/white"
      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"/>
</vector>


================================================
FILE: app/src/main/res/drawable/ic_baseline_error_24.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24"
    android:viewportHeight="24"
    android:tint="?attr/colorControlNormal">
  <path
      android:fillColor="@android:color/white"
      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"/>
</vector>


================================================
FILE: app/src/main/res/drawable/ic_baseline_notifications_24.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24"
    android:viewportHeight="24"
    android:tint="?attr/colorControlNormal">
  <path
      android:fillColor="@android:color/white"
      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"/>
</vector>


================================================
FILE: app/src/main/res/drawable/ic_baseline_people_24.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24"
    android:viewportHeight="24"
    android:tint="?attr/colorControlNormal">
  <path
      android:fillColor="@android:color/white"
      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"/>
</vector>


================================================
FILE: app/src/main/res/drawable/ic_baseline_person_24.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24"
    android:viewportHeight="24"
    android:tint="?attr/colorControlNormal">
  <path
      android:fillColor="@android:color/white"
      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"/>
</vector>


================================================
FILE: app/src/main/res/drawable/ic_baseline_settings_24.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24"
    android:viewportHeight="24"
    android:tint="?attr/colorControlNormal">
  <path
      android:fillColor="@android:color/white"
      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"/>
</vector>


================================================
FILE: app/src/main/res/drawable/round_circle_online_green.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">

    <solid android:color="@android:color/holo_green_light" />
    <stroke android:color="@android:color/white" android:width="1.5dp" />

    <size
        android:width="20dp"
        android:height="20dp" />
</shape>


================================================
FILE: app/src/main/res/drawable/round_circle_primary.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">

    <solid android:color="?attr/colorPrimary" />

    <size
        android:width="20dp"
        android:height="20dp" />
</shape>


================================================
FILE: app/src/main/res/drawable-v24/rounded_rectangle_primary.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">

    <solid android:color="?attr/colorPrimary" />
    <corners android:radius="10dp" />
</shape>


================================================
FILE: app/src/main/res/drawable-v24/rounded_rectangle_secondary.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">

    <solid android:color="#F1F1F1" />

    <corners android:radius="5dp" />
</shape>


================================================
FILE: app/src/main/res/font/nunito.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
        app:fontProviderAuthority="com.google.android.gms.fonts"
        app:fontProviderPackage="com.google.android.gms"
        app:fontProviderQuery="Nunito"
        app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
</font-family>


================================================
FILE: app/src/main/res/font/nunito_bold.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
        app:fontProviderAuthority="com.google.android.gms.fonts"
        app:fontProviderPackage="com.google.android.gms"
        app:fontProviderQuery="name=Nunito&amp;weight=700"
        app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
</font-family>


================================================
FILE: app/src/main/res/font/nunito_extrabold.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
        app:fontProviderAuthority="com.google.android.gms.fonts"
        app:fontProviderPackage="com.google.android.gms"
        app:fontProviderQuery="name=Nunito&amp;weight=800"
        app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
</font-family>


================================================
FILE: app/src/main/res/font/nunito_semibold.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
        app:fontProviderAuthority="com.google.android.gms.fonts"
        app:fontProviderPackage="com.google.android.gms"
        app:fontProviderQuery="name=Nunito&amp;weight=600"
        app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
</font-family>


================================================
FILE: app/src/main/res/layout/activity_main.xml
================================================
<?xml version="1.0" encoding="utf-8"?>


<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <include
        android:id="@+id/main_toolbar"
        layout="@layout/toolbar_main"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <fragment
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="?attr/actionBarSize"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toTopOf="@id/nav_view"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/mobile_navigation"
        tools:ignore="FragmentTagUsage" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/nav_view"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="0dp"
        android:layout_marginEnd="0dp"
        android:background="?android:attr/windowBackground"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:menu="@menu/bottom_nav_menu" />

    <ProgressBar
        android:id="@+id/main_progressBar"
        style="?android:attr/progressBarStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:indeterminate="true"
        android:visibility="invisible"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:visibility="visible" />

</androidx.constraintlayout.widget.ConstraintLayout>

================================================
FILE: app/src/main/res/layout/fragment_chat.xml
================================================
<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="viewmodel"
            type="com.fredrikbogg.android_chat_app.ui.chat.ChatViewModel" />
    </data>


    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/textContentLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/messagesRecyclerView"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:focusable="true"
            android:focusableInTouchMode="true"
            app:bind_disable_item_animator="@{true}"
            app:bind_messages_list="@{viewmodel.messagesList}"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            app:layout_constraintBottom_toTopOf="@id/layoutChatbox"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:listitem="@layout/list_item_message_received" />

        <View
            android:layout_width="0dp"
            android:layout_height="2dp"
            android:layout_marginBottom="0dp"
            android:background="#dfdfdf"
            app:layout_constraintBottom_toTopOf="@+id/layoutChatbox"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent" />

        <LinearLayout
            android:id="@+id/layoutChatbox"
            android:layout_width="0dp"
            android:layout_height="50dp"
            android:background="@android:color/white"
            android:minHeight="48dp"
            android:orientation="horizontal"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent">

            <EditText
                android:id="@+id/editTextMessage"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:layout_marginLeft="16dp"
                android:layout_marginRight="16dp"
                android:layout_weight="1"
                android:background="@android:color/transparent"
                android:hint="@string/enter_message"
                android:importantForAutofill="no"
                android:inputType="text"
                android:maxLines="6"
                android:text="@={viewmodel.newMessageText}" />

            <Button
                android:id="@+id/sendBtn"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginEnd="8dp"
                android:clickable="true"
                android:onClick="@{() -> viewmodel.sendMessagePressed()}"
                android:text="@string/send"
                android:textSize="14sp" />
        </LinearLayout>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

================================================
FILE: app/src/main/res/layout/fragment_chats.xml
================================================
<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="viewmodel"
            type="com.fredrikbogg.android_chat_app.ui.chats.ChatsViewModel" />

    </data>

    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginBottom="?attr/actionBarSize">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/chatsRecyclerView"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:bind_chats_list="@{viewmodel.chatsList}"
                app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintHorizontal_bias="0.5"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                tools:listitem="@layout/list_item_user" />

        </androidx.constraintlayout.widget.ConstraintLayout>
    </androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

================================================
FILE: app/src/main/res/layout/fragment_create_account.xml
================================================
<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="viewmodel"
            type="com.fredrikbogg.android_chat_app.ui.start.createAccount.CreateAccountViewModel" />

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="fill_parent">

        <LinearLayout
            android:id="@+id/textContentLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="20dp"
            android:layout_marginTop="70dp"
            android:layout_marginEnd="20dp"
            android:orientation="vertical"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">

            <TextView
                android:id="@+id/titleText"
                style="@style/BoldText"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="40dp"
                android:gravity="start"
                android:text="@string/create_a_new_account"
                android:textSize="30sp"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintHorizontal_bias="0.5"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <com.google.android.material.textfield.TextInputLayout
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:background="@android:color/transparent"
                app:boxBackgroundColor="@android:color/transparent">

                <EditText
                    android:id="@+id/editTextDisplayName"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:ems="10"
                    android:hint="@string/display_name"
                    android:importantForAutofill="no"
                    android:inputType="textCapWords"
                    android:maxLength="25"
                    android:paddingStart="0dp"
                    android:paddingEnd="0dp"
                    android:text="@={viewmodel.displayNameText}"
                    tools:background="@android:color/transparent" />
            </com.google.android.material.textfield.TextInputLayout>

            <com.google.android.material.textfield.TextInputLayout
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:background="@android:color/transparent"
                android:padding="0dp"
                app:boxBackgroundColor="@android:color/transparent">

                <EditText
                    android:id="@+id/editTextEmail"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:ems="10"
                    android:hint="@string/email"
                    android:importantForAutofill="no"
                    android:inputType="textEmailAddress"
                    android:maxLength="25"
                    android:paddingStart="0dp"
                    android:paddingEnd="0dp"
                    android:text="@={viewmodel.emailText}"
                    tools:background="@android:color/transparent" />
            </com.google.android.material.textfield.TextInputLayout>

            <com.google.android.material.textfield.TextInputLayout

                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:background="@android:color/transparent"
                android:padding="0dp"
                app:boxBackgroundColor="@android:color/transparent">

                <EditText
                    android:id="@+id/editTextPassword"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:ems="10"
                    android:hint="@string/password"
                    android:importantForAutofill="no"
                    android:inputType="textPassword"
                    android:maxLength="25"
                    android:paddingStart="0dp"
                    android:paddingEnd="0dp"
                    android:text="@={viewmodel.passwordText}"
                    tools:background="@android:color/transparent" />
            </com.google.android.material.textfield.TextInputLayout>

        </LinearLayout>

        <Button
            android:id="@+id/loginButton"
            android:layout_width="300dp"
            android:layout_height="60dp"
            android:layout_marginTop="98dp"
            android:backgroundTint="@color/colorAccent"
            android:enabled="@{!viewmodel.isCreatingAccount()}"
            android:onClick="@{() -> viewmodel.createAccountPressed()}"
            android:text="@string/create"
            android:textSize="16sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/guideline50"
            tools:enabled="@{true}" />

        <androidx.constraintlayout.widget.Guideline
            android:id="@+id/guideline50"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            app:layout_constraintGuide_percent=".5" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

================================================
FILE: app/src/main/res/layout/fragment_login.xml
================================================
<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="viewmodel"
            type="com.fredrikbogg.android_chat_app.ui.start.login.LoginViewModel" />

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="fill_parent">

        <LinearLayout
            android:id="@+id/textContentLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="20dp"
            android:layout_marginTop="70dp"
            android:layout_marginEnd="20dp"
            android:orientation="vertical"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">

            <TextView
                android:id="@+id/titleText"
                style="@style/BoldText"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="40dp"
                android:gravity="start"
                android:text="@string/login_to_your_account"
                android:textSize="30sp"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintHorizontal_bias="0.5"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <com.google.android.material.textfield.TextInputLayout
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:background="@android:color/transparent"
                app:boxBackgroundColor="@android:color/transparent">

                <EditText
                    android:id="@+id/editTextEmail"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:ems="10"
                    android:hint="@string/email"
                    android:importantForAutofill="no"
                    android:inputType="textEmailAddress"
                    android:maxLength="25"
                    android:paddingStart="0dp"
                    android:paddingEnd="0dp"
                    android:text="@={viewmodel.emailText}"
                    tools:background="@android:color/transparent" />
            </com.google.android.material.textfield.TextInputLayout>

            <com.google.android.material.textfield.TextInputLayout
                android:id="@+id/editTextPasswordInputLayout"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:background="@android:color/transparent"
                android:padding="0dp"
                app:boxBackgroundColor="@android:color/transparent">

                <EditText
                    android:id="@+id/editTextPassword"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="20dp"
                    android:ems="10"
                    android:hint="@string/password"
                    android:importantForAutofill="no"
                    android:inputType="textPassword"
                    android:maxLength="25"
                    android:paddingStart="0dp"
                    android:paddingEnd="0dp"
                    android:text="@={viewmodel.passwordText}"
                    tools:background="@android:color/transparent" />
            </com.google.android.material.textfield.TextInputLayout>

        </LinearLayout>

        <Button
            android:id="@+id/loginButton"
            android:layout_width="300dp"
            android:layout_height="60dp"
            android:layout_marginTop="98dp"
            android:enabled="@{!viewmodel.isLoggingIn()}"
            android:onClick="@{() -> viewmodel.loginPressed()}"
            android:text="@string/login"
            android:textSize="16sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/guideline50"
            tools:enabled="@{true}" />

        <androidx.constraintlayout.widget.Guideline
            android:id="@+id/guideline50"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            app:layout_constraintGuide_percent=".5" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

================================================
FILE: app/src/main/res/layout/fragment_notifications.xml
================================================
<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="viewmodel"
            type="com.fredrikbogg.android_chat_app.ui.notifications.NotificationsViewModel" />

    </data>

    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginBottom="?attr/actionBarSize">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/usersRecyclerView"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:bind_notifications_list="@{viewmodel.usersInfoList}"
                app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintHorizontal_bias="0.5"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                tools:listitem="@layout/list_item_notification" />

        </androidx.constraintlayout.widget.ConstraintLayout>
    </androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

================================================
FILE: app/src/main/res/layout/fragment_profile.xml
================================================
<?xml version="1.0" encoding="utf-8"?>


<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <import type="android.view.View" />

        <import type="com.fredrikbogg.android_chat_app.ui.chats.ChatsViewModel" />

        <variable
            name="viewmodel"
            type="com.fredrikbogg.android_chat_app.ui.profile.ProfileViewModel" />

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="fill_parent">

        <androidx.constraintlayout.widget.Guideline
            android:id="@+id/guideline60"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            app:layout_constraintGuide_percent=".6" />

        <ImageView
            android:id="@+id/blurredUserImage"
            android:layout_width="0dp"
            android:layout_height="170dp"
            android:contentDescription="@string/user_image"
            android:scaleType="centerCrop"
            app:bind_image_url_blur="@{viewmodel.otherUser.info.profileImageUrl}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:srcCompat="@tools:sample/avatars" />

        <com.google.android.material.card.MaterialCardView
            android:id="@+id/userImageCardView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:cardCornerRadius="60dp"
            app:cardPreventCornerOverlap="true"
            app:layout_constraintBottom_toBottomOf="@id/blurredUserImage"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/blurredUserImage"
            app:strokeColor="@android:color/white"
            app:strokeWidth="2dp">

            <ImageView
                android:layout_width="120dp"
                android:layout_height="120dp"
                android:layout_margin="0dp"
                android:contentDescription="@string/user_image"
                android:scaleType="centerCrop"
                app:bind_image_url="@{viewmodel.otherUser.info.profileImageUrl}"
                tools:src="@tools:sample/avatars[0]" />
        </com.google.android.material.card.MaterialCardView>

        <TextView
            android:id="@+id/nameText"
            style="@style/BoldText"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:gravity="center"
            android:text="@{viewmodel.otherUser.info.displayName}"
            android:textSize="36sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/userImageCardView"
            tools:text="Name" />

        <TextView
            android:id="@+id/statusText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="12dp"
            android:maxLength="40"
            style="@style/MessageSeen"
            android:singleLine="true"
            android:text="@{viewmodel.otherUser.info.status}"
            android:textSize="18sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/nameText"
            tools:text="Status" />

        <androidx.constraintlayout.widget.ConstraintLayout
            android:id="@+id/statesLayout"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_marginStart="42dp"
            android:layout_marginTop="42dp"
            android:layout_marginEnd="42dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/guideline60">

            <Button
                android:id="@+id/addFriendButton"
                android:layout_width="match_parent"
                android:layout_height="55dp"
                android:onClick="@{() -> viewmodel.addFriendPressed()}"
                android:text="@string/add_friend"
                android:textSize="16sp"
                android:visibility="@{viewmodel.layoutState == viewmodel.layoutState.NOT_FRIEND? View.VISIBLE : View.GONE}"
                app:layout_constraintTop_toTopOf="parent"
                tools:visibility="visible" />

            <Button
                android:id="@+id/removeFriendButton"
                android:layout_width="match_parent"
                android:layout_height="55dp"
                android:onClick="@{() -> viewmodel.removeFriendPressed()}"
                android:text="@string/remove_friend"
                android:textSize="16sp"
                android:visibility="@{viewmodel.layoutState == viewmodel.layoutState.IS_FRIEND? View.VISIBLE : View.GONE}"
                app:layout_constraintTop_toTopOf="parent"
                tools:visibility="gone" />

            <Button
                android:id="@+id/requestSentButton"
                android:layout_width="match_parent"
                android:layout_height="55dp"
                android:enabled="false"
                android:text="@string/request_sent"
                android:textSize="16sp"
                android:visibility="@{viewmodel.layoutState == viewmodel.layoutState.REQUEST_SENT? View.VISIBLE : View.GONE}"
                app:layout_constraintTop_toTopOf="parent"
                tools:visibility="gone" />

            <Button
                android:id="@+id/acceptRequestButton"
                android:layout_width="match_parent"
                android:layout_height="55dp"
                android:onClick="@{() -> viewmodel.acceptFriendRequestPressed()}"
                android:text="@string/accept_friend_request"
                android:textSize="16sp"
                android:visibility="@{viewmodel.layoutState == viewmodel.layoutState.ACCEPT_DECLINE? View.VISIBLE : View.GONE}"
                app:layout_constraintTop_toTopOf="parent"
                tools:visibility="gone" />

            <Button
                android:id="@+id/declineRequestButton"
                android:layout_width="match_parent"
                android:backgroundTint="@color/textError"
                android:layout_height="55dp"
                android:layout_marginTop="5dp"
                android:onClick="@{() -> viewmodel.declineFriendRequestPressed()}"
                android:text="@string/decline_friend_request"
                android:textSize="16sp"
                android:visibility="@{viewmodel.layoutState == viewmodel.layoutState.ACCEPT_DECLINE? View.VISIBLE : View.GONE}"
                app:layout_constraintTop_toBottomOf="@id/acceptRequestButton"
                tools:visibility="gone" />

        </androidx.constraintlayout.widget.ConstraintLayout>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

================================================
FILE: app/src/main/res/layout/fragment_settings.xml
================================================
<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="viewmodel"
            type="com.fredrikbogg.android_chat_app.ui.settings.SettingsViewModel" />

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginBottom="?attr/actionBarSize">

        <Button
            android:id="@+id/logoutButton"
            style="@style/VeryBoldText"
            android:layout_width="120dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginBottom="42dp"
            android:backgroundTint="@color/textError"
            android:onClick="@{() -> viewmodel.logoutUserPressed()}"
            android:text="@string/logout"
            android:textColor="@android:color/white"
            android:textSize="14sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent" />

        <ImageView
            android:id="@+id/blurredUserImage"
            android:layout_width="0dp"
            android:layout_height="170dp"
            android:contentDescription="@string/user_image"
            android:scaleType="centerCrop"
            app:bind_image_url_blur="@{viewmodel.userInfo.profileImageUrl}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:srcCompat="@tools:sample/avatars" />

        <com.google.android.material.card.MaterialCardView
            android:id="@+id/userImageCardView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:cardCornerRadius="60dp"
            app:cardPreventCornerOverlap="true"
            app:layout_constraintBottom_toBottomOf="@id/blurredUserImage"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/blurredUserImage"
            app:strokeColor="@android:color/white"
            app:strokeWidth="2dp">

            <ImageView
                android:layout_width="120dp"
                android:layout_height="120dp"
                android:layout_margin="0dp"
                android:contentDescription="@string/user_image"
                android:scaleType="centerCrop"
                app:bind_image_url="@{viewmodel.userInfo.profileImageUrl}"
                tools:src="@tools:sample/avatars[0]" />
        </com.google.android.material.card.MaterialCardView>

        <LinearLayout
            android:id="@+id/contentLayout"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_marginTop="8dp"
            android:orientation="vertical"
            app:layout_constraintBottom_toTopOf="@id/logoutButton"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/userImageCardView">

            <TextView
                android:id="@+id/nameText"
                style="@style/BoldText"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:text="@{viewmodel.userInfo.displayName}"
                android:textSize="36sp"
                tools:text="Name" />

            <TextView
                android:id="@+id/statusText"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="12dp"
                android:gravity="center"
                style="@style/MessageSeen"
                android:text="@{viewmodel.userInfo.status}"
                android:textSize="16sp"
                tools:text="This is a status message" />

            <Button
                android:id="@+id/changeImageButton"
                android:layout_width="match_parent"
                android:layout_height="55dp"
                android:layout_marginStart="24dp"
                android:layout_marginTop="62dp"
                android:layout_marginEnd="24dp"
                android:onClick="@{() -> viewmodel.changeUserImagePressed()}"
                android:text="@string/change_image"
                android:textSize="14sp" />

            <Button
                android:id="@+id/changeStatusButton"
                android:layout_width="match_parent"
                android:layout_height="55dp"
                android:layout_marginStart="24dp"
                android:layout_marginTop="12dp"
                android:layout_marginEnd="24dp"
                android:onClick="@{() -> viewmodel.changeUserStatusPressed()}"
                android:text="@string/change_status"
                android:textSize="14sp" />
        </LinearLayout>


    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

================================================
FILE: app/src/main/res/layout/fragment_start.xml
================================================
<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="viewmodel"
            type="com.fredrikbogg.android_chat_app.ui.start.StartViewModel" />

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="fill_parent">

        <TextView
            android:id="@+id/textView2"
            style="@style/BoldText"
            android:layout_width="280dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="35dp"
            android:gravity="center_horizontal"
            android:text="@string/welcome_to_quick_chat"
            android:textSize="36sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/imageView" />

        <Button
            android:id="@+id/loginButton"
            android:layout_width="0dp"
            android:layout_height="55dp"
            android:layout_marginStart="20dp"
            android:layout_marginTop="50dp"
            android:layout_marginEnd="20dp"
            android:onClick="@{() -> viewmodel.goToLoginPressed()}"
            android:text="@string/login"
            android:textSize="16sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/guideline50" />

        <Button
            android:id="@+id/createAccountButton"
            android:layout_width="0dp"
            android:layout_height="55dp"
            android:layout_marginStart="20dp"
            android:layout_marginTop="10dp"
            android:layout_marginEnd="20dp"
            android:backgroundTint="@color/colorAccent"
            android:onClick="@{() -> viewmodel.goToCreateAccountPressed()}"
            android:text="@string/create_account"
            android:textSize="16sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/loginButton" />

        <androidx.constraintlayout.widget.Guideline
            android:id="@+id/guideline50"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            app:layout_constraintGuide_percent=".5" />

        <ImageView
            android:id="@+id/imageView"
            android:layout_width="70dp"
            android:layout_height="70dp"
            android:layout_marginTop="40dp"
            android:src="@drawable/chat_box"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.498"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:layout_editor_absoluteY="73dp"
            android:contentDescription="@string/chat_icon" />

        <TextView
            android:id="@+id/textView"
            style="@style/BoldText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:alpha=".8"
            android:gravity="center"
            android:text="@string/login_or_create_an_account_to_get_started"
            android:textSize="16sp"
            android:textStyle="italic"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/createAccountButton" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

================================================
FILE: app/src/main/res/layout/fragment_users.xml
================================================
<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="viewmodel"
            type="com.fredrikbogg.android_chat_app.ui.users.UsersViewModel" />

    </data>

    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginBottom="?attr/actionBarSize">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/usersRecyclerView"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:bind_users_list="@{viewmodel.usersList}"
                app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintHorizontal_bias="0.5"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                tools:listitem="@layout/list_item_user" />

        </androidx.constraintlayout.widget.ConstraintLayout>
    </androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

================================================
FILE: app/src/main/res/layout/list_item_chat.xml
================================================
<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <import type="android.view.View" />

        <variable
            name="viewmodel"
            type="com.fredrikbogg.android_chat_app.ui.chats.ChatsViewModel" />

        <variable
            name="chatwithuserinfo"
            type="com.fredrikbogg.android_chat_app.data.model.ChatWithUserInfo" />

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="12dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="12dp"
        android:clickable="true"
        android:focusable="true"
        android:onClick="@{() -> viewmodel.selectChatWithUserInfoPressed(chatwithuserinfo)}"
        app:bind_message="@{chatwithuserinfo.MChat.lastMessage}"
        app:bind_message_textView="@{messageText}"
        app:bind_message_view="@{notSeenView}"
        app:bind_myUserID="@{viewmodel.myUserID}">

        <androidx.cardview.widget.CardView
            android:id="@+id/imageCardView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:cardCornerRadius="29dp"
            app:cardElevation="0dp"
            app:cardPreventCornerOverlap="false"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">

            <ImageView
                android:id="@+id/userProfileImage"
                android:layout_width="58dp"
                android:layout_height="58dp"
                android:layout_margin="0dp"
                android:contentDescription="@string/user_image"
                android:scaleType="centerCrop"
                app:bind_image_url="@{chatwithuserinfo.MUserInfo.profileImageUrl}"
                tools:src="@tools:sample/avatars[0]" />

        </androidx.cardview.widget.CardView>

        <View
            android:id="@+id/onlineView"
            android:layout_width="12dp"
            android:layout_height="12dp"
            android:background="@drawable/round_circle_online_green"
            android:visibility="@{chatwithuserinfo.MUserInfo.online == true? View.VISIBLE : View.INVISIBLE}"
            app:layout_constraintBottom_toBottomOf="@id/imageCardView"
            app:layout_constraintRight_toRightOf="@id/imageCardView"
            tools:visibility="visible" />

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_marginStart="12dp"
            android:layout_marginTop="3dp"
            android:layout_marginBottom="3dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toRightOf="@id/imageCardView"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent">

            <TextView
                android:id="@+id/displayNameText"
                style="@style/BoldText"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:singleLine="true"
                android:text="@{chatwithuserinfo.MUserInfo.displayName}"
                android:textSize="18sp"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toLeftOf="@id/timeText"
                app:layout_constraintTop_toTopOf="parent"
                tools:text="Display name" />

            <TextView
                android:id="@+id/messageText"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:singleLine="true"
                android:textSize="14sp"
                app:bind_chat_message_text="@{chatwithuserinfo.MChat.lastMessage}"
                app:bind_chat_message_text_viewModel="@{viewmodel}"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toLeftOf="@id/notSeenLayout"
                tools:text="Message"
                tools:textAppearance="@style/MessageNotSeen" />

            <TextView
                android:id="@+id/timeText"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:textSize="12sp"
                app:bind_epochTimeMsToDate_with_days_ago="@{chatwithuserinfo.MChat.lastMessage.epochTimeMs}"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                tools:text="11:00 AM" />

            <androidx.constraintlayout.widget.ConstraintLayout
                android:id="@+id/notSeenLayout"
                android:layout_width="54dp"
                android:layout_height="23dp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintRight_toRightOf="parent">

                <View
                    android:id="@+id/notSeenView"
                    android:layout_width="8dp"
                    android:layout_height="8dp"
                    android:background="@drawable/round_circle_primary"
                    android:visibility="invisible"
                    app:layout_constraintBottom_toBottomOf="parent"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintHorizontal_bias="0.5"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toTopOf="parent"
                    tools:visibility="visible" />
            </androidx.constraintlayout.widget.ConstraintLayout>
        </androidx.constraintlayout.widget.ConstraintLayout>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>


================================================
FILE: app/src/main/res/layout/list_item_message_received.xml
================================================
<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="viewmodel"
            type="com.fredrikbogg.android_chat_app.ui.chat.ChatViewModel" />

        <variable
            name="message"
            type="com.fredrikbogg.android_chat_app.data.db.entity.Message" />

    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:layout_marginBottom="10dp"
        android:orientation="vertical">

        <TextView
            android:id="@+id/timeText"
            android:layout_width="match_parent"
            android:layout_height="36dp"
            android:gravity="center"
            android:textSize="12sp"
            app:bind_epochTimeMsToDate="@{message.epochTimeMs}"
            app:bind_message="@{message}"
            app:bind_message_viewModel="@{viewmodel}"
            tools:text="11:40" />

        <TextView
            android:id="@+id/messageText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="start"
            android:layout_marginStart="12dp"
            android:background="@drawable/rounded_rectangle_secondary"
            android:maxWidth="240dp"
            android:paddingLeft="12dp"
            android:paddingTop="6dp"
            android:paddingRight="12dp"
            android:paddingBottom="6dp"
            android:text="@{message.text}"
            android:textColor="@android:color/black"
            android:textSize="16sp"
            tools:text="This is a message" />
    </LinearLayout>
</layout>

================================================
FILE: app/src/main/res/layout/list_item_message_sent.xml
================================================
<?xml version="1.0" encoding="utf-8"?>


<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="viewmodel"
            type="com.fredrikbogg.android_chat_app.ui.chat.ChatViewModel" />

        <variable
            name="message"
            type="com.fredrikbogg.android_chat_app.data.db.entity.Message" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:layout_marginBottom="10dp"
        android:orientation="vertical">

        <TextView
            android:id="@+id/timeText"
            android:layout_width="match_parent"
            android:layout_height="36dp"
            android:gravity="center"
            android:textSize="12sp"
            app:bind_epochTimeMsToDate="@{message.epochTimeMs}"
            app:bind_message="@{message}"
            app:bind_message_viewModel="@{viewmodel}"
            tools:text="11:40" />

        <TextView
            android:id="@+id/messageText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="end"
            android:layout_marginEnd="12dp"
            android:background="@drawable/rounded_rectangle_primary"
            android:maxWidth="240dp"
            android:paddingLeft="12dp"
            android:paddingTop="6dp"
            android:paddingRight="12dp"
            android:paddingBottom="6dp"
            android:text="@{message.text}"
            android:textColor="@android:color/white"
            android:textSize="16sp"
            tools:text="This is a message" />
    </LinearLayout>
</layout>

================================================
FILE: app/src/main/res/layout/list_item_notification.xml
================================================
<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="viewmodel"
            type="com.fredrikbogg.android_chat_app.ui.notifications.NotificationsViewModel" />

        <variable
            name="userinfo"
            type="com.fredrikbogg.android_chat_app.data.db.entity.UserInfo" />

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="12dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="12dp">

        <androidx.cardview.widget.CardView
            android:id="@+id/chatCardView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:cardCornerRadius="29dp"
            app:cardPreventCornerOverlap="false"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">

            <ImageView
                android:id="@+id/userProfileImage"
                android:layout_width="58dp"
                android:layout_height="58dp"
                android:layout_margin="0dp"
                android:contentDescription="@string/user_image"
                android:scaleType="centerCrop"
                app:bind_image_url="@{userinfo.profileImageUrl}"
                tools:src="@tools:sample/avatars[0]" />
        </androidx.cardview.widget.CardView>

        <TextView
            android:id="@+id/displayNameText"
            style="@style/BoldText"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:singleLine="true"
            android:text="@{userinfo.displayName}"
            android:textSize="18sp"
            app:layout_constraintLeft_toRightOf="@id/chatCardView"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="@id/chatCardView"
            tools:text="Display name" />

        <TextView
            android:id="@+id/requestText"
            style="@style/BoldText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginBottom="7dp"
            android:text="@string/new_friend_request"

            android:textSize="14sp"
            app:layout_constraintBottom_toBottomOf="@id/chatCardView"
            app:layout_constraintLeft_toRightOf="@id/chatCardView" />

        <Button
            android:id="@+id/acceptButton"
            android:layout_width="wrap_content"
            android:layout_height="45dp"
            android:layout_marginTop="10dp"
            android:onClick="@{() -> viewmodel.acceptNotificationPressed(userinfo)}"
            android:text="@string/accept"
            app:layout_constraintLeft_toLeftOf="@id/requestText"
            app:layout_constraintTop_toBottomOf="@id/requestText" />

        <Button
            android:id="@+id/declineButton"
            android:layout_width="wrap_content"
            android:layout_height="45dp"
            android:layout_marginStart="24dp"
            android:backgroundTint="@color/textError"
            android:onClick="@{() -> viewmodel.declineNotificationPressed(userinfo)}"
            android:text="@string/decline"
            app:layout_constraintLeft_toRightOf="@id/acceptButton"
            app:layout_constraintTop_toTopOf="@id/acceptButton" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>


================================================
FILE: app/src/main/res/layout/list_item_user.xml
================================================
<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="viewmodel"
            type="com.fredrikbogg.android_chat_app.ui.users.UsersViewModel" />

        <variable
            name="user"
            type="com.fredrikbogg.android_chat_app.data.db.entity.User" />

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="12dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="12dp"
        android:clickable="true"
        android:focusable="true"
        android:onClick="@{() -> viewmodel.selectUser(user)}">

        <androidx.cardview.widget.CardView
            android:id="@+id/imageCardView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:cardCornerRadius="29dp"
            app:cardPreventCornerOverlap="false"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">

            <ImageView
                android:id="@+id/userProfileImage"
                android:layout_width="58dp"
                android:layout_height="58dp"
                android:layout_margin="0dp"
                android:contentDescription="@string/user_image"
                android:scaleType="centerCrop"
                app:bind_image_url="@{user.info.profileImageUrl}"
                tools:src="@tools:sample/avatars[0]" />
        </androidx.cardview.widget.CardView>

        <TextView
            android:id="@+id/displayNameText"
            style="@style/BoldText"
            android:layout_width="0dp"
            android:layout_marginTop="3dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:singleLine="true"
            android:text="@{user.info.displayName}"
            android:textSize="18sp"
            app:layout_constraintLeft_toRightOf="@id/imageCardView"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="@id/imageCardView"
            tools:text="Display name" />

        <TextView
            android:id="@+id/statusText"
            style="@style/MessageSeen"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginBottom="3dp"
            android:layout_marginStart="16dp"
            android:singleLine="true"
            android:text="@{user.info.status}"
            android:textSize="14sp"
            app:layout_constraintBottom_toBottomOf="@id/imageCardView"
            app:layout_constraintLeft_toRightOf="@id/imageCardView"
            app:layout_constraintRight_toRightOf="parent"
            tools:text="Status" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>


================================================
FILE: app/src/main/res/layout/toolbar_addon_chat.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <import type="android.view.View" />

        <variable
            name="viewmodel"
            type="com.fredrikbogg.android_chat_app.ui.chat.ChatViewModel" />
    </data>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorPrimary"
        android:minHeight="?attr/actionBarSize"
        tools:layout_height="?attr/actionBarSize">


        <androidx.constraintlayout.widget.ConstraintLayout
            android:id="@+id/imageContentLayout"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentStart="true"
            android:layout_centerInParent="true">

            <androidx.cardview.widget.CardView
                android:id="@+id/imageCardView"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                app:cardCornerRadius="22dp"
                app:cardElevation="0dp"
                app:cardPreventCornerOverlap="false"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintHorizontal_bias="0.5"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent">

                <ImageView
                    android:id="@+id/userProfileImage"
                    android:layout_width="44dp"
                    android:layout_height="44dp"
                    android:layout_margin="0dp"
                    android:contentDescription="@string/user_image"
                    android:scaleType="centerCrop"
                    app:bind_image_url="@{viewmodel.otherUser.profileImageUrl}"
                    tools:src="@drawable/ic_baseline_person_24" />
            </androidx.cardview.widget.CardView>

            <View
                android:id="@+id/onlineView"
                android:layout_width="11dp"
                android:layout_height="11dp"
                android:layout_marginStart="35dp"
                android:background="@drawable/round_circle_online_green"
                android:visibility="@{viewmodel.otherUser.online == true? View.VISIBLE : View.INVISIBLE}"
                app:layout_constraintBottom_toBottomOf="@id/imageCardView"
                app:layout_constraintRight_toRightOf="@id/imageCardView"
                tools:visibility="visible" />

        </androidx.constraintlayout.widget.ConstraintLayout>

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:layout_alignParentEnd="true"
            android:layout_centerInParent="true"
            android:layout_marginStart="16dp"
            android:layout_toEndOf="@id/imageContentLayout"
            android:orientation="vertical">

            <TextView
                android:id="@+id/otherUserNameText"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"

                android:maxLines="1"
                android:text="@{viewmodel.otherUser.displayName}"
                android:textColor="@android:color/white"
                android:textSize="18sp"
                tools:text="John" />

            <TextView
                android:id="@+id/onlineStatusText"
                android:layout_width="wrap_content"
             
                android:layout_height="wrap_content"
                android:alpha=".7"
                android:maxLines="1"
                android:text="@{viewmodel.otherUser.online == true? `Online` : `Offline`}"
                android:textColor="@android:color/white"
                android:textSize="14sp"
                tools:text="Online" />
        </LinearLayout>


    </RelativeLayout>

</layout>

================================================
FILE: app/src/main/res/layout/toolbar_main.xml
================================================
<?xml version="1.0" encoding="utf-8"?>


<androidx.appcompat.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/colorPrimary"
    android:minHeight="?attr/actionBarSize"
    android:theme="@style/ThemeOverlay.MaterialComponents.Dark"
    tools:ignore="Overdraw">

</androidx.appcompat.widget.Toolbar>



================================================
FILE: app/src/main/res/menu/bottom_nav_menu.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/navigation_chats"
        android:icon="@drawable/ic_baseline_chat_bubble_24"
        android:title="@string/title_chats" />
    <item
        android:id="@+id/navigation_notifications"
        android:icon="@drawable/ic_baseline_notifications_24"
        android:title="@string/title_notifications" />
    <item
        android:id="@+id/navigation_users"
        android:icon="@drawable/ic_baseline_people_24"
        android:title="@string/title_users" />
    <item
        android:id="@+id/navigation_settings"
        android:icon="@drawable/ic_baseline_settings_24"
        android:title="@string/title_settings" />
</menu>

================================================
FILE: app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
    <background android:drawable="@color/ic_launcher_background"/>
    <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

================================================
FILE: app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
    <background android:drawable="@color/ic_launcher_background"/>
    <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

================================================
FILE: app/src/main/res/navigation/mobile_navigation.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    app:startDestination="@+id/startFragment">

    <fragment
        android:id="@+id/navigation_chats"
        android:name="com.fredrikbogg.android_chat_app.ui.chats.ChatsFragment"
        android:label="@string/title_chats"
        tools:layout="@layout/fragment_chats">
        <action
            android:id="@+id/action_navigation_chats_to_chatFragment"
            app:destination="@id/chatFragment" />
    </fragment>
    <fragment
        android:id="@+id/navigation_notifications"
        android:name="com.fredrikbogg.android_chat_app.ui.notifications.NotificationsFragment"
        android:label="@string/title_notifications"
        tools:layout="@layout/fragment_notifications" />
    <fragment
        android:id="@+id/navigation_users"
        android:name="com.fredrikbogg.android_chat_app.ui.users.UsersFragment"
        android:label="@string/title_users"
        tools:layout="@layout/fragment_users">
        <action
            android:id="@+id/a
Download .txt
gitextract_1onv52u2/

├── .gitattributes
├── .gitignore
├── .idea/
│   ├── .name
│   ├── codeStyles/
│   │   ├── Project.xml
│   │   └── codeStyleConfig.xml
│   ├── jarRepositories.xml
│   ├── misc.xml
│   ├── render.experimental.xml
│   └── runConfigurations.xml
├── LICENSE
├── README.md
├── app/
│   ├── .gitignore
│   ├── build.gradle
│   ├── google-services.json
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── com/
│       │           └── fredrikbogg/
│       │               └── android_chat_app/
│       │                   └── ExampleInstrumentedTest.kt
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   ├── java/
│       │   │   └── com/
│       │   │       └── fredrikbogg/
│       │   │           └── android_chat_app/
│       │   │               ├── App.kt
│       │   │               ├── data/
│       │   │               │   ├── Event.kt
│       │   │               │   ├── Result.kt
│       │   │               │   ├── db/
│       │   │               │   │   ├── entity/
│       │   │               │   │   │   ├── Chat.kt
│       │   │               │   │   │   ├── Message.kt
│       │   │               │   │   │   └── User.kt
│       │   │               │   │   ├── remote/
│       │   │               │   │   │   ├── FirebaseAuthSource.kt
│       │   │               │   │   │   ├── FirebaseDatabaseSource.kt
│       │   │               │   │   │   └── FirebaseStorageSource.kt
│       │   │               │   │   └── repository/
│       │   │               │   │       ├── AuthRepository.kt
│       │   │               │   │       ├── DatabaseRepository.kt
│       │   │               │   │       └── StorageRepository.kt
│       │   │               │   └── model/
│       │   │               │       ├── ChatWithUserInfo.kt
│       │   │               │       ├── CreateUser.kt
│       │   │               │       └── Login.kt
│       │   │               ├── ui/
│       │   │               │   ├── DefaultBindings.kt
│       │   │               │   ├── DefaultViewModel.kt
│       │   │               │   ├── chat/
│       │   │               │   │   ├── ChatFragment.kt
│       │   │               │   │   ├── ChatViewModel.kt
│       │   │               │   │   ├── MessagesBindings.kt
│       │   │               │   │   └── MessagesListAdapter.kt
│       │   │               │   ├── chats/
│       │   │               │   │   ├── ChatsBindings.kt
│       │   │               │   │   ├── ChatsFragment.kt
│       │   │               │   │   ├── ChatsListAdapter.kt
│       │   │               │   │   └── ChatsViewModel.kt
│       │   │               │   ├── main/
│       │   │               │   │   ├── MainActivity.kt
│       │   │               │   │   └── MainViewModel.kt
│       │   │               │   ├── notifications/
│       │   │               │   │   ├── NotificationsBindings.kt
│       │   │               │   │   ├── NotificationsFragment.kt
│       │   │               │   │   ├── NotificationsListAdapter.kt
│       │   │               │   │   └── NotificationsViewModel.kt
│       │   │               │   ├── profile/
│       │   │               │   │   ├── ProfileFragment.kt
│       │   │               │   │   └── ProfileViewModel.kt
│       │   │               │   ├── settings/
│       │   │               │   │   ├── SettingsFragment.kt
│       │   │               │   │   └── SettingsViewModel.kt
│       │   │               │   ├── start/
│       │   │               │   │   ├── StartFragment.kt
│       │   │               │   │   ├── StartViewModel.kt
│       │   │               │   │   ├── createAccount/
│       │   │               │   │   │   ├── CreateAccountFragment.kt
│       │   │               │   │   │   └── CreateAccountViewModel.kt
│       │   │               │   │   └── login/
│       │   │               │   │       ├── LoginFragment.kt
│       │   │               │   │       └── LoginViewModel.kt
│       │   │               │   └── users/
│       │   │               │       ├── UsersBindings.kt
│       │   │               │       ├── UsersFragment.kt
│       │   │               │       ├── UsersListAdapter.kt
│       │   │               │       └── UsersViewModel.kt
│       │   │               └── util/
│       │   │                   ├── FileConverterUtil.kt
│       │   │                   ├── FirebaseUtil.kt
│       │   │                   ├── LiveDataExt.kt
│       │   │                   ├── SharedPreferencesUtil.kt
│       │   │                   ├── TextUtil.kt
│       │   │                   └── ViewExt.kt
│       │   └── res/
│       │       ├── drawable/
│       │       │   ├── ic_baseline_chat_bubble_24.xml
│       │       │   ├── ic_baseline_error_24.xml
│       │       │   ├── ic_baseline_notifications_24.xml
│       │       │   ├── ic_baseline_people_24.xml
│       │       │   ├── ic_baseline_person_24.xml
│       │       │   ├── ic_baseline_settings_24.xml
│       │       │   ├── round_circle_online_green.xml
│       │       │   └── round_circle_primary.xml
│       │       ├── drawable-v24/
│       │       │   ├── rounded_rectangle_primary.xml
│       │       │   └── rounded_rectangle_secondary.xml
│       │       ├── font/
│       │       │   ├── nunito.xml
│       │       │   ├── nunito_bold.xml
│       │       │   ├── nunito_extrabold.xml
│       │       │   └── nunito_semibold.xml
│       │       ├── layout/
│       │       │   ├── activity_main.xml
│       │       │   ├── fragment_chat.xml
│       │       │   ├── fragment_chats.xml
│       │       │   ├── fragment_create_account.xml
│       │       │   ├── fragment_login.xml
│       │       │   ├── fragment_notifications.xml
│       │       │   ├── fragment_profile.xml
│       │       │   ├── fragment_settings.xml
│       │       │   ├── fragment_start.xml
│       │       │   ├── fragment_users.xml
│       │       │   ├── list_item_chat.xml
│       │       │   ├── list_item_message_received.xml
│       │       │   ├── list_item_message_sent.xml
│       │       │   ├── list_item_notification.xml
│       │       │   ├── list_item_user.xml
│       │       │   ├── toolbar_addon_chat.xml
│       │       │   └── toolbar_main.xml
│       │       ├── menu/
│       │       │   └── bottom_nav_menu.xml
│       │       ├── mipmap-anydpi-v26/
│       │       │   ├── ic_launcher.xml
│       │       │   └── ic_launcher_round.xml
│       │       ├── navigation/
│       │       │   └── mobile_navigation.xml
│       │       └── values/
│       │           ├── colors.xml
│       │           ├── dimens.xml
│       │           ├── font_certs.xml
│       │           ├── ic_launcher_background.xml
│       │           ├── preloaded_fonts.xml
│       │           ├── strings.xml
│       │           └── styles.xml
│       └── test/
│           └── java/
│               └── com/
│                   └── fredrikbogg/
│                       └── android_chat_app/
│                           └── ExampleUnitTest.kt
├── build.gradle
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
Condensed preview — 118 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (222K chars).
[
  {
    "path": ".gitattributes",
    "chars": 66,
    "preview": "# Auto detect text files and perform LF normalization\n* text=auto\n"
  },
  {
    "path": ".gitignore",
    "chars": 1436,
    "preview": "# 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# Ge"
  },
  {
    "path": ".idea/.name",
    "chars": 16,
    "preview": "Android-Chat-App"
  },
  {
    "path": ".idea/codeStyles/Project.xml",
    "chars": 4353,
    "preview": "<component name=\"ProjectCodeStyleConfiguration\">\n  <code_scheme name=\"Project\" version=\"173\">\n    <JetCodeStyleSettings>"
  },
  {
    "path": ".idea/codeStyles/codeStyleConfig.xml",
    "chars": 142,
    "preview": "<component name=\"ProjectCodeStyleConfiguration\">\n  <state>\n    <option name=\"USE_PER_PROJECT_SETTINGS\" value=\"true\" />\n "
  },
  {
    "path": ".idea/jarRepositories.xml",
    "chars": 1052,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"RemoteRepositoriesConfiguration\">\n    <r"
  },
  {
    "path": ".idea/misc.xml",
    "chars": 357,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ProjectRootManager\" version=\"2\" language"
  },
  {
    "path": ".idea/render.experimental.xml",
    "chars": 173,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"RenderSettings\">\n    <option name=\"showD"
  },
  {
    "path": ".idea/runConfigurations.xml",
    "chars": 564,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"RunConfigurationProducerService\">\n    <o"
  },
  {
    "path": "LICENSE",
    "chars": 1069,
    "preview": "MIT License\n\nCopyright (c) 2020 Fredrik Bogg\n\nPermission is hereby granted, free of charge, to any person obtaining a co"
  },
  {
    "path": "README.md",
    "chars": 2868,
    "preview": "# Chat App Android\n![HeaderImage](github_images/header.png)\n\n## Introduction\nThis is a demo application built with the g"
  },
  {
    "path": "app/.gitignore",
    "chars": 6,
    "preview": "/build"
  },
  {
    "path": "app/build.gradle",
    "chars": 2381,
    "preview": "apply plugin: 'com.android.application'\napply plugin: 'kotlin-android'\napply plugin: 'kotlin-android-extensions'\napply p"
  },
  {
    "path": "app/google-services.json",
    "chars": 11,
    "preview": "-EDIT THIS-"
  },
  {
    "path": "app/proguard-rules.pro",
    "chars": 750,
    "preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
  },
  {
    "path": "app/src/androidTest/java/com/fredrikbogg/android_chat_app/ExampleInstrumentedTest.kt",
    "chars": 691,
    "preview": "package com.fredrikbogg.android_chat_app\n\nimport androidx.test.platform.app.InstrumentationRegistry\nimport androidx.test"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "chars": 1118,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package="
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/App.kt",
    "chars": 581,
    "preview": "package com.fredrikbogg.android_chat_app\n\nimport android.app.Application\nimport com.fredrikbogg.android_chat_app.util.Sh"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/data/Event.kt",
    "chars": 570,
    "preview": "package com.fredrikbogg.android_chat_app.data\n\nimport androidx.lifecycle.Observer\n\n\nopen class Event<out T>(private val "
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/data/Result.kt",
    "chars": 269,
    "preview": "package com.fredrikbogg.android_chat_app.data\n\n\nsealed class Result<out R> {\n    data class Success<out T>(val data: T? "
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/data/db/entity/Chat.kt",
    "chars": 418,
    "preview": "package com.fredrikbogg.android_chat_app.data.db.entity\n\nimport com.google.firebase.database.PropertyName\n\n\ndata class C"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/data/db/entity/Message.kt",
    "chars": 507,
    "preview": "package com.fredrikbogg.android_chat_app.data.db.entity\n\nimport com.google.firebase.database.PropertyName\nimport java.ut"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/data/db/entity/User.kt",
    "chars": 1432,
    "preview": "package com.fredrikbogg.android_chat_app.data.db.entity\n\nimport com.google.firebase.database.PropertyName\n\n\ndata class U"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/data/db/remote/FirebaseAuthSource.kt",
    "chars": 1956,
    "preview": "package com.fredrikbogg.android_chat_app.data.db.remote\n\nimport com.fredrikbogg.android_chat_app.data.model.CreateUser\ni"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/data/db/remote/FirebaseDatabaseSource.kt",
    "chars": 11041,
    "preview": "package com.fredrikbogg.android_chat_app.data.db.remote\n\nimport com.fredrikbogg.android_chat_app.data.Result\nimport com."
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/data/db/remote/FirebaseStorageSource.kt",
    "chars": 562,
    "preview": "package com.fredrikbogg.android_chat_app.data.db.remote\n\nimport android.net.Uri\nimport com.google.android.gms.tasks.Task"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/data/db/repository/AuthRepository.kt",
    "chars": 1438,
    "preview": "package com.fredrikbogg.android_chat_app.data.db.repository\n\nimport com.fredrikbogg.android_chat_app.data.model.CreateUs"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/data/db/repository/DatabaseRepository.kt",
    "chars": 5987,
    "preview": "package com.fredrikbogg.android_chat_app.data.db.repository\n\nimport com.fredrikbogg.android_chat_app.data.db.entity.*\nim"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/data/db/repository/StorageRepository.kt",
    "chars": 665,
    "preview": "package com.fredrikbogg.android_chat_app.data.db.repository\n\nimport android.net.Uri\nimport com.fredrikbogg.android_chat_"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/data/model/ChatWithUserInfo.kt",
    "chars": 258,
    "preview": "package com.fredrikbogg.android_chat_app.data.model\n\nimport com.fredrikbogg.android_chat_app.data.db.entity.Chat\nimport "
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/data/model/CreateUser.kt",
    "chars": 169,
    "preview": "package com.fredrikbogg.android_chat_app.data.model\n\ndata class CreateUser(\n    var displayName: String = \"\",\n    var em"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/data/model/Login.kt",
    "chars": 130,
    "preview": "package com.fredrikbogg.android_chat_app.data.model\n\ndata class Login(\n    var email: String = \"\",\n    var password: Str"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/DefaultBindings.kt",
    "chars": 2573,
    "preview": "package com.fredrikbogg.android_chat_app.ui\n\nimport android.annotation.SuppressLint\nimport android.widget.ImageView\nimpo"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/DefaultViewModel.kt",
    "chars": 1170,
    "preview": "package com.fredrikbogg.android_chat_app.ui\n\nimport androidx.lifecycle.LiveData\nimport androidx.lifecycle.MutableLiveDat"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/chat/ChatFragment.kt",
    "chars": 4034,
    "preview": "package com.fredrikbogg.android_chat_app.ui.chat\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport and"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/chat/ChatViewModel.kt",
    "chars": 3341,
    "preview": "package com.fredrikbogg.android_chat_app.ui.chat\n\nimport androidx.lifecycle.*\nimport com.fredrikbogg.android_chat_app.da"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/chat/MessagesBindings.kt",
    "chars": 1098,
    "preview": "package com.fredrikbogg.android_chat_app.ui.chat\n\nimport android.view.View\nimport androidx.databinding.BindingAdapter\nim"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/chat/MessagesListAdapter.kt",
    "chars": 3104,
    "preview": "package com.fredrikbogg.android_chat_app.ui.chat\n\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimpor"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/chats/ChatsBindings.kt",
    "chars": 1408,
    "preview": "@file:Suppress(\"unused\")\n\npackage com.fredrikbogg.android_chat_app.ui.chats\n\nimport android.view.View\nimport android.wid"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/chats/ChatsFragment.kt",
    "chars": 2484,
    "preview": "package com.fredrikbogg.android_chat_app.ui.chats\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport an"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/chats/ChatsListAdapter.kt",
    "chars": 1719,
    "preview": "package com.fredrikbogg.android_chat_app.ui.chats\n\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimpo"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/chats/ChatsViewModel.kt",
    "chars": 3599,
    "preview": "package com.fredrikbogg.android_chat_app.ui.chats\n\nimport androidx.lifecycle.*\nimport com.fredrikbogg.android_chat_app.d"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/main/MainActivity.kt",
    "chars": 3491,
    "preview": "package com.fredrikbogg.android_chat_app.ui.main\n\nimport android.os.Bundle\nimport android.view.View\nimport android.widge"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/main/MainViewModel.kt",
    "chars": 2485,
    "preview": "package com.fredrikbogg.android_chat_app.ui.main\n\nimport androidx.lifecycle.LiveData\nimport androidx.lifecycle.MutableLi"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/notifications/NotificationsBindings.kt",
    "chars": 421,
    "preview": "package com.fredrikbogg.android_chat_app.ui.notifications\n\nimport androidx.databinding.BindingAdapter\nimport androidx.re"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/notifications/NotificationsFragment.kt",
    "chars": 1552,
    "preview": "package com.fredrikbogg.android_chat_app.ui.notifications\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\ni"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/notifications/NotificationsListAdapter.kt",
    "chars": 1664,
    "preview": "package com.fredrikbogg.android_chat_app.ui.notifications\n\nimport android.view.LayoutInflater\nimport android.view.ViewGr"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/notifications/NotificationsViewModel.kt",
    "chars": 3031,
    "preview": "package com.fredrikbogg.android_chat_app.ui.notifications\n\nimport androidx.lifecycle.MediatorLiveData\nimport androidx.li"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/profile/ProfileFragment.kt",
    "chars": 2260,
    "preview": "package com.fredrikbogg.android_chat_app.ui.profile\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport "
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/profile/ProfileViewModel.kt",
    "chars": 3685,
    "preview": "package com.fredrikbogg.android_chat_app.ui.profile\n\nimport androidx.lifecycle.*\nimport com.fredrikbogg.android_chat_app"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/settings/SettingsFragment.kt",
    "chars": 3818,
    "preview": "package com.fredrikbogg.android_chat_app.ui.settings\n\nimport android.app.Activity.RESULT_OK\nimport android.content.Conte"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/settings/SettingsViewModel.kt",
    "chars": 2915,
    "preview": "package com.fredrikbogg.android_chat_app.ui.settings\n\nimport android.net.Uri\nimport androidx.lifecycle.LiveData\nimport a"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/start/StartFragment.kt",
    "chars": 2124,
    "preview": "package com.fredrikbogg.android_chat_app.ui.start\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport an"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/start/StartViewModel.kt",
    "chars": 685,
    "preview": "package com.fredrikbogg.android_chat_app.ui.start\n\nimport androidx.lifecycle.LiveData\nimport androidx.lifecycle.MutableL"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/start/createAccount/CreateAccountFragment.kt",
    "chars": 2504,
    "preview": "package com.fredrikbogg.android_chat_app.ui.start.createAccount\n\nimport android.os.Bundle\nimport android.view.LayoutInfl"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/start/createAccount/CreateAccountViewModel.kt",
    "chars": 2514,
    "preview": "package com.fredrikbogg.android_chat_app.ui.start.createAccount\n\nimport androidx.lifecycle.LiveData\nimport androidx.life"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/start/login/LoginFragment.kt",
    "chars": 2449,
    "preview": "package com.fredrikbogg.android_chat_app.ui.start.login\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimp"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/start/login/LoginViewModel.kt",
    "chars": 1790,
    "preview": "package com.fredrikbogg.android_chat_app.ui.start.login\n\nimport androidx.lifecycle.LiveData\nimport androidx.lifecycle.Mu"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/users/UsersBindings.kt",
    "chars": 382,
    "preview": "package com.fredrikbogg.android_chat_app.ui.users\n\nimport androidx.databinding.BindingAdapter\nimport androidx.recyclervi"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/users/UsersFragment.kt",
    "chars": 2137,
    "preview": "package com.fredrikbogg.android_chat_app.ui.users\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport an"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/users/UsersListAdapter.kt",
    "chars": 1566,
    "preview": "package com.fredrikbogg.android_chat_app.ui.users\n\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimpo"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/ui/users/UsersViewModel.kt",
    "chars": 1431,
    "preview": "package com.fredrikbogg.android_chat_app.ui.users\n\nimport androidx.lifecycle.*\nimport com.fredrikbogg.android_chat_app.d"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/util/FileConverterUtil.kt",
    "chars": 623,
    "preview": "package com.fredrikbogg.android_chat_app.util\n\nimport android.content.Context\nimport android.graphics.Bitmap\nimport andr"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/util/FirebaseUtil.kt",
    "chars": 725,
    "preview": "package com.fredrikbogg.android_chat_app.util\n\nimport com.google.firebase.database.DataSnapshot\n\nfun <T> wrapSnapshotToC"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/util/LiveDataExt.kt",
    "chars": 686,
    "preview": "package com.fredrikbogg.android_chat_app.util\n\nimport androidx.lifecycle.MutableLiveData\n\nfun <T> MutableLiveData<Mutabl"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/util/SharedPreferencesUtil.kt",
    "chars": 782,
    "preview": "package com.fredrikbogg.android_chat_app.util\n\nimport android.content.Context\nimport android.content.SharedPreferences\n\n"
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/util/TextUtil.kt",
    "chars": 334,
    "preview": "package com.fredrikbogg.android_chat_app.util\n\nfun isEmailValid(email: CharSequence): Boolean {\n    return android.util."
  },
  {
    "path": "app/src/main/java/com/fredrikbogg/android_chat_app/util/ViewExt.kt",
    "chars": 649,
    "preview": "package com.fredrikbogg.android_chat_app.util\n\nimport android.content.Context\nimport android.view.View\nimport android.vi"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_chat_bubble_24.xml",
    "chars": 398,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_error_24.xml",
    "chars": 423,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_notifications_24.xml",
    "chars": 508,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_people_24.xml",
    "chars": 645,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_person_24.xml",
    "chars": 444,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_settings_24.xml",
    "chars": 1223,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "app/src/main/res/drawable/round_circle_online_green.xml",
    "chars": 350,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:sha"
  },
  {
    "path": "app/src/main/res/drawable/round_circle_primary.xml",
    "chars": 263,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:sha"
  },
  {
    "path": "app/src/main/res/drawable-v24/rounded_rectangle_primary.xml",
    "chars": 233,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:sha"
  },
  {
    "path": "app/src/main/res/drawable-v24/rounded_rectangle_secondary.xml",
    "chars": 222,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:sha"
  },
  {
    "path": "app/src/main/res/font/nunito.xml",
    "chars": 355,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<font-family xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        app:font"
  },
  {
    "path": "app/src/main/res/font/nunito_bold.xml",
    "chars": 375,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<font-family xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        app:font"
  },
  {
    "path": "app/src/main/res/font/nunito_extrabold.xml",
    "chars": 375,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<font-family xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        app:font"
  },
  {
    "path": "app/src/main/res/font/nunito_semibold.xml",
    "chars": 375,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<font-family xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        app:font"
  },
  {
    "path": "app/src/main/res/layout/activity_main.xml",
    "chars": 2329,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schema"
  },
  {
    "path": "app/src/main/res/layout/fragment_chat.xml",
    "chars": 3330,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app"
  },
  {
    "path": "app/src/main/res/layout/fragment_chats.xml",
    "chars": 1580,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app"
  },
  {
    "path": "app/src/main/res/layout/fragment_create_account.xml",
    "chars": 6030,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app"
  },
  {
    "path": "app/src/main/res/layout/fragment_login.xml",
    "chars": 4978,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app"
  },
  {
    "path": "app/src/main/res/layout/fragment_notifications.xml",
    "chars": 1616,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app"
  },
  {
    "path": "app/src/main/res/layout/fragment_profile.xml",
    "chars": 7649,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:ap"
  },
  {
    "path": "app/src/main/res/layout/fragment_settings.xml",
    "chars": 5465,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app"
  },
  {
    "path": "app/src/main/res/layout/fragment_start.xml",
    "chars": 4072,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app"
  },
  {
    "path": "app/src/main/res/layout/fragment_users.xml",
    "chars": 1580,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app"
  },
  {
    "path": "app/src/main/res/layout/list_item_chat.xml",
    "chars": 6074,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app"
  },
  {
    "path": "app/src/main/res/layout/list_item_message_received.xml",
    "chars": 1852,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app"
  },
  {
    "path": "app/src/main/res/layout/list_item_message_sent.xml",
    "chars": 1846,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:ap"
  },
  {
    "path": "app/src/main/res/layout/list_item_notification.xml",
    "chars": 3790,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app"
  },
  {
    "path": "app/src/main/res/layout/list_item_user.xml",
    "chars": 3090,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app"
  },
  {
    "path": "app/src/main/res/layout/toolbar_addon_chat.xml",
    "chars": 4180,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app="
  },
  {
    "path": "app/src/main/res/layout/toolbar_main.xml",
    "chars": 488,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n\n<androidx.appcompat.widget.Toolbar xmlns:android=\"http://schemas.android.com/ap"
  },
  {
    "path": "app/src/main/res/menu/bottom_nav_menu.xml",
    "chars": 769,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n    "
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
    "chars": 265,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <b"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
    "chars": 265,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <b"
  },
  {
    "path": "app/src/main/res/navigation/mobile_navigation.xml",
    "chars": 3859,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<navigation xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "chars": 263,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#2D9CDB</color>\n    <color name=\"color"
  },
  {
    "path": "app/src/main/res/values/dimens.xml",
    "chars": 96,
    "preview": "<resources>\n    <!-- Default screen margins, per the Android Design guidelines. -->\n</resources>"
  },
  {
    "path": "app/src/main/res/values/font_certs.xml",
    "chars": 3581,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <array name=\"com_google_android_gms_fonts_certs\">\n        <item>@"
  },
  {
    "path": "app/src/main/res/values/ic_launcher_background.xml",
    "chars": 120,
    "preview": "<?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",
    "chars": 293,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <array name=\"preloaded_fonts\" translatable=\"false\">\n        <item"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "chars": 1778,
    "preview": "<resources>\n    <string name=\"app_name\">Chat App</string>\n    <string name=\"title_chats\">Chats</string>\n    <string name"
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "chars": 1188,
    "preview": "<resources>\n\n    <style name=\"AppTheme\" parent=\"Theme.MaterialComponents.Light.NoActionBar\">\n        <item name=\"colorPr"
  },
  {
    "path": "app/src/test/java/com/fredrikbogg/android_chat_app/ExampleUnitTest.kt",
    "chars": 356,
    "preview": "package com.fredrikbogg.android_chat_app\n\nimport org.junit.Test\n\nimport org.junit.Assert.*\n\n/**\n * Example local unit te"
  },
  {
    "path": "build.gradle",
    "chars": 698,
    "preview": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\nbuildscript {\n    ex"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "chars": 233,
    "preview": "#Wed Aug 05 11:42:06 CEST 2020\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER"
  },
  {
    "path": "gradle.properties",
    "chars": 1162,
    "preview": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will ov"
  },
  {
    "path": "gradlew",
    "chars": 5296,
    "preview": "#!/usr/bin/env sh\n\n##############################################################################\n##\n##  Gradle start up"
  },
  {
    "path": "gradlew.bat",
    "chars": 2176,
    "preview": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem "
  },
  {
    "path": "settings.gradle",
    "chars": 52,
    "preview": "include ':app'\nrootProject.name = \"Android-Chat-App\""
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the dgewe/Chat-App-Android GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 118 files (198.1 KB), approximately 51.7k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!