├── .gitignore ├── .idea ├── codeStyles │ └── Project.xml ├── gradle.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── app ├── .gitignore ├── build.gradle ├── google-services.json ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── resocoder │ │ └── firemessage │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── resocoder │ │ │ └── firemessage │ │ │ ├── AppConstants.kt │ │ │ ├── ChatActivity.kt │ │ │ ├── MainActivity.kt │ │ │ ├── SignInActivity.kt │ │ │ ├── SplashActivity.kt │ │ │ ├── fragment │ │ │ ├── MyAccountFragment.kt │ │ │ └── PeopleFragment.kt │ │ │ ├── glide │ │ │ └── FireMessageGlideModule.kt │ │ │ ├── model │ │ │ ├── ChatChannel.kt │ │ │ ├── ImageMessage.kt │ │ │ ├── Message.kt │ │ │ ├── TextMessage.kt │ │ │ └── User.kt │ │ │ ├── recyclerview │ │ │ └── item │ │ │ │ ├── ImageMessageItem.kt │ │ │ │ ├── MessageItem.kt │ │ │ │ ├── PersonItem.kt │ │ │ │ └── TextMessageItem.kt │ │ │ ├── service │ │ │ ├── MyFirebaseInstanceIDService.kt │ │ │ └── MyFirebaseMessagingService.kt │ │ │ └── util │ │ │ ├── FirestoreUtil.kt │ │ │ └── StorageUtil.kt │ └── res │ │ ├── drawable-hdpi │ │ └── ic_fire_emoji.png │ │ ├── drawable-mdpi │ │ └── ic_fire_emoji.png │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable-xhdpi │ │ └── ic_fire_emoji.png │ │ ├── drawable-xxhdpi │ │ └── ic_fire_emoji.png │ │ ├── drawable-xxxhdpi │ │ └── ic_fire_emoji.png │ │ ├── drawable │ │ ├── ic_account_circle_black_24dp.xml │ │ ├── ic_image_black_24dp.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_people_black_24dp.xml │ │ ├── ic_send_black_24dp.xml │ │ ├── rect_oval_white.xml │ │ ├── rect_round_primary_color.xml │ │ ├── rect_round_white.xml │ │ └── splash_screen_background.xml │ │ ├── layout │ │ ├── activity_chat.xml │ │ ├── activity_main.xml │ │ ├── activity_sign_in.xml │ │ ├── fragment_my_account.xml │ │ ├── fragment_people.xml │ │ ├── item_image_message.xml │ │ ├── item_person.xml │ │ └── item_text_message.xml │ │ ├── menu │ │ └── navigation.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── resocoder │ └── firemessage │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | android { 8 | compileSdkVersion 27 9 | defaultConfig { 10 | applicationId "com.resocoder.firemessage" 11 | minSdkVersion 19 12 | targetSdkVersion 27 13 | versionCode 1 14 | versionName "1.0" 15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 16 | vectorDrawables.useSupportLibrary = true 17 | } 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | } 25 | 26 | dependencies { 27 | implementation fileTree(dir: 'libs', include: ['*.jar']) 28 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 29 | implementation 'com.android.support:appcompat-v7:27.1.1' 30 | implementation 'com.android.support:design:27.1.1' 31 | implementation 'com.android.support.constraint:constraint-layout:1.1.0' 32 | implementation 'com.android.support:support-vector-drawable:27.1.1' 33 | implementation 'com.android.support:support-v4:27.1.1' 34 | implementation 'com.android.support:cardview-v7:27.1.1' 35 | implementation 'com.android.support:customtabs:27.1.1' 36 | testImplementation 'junit:junit:4.12' 37 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 38 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 39 | //anko 40 | implementation "org.jetbrains.anko:anko:$anko_version" 41 | implementation "org.jetbrains.anko:anko-design:$anko_version" 42 | implementation "org.jetbrains.anko:anko-coroutines:$anko_version" 43 | //firebase 44 | implementation 'com.google.firebase:firebase-core:15.0.2' 45 | implementation 'com.google.firebase:firebase-firestore:16.0.0' 46 | implementation 'com.google.firebase:firebase-auth:15.1.0' 47 | implementation 'com.firebaseui:firebase-ui-auth:3.3.1' 48 | implementation 'com.google.firebase:firebase-storage:15.0.2' 49 | implementation 'com.firebaseui:firebase-ui-storage:3.3.1' 50 | implementation 'com.google.android.gms:play-services-auth:15.0.1' 51 | implementation 'com.google.firebase:firebase-messaging:15.0.2' 52 | //glide 53 | implementation 'com.github.bumptech.glide:glide:4.6.1' 54 | kapt 'com.github.bumptech.glide:compiler:4.6.1' 55 | //groupie 56 | implementation 'com.xwray:groupie:2.0.3' 57 | implementation 'com.xwray:groupie-kotlin-android-extensions:2.0.3' 58 | 59 | apply plugin: 'com.google.gms.google-services' 60 | //kotlin annotation processor 61 | apply plugin: 'kotlin-kapt' 62 | 63 | kotlin { 64 | experimental { 65 | coroutines "enable" 66 | } 67 | } 68 | 69 | androidExtensions { 70 | experimental = true 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "277485433091", 4 | "firebase_url": "https://firemessage-db177.firebaseio.com", 5 | "project_id": "firemessage-db177", 6 | "storage_bucket": "firemessage-db177.appspot.com" 7 | }, 8 | "client": [ 9 | { 10 | "client_info": { 11 | "mobilesdk_app_id": "1:277485433091:android:ba89bfa487b92988", 12 | "android_client_info": { 13 | "package_name": "com.resocoder.firemessage" 14 | } 15 | }, 16 | "oauth_client": [ 17 | { 18 | "client_id": "277485433091-ijnbcps9nv94spj9p1sk153774rt4432.apps.googleusercontent.com", 19 | "client_type": 3 20 | } 21 | ], 22 | "api_key": [ 23 | { 24 | "current_key": "AIzaSyBx9HueAK6SYyaxXDKULgpecQJuMFTNivM" 25 | } 26 | ], 27 | "services": { 28 | "analytics_service": { 29 | "status": 1 30 | }, 31 | "appinvite_service": { 32 | "status": 1, 33 | "other_platform_oauth_client": [] 34 | }, 35 | "ads_service": { 36 | "status": 2 37 | } 38 | } 39 | } 40 | ], 41 | "configuration_version": "1" 42 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/resocoder/firemessage/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.resocoder.firemessage 2 | 3 | import android.support.test.InstrumentationRegistry 4 | import android.support.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getTargetContext() 22 | assertEquals("com.resocoder.firemessage", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | 27 | 28 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/resocoder/firemessage/AppConstants.kt: -------------------------------------------------------------------------------- 1 | package com.resocoder.firemessage 2 | 3 | 4 | object AppConstants { 5 | const val USER_NAME = "USER_NAME" 6 | const val USER_ID = "USER_ID" 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/resocoder/firemessage/ChatActivity.kt: -------------------------------------------------------------------------------- 1 | package com.resocoder.firemessage 2 | 3 | import android.app.Activity 4 | import android.content.Intent 5 | import android.graphics.Bitmap 6 | import android.support.v7.app.AppCompatActivity 7 | import android.os.Bundle 8 | import android.provider.MediaStore 9 | import android.support.v7.widget.LinearLayoutManager 10 | import com.google.firebase.auth.FirebaseAuth 11 | import com.google.firebase.firestore.ListenerRegistration 12 | import com.resocoder.firemessage.model.ImageMessage 13 | import com.resocoder.firemessage.model.MessageType 14 | import com.resocoder.firemessage.model.TextMessage 15 | import com.resocoder.firemessage.model.User 16 | import com.resocoder.firemessage.util.FirestoreUtil 17 | import com.resocoder.firemessage.util.StorageUtil 18 | import com.xwray.groupie.GroupAdapter 19 | import com.xwray.groupie.Section 20 | import com.xwray.groupie.kotlinandroidextensions.Item 21 | import com.xwray.groupie.kotlinandroidextensions.ViewHolder 22 | import kotlinx.android.synthetic.main.activity_chat.* 23 | import org.jetbrains.anko.startActivityForResult 24 | import org.jetbrains.anko.toast 25 | import java.io.ByteArrayOutputStream 26 | import java.util.* 27 | 28 | private const val RC_SELECT_IMAGE = 2 29 | 30 | class ChatActivity : AppCompatActivity() { 31 | 32 | private lateinit var currentChannelId: String 33 | private lateinit var currentUser: User 34 | private lateinit var otherUserId: String 35 | 36 | private lateinit var messagesListenerRegistration: ListenerRegistration 37 | private var shouldInitRecyclerView = true 38 | private lateinit var messagesSection: Section 39 | 40 | override fun onCreate(savedInstanceState: Bundle?) { 41 | super.onCreate(savedInstanceState) 42 | setContentView(R.layout.activity_chat) 43 | 44 | supportActionBar?.setDisplayHomeAsUpEnabled(true) 45 | supportActionBar?.title = intent.getStringExtra(AppConstants.USER_NAME) 46 | 47 | FirestoreUtil.getCurrentUser { 48 | currentUser = it 49 | } 50 | 51 | otherUserId = intent.getStringExtra(AppConstants.USER_ID) 52 | FirestoreUtil.getOrCreateChatChannel(otherUserId) { channelId -> 53 | currentChannelId = channelId 54 | 55 | messagesListenerRegistration = 56 | FirestoreUtil.addChatMessagesListener(channelId, this, this::updateRecyclerView) 57 | 58 | imageView_send.setOnClickListener { 59 | val messageToSend = 60 | TextMessage(editText_message.text.toString(), Calendar.getInstance().time, 61 | FirebaseAuth.getInstance().currentUser!!.uid, 62 | otherUserId, currentUser.name) 63 | editText_message.setText("") 64 | FirestoreUtil.sendMessage(messageToSend, channelId) 65 | } 66 | 67 | fab_send_image.setOnClickListener { 68 | val intent = Intent().apply { 69 | type = "image/*" 70 | action = Intent.ACTION_GET_CONTENT 71 | putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("image/jpeg", "image/png")) 72 | } 73 | startActivityForResult(Intent.createChooser(intent, "Select Image"), RC_SELECT_IMAGE) 74 | } 75 | } 76 | } 77 | 78 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 79 | if (requestCode == RC_SELECT_IMAGE && resultCode == Activity.RESULT_OK && 80 | data != null && data.data != null) { 81 | val selectedImagePath = data.data 82 | 83 | val selectedImageBmp = MediaStore.Images.Media.getBitmap(contentResolver, selectedImagePath) 84 | 85 | val outputStream = ByteArrayOutputStream() 86 | 87 | selectedImageBmp.compress(Bitmap.CompressFormat.JPEG, 90, outputStream) 88 | val selectedImageBytes = outputStream.toByteArray() 89 | 90 | StorageUtil.uploadMessageImage(selectedImageBytes) { imagePath -> 91 | val messageToSend = 92 | ImageMessage(imagePath, Calendar.getInstance().time, 93 | FirebaseAuth.getInstance().currentUser!!.uid, 94 | otherUserId, currentUser.name) 95 | FirestoreUtil.sendMessage(messageToSend, currentChannelId) 96 | } 97 | } 98 | } 99 | 100 | private fun updateRecyclerView(messages: List) { 101 | fun init() { 102 | recycler_view_messages.apply { 103 | layoutManager = LinearLayoutManager(this@ChatActivity) 104 | adapter = GroupAdapter().apply { 105 | messagesSection = Section(messages) 106 | this.add(messagesSection) 107 | } 108 | } 109 | shouldInitRecyclerView = false 110 | } 111 | 112 | fun updateItems() = messagesSection.update(messages) 113 | 114 | if (shouldInitRecyclerView) 115 | init() 116 | else 117 | updateItems() 118 | 119 | recycler_view_messages.scrollToPosition(recycler_view_messages.adapter.itemCount - 1) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /app/src/main/java/com/resocoder/firemessage/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.resocoder.firemessage 2 | 3 | import android.annotation.SuppressLint 4 | import android.os.Bundle 5 | import android.support.design.widget.BottomNavigationView 6 | import android.support.v4.app.Fragment 7 | import android.support.v7.app.AppCompatActivity 8 | import com.resocoder.firemessage.fragment.MyAccountFragment 9 | import com.resocoder.firemessage.fragment.PeopleFragment 10 | import kotlinx.android.synthetic.main.activity_main.* 11 | 12 | class MainActivity : AppCompatActivity() { 13 | 14 | override fun onCreate(savedInstanceState: Bundle?) { 15 | super.onCreate(savedInstanceState) 16 | setContentView(R.layout.activity_main) 17 | 18 | replaceFragment(PeopleFragment()) 19 | 20 | navigation.setOnNavigationItemSelectedListener { 21 | when (it.itemId) { 22 | R.id.navigation_people -> { 23 | replaceFragment(PeopleFragment()) 24 | true 25 | } 26 | R.id.navigation_my_account -> { 27 | replaceFragment(MyAccountFragment()) 28 | true 29 | } 30 | else -> false 31 | } 32 | } 33 | } 34 | 35 | private fun replaceFragment(fragment: Fragment) { 36 | supportFragmentManager.beginTransaction() 37 | .replace(R.id.fragment_layout, fragment) 38 | .commit() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/resocoder/firemessage/SignInActivity.kt: -------------------------------------------------------------------------------- 1 | package com.resocoder.firemessage 2 | 3 | import android.app.Activity 4 | import android.content.Intent 5 | import android.support.v7.app.AppCompatActivity 6 | import android.os.Bundle 7 | import com.firebase.ui.auth.AuthUI 8 | import com.firebase.ui.auth.ErrorCodes 9 | import com.firebase.ui.auth.IdpResponse 10 | import com.google.firebase.iid.FirebaseInstanceId 11 | import com.resocoder.firemessage.service.MyFirebaseInstanceIDService 12 | import com.resocoder.firemessage.util.FirestoreUtil 13 | import kotlinx.android.synthetic.main.activity_sign_in.* 14 | import org.jetbrains.anko.clearTask 15 | import org.jetbrains.anko.design.longSnackbar 16 | import org.jetbrains.anko.indeterminateProgressDialog 17 | import org.jetbrains.anko.intentFor 18 | import org.jetbrains.anko.newTask 19 | 20 | class SignInActivity : AppCompatActivity() { 21 | 22 | private val RC_SIGN_IN = 1 23 | 24 | private val signInProviders = 25 | listOf(AuthUI.IdpConfig.EmailBuilder() 26 | .setAllowNewAccounts(true) 27 | .setRequireName(true) 28 | .build()) 29 | 30 | override fun onCreate(savedInstanceState: Bundle?) { 31 | super.onCreate(savedInstanceState) 32 | setContentView(R.layout.activity_sign_in) 33 | 34 | account_sign_in.setOnClickListener { 35 | val intent = AuthUI.getInstance().createSignInIntentBuilder() 36 | .setAvailableProviders(signInProviders) 37 | .setLogo(R.drawable.ic_fire_emoji) 38 | .build() 39 | startActivityForResult(intent, RC_SIGN_IN) 40 | } 41 | } 42 | 43 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 44 | super.onActivityResult(requestCode, resultCode, data) 45 | 46 | if (requestCode == RC_SIGN_IN) { 47 | val response = IdpResponse.fromResultIntent(data) 48 | 49 | if (resultCode == Activity.RESULT_OK) { 50 | val progressDialog = indeterminateProgressDialog("Setting up your account") 51 | FirestoreUtil.initCurrentUserIfFirstTime { 52 | startActivity(intentFor().newTask().clearTask()) 53 | 54 | val registrationToken = FirebaseInstanceId.getInstance().token 55 | MyFirebaseInstanceIDService.addTokenToFirestore(registrationToken) 56 | 57 | progressDialog.dismiss() 58 | } 59 | } 60 | else if (resultCode == Activity.RESULT_CANCELED) { 61 | if (response == null) return 62 | 63 | when (response.error?.errorCode) { 64 | ErrorCodes.NO_NETWORK -> 65 | longSnackbar(constraint_layout, "No network") 66 | ErrorCodes.UNKNOWN_ERROR -> 67 | longSnackbar(constraint_layout, "Unknown error") 68 | } 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/java/com/resocoder/firemessage/SplashActivity.kt: -------------------------------------------------------------------------------- 1 | package com.resocoder.firemessage 2 | 3 | import android.support.v7.app.AppCompatActivity 4 | import android.os.Bundle 5 | import com.google.firebase.auth.FirebaseAuth 6 | import org.jetbrains.anko.startActivity 7 | 8 | class SplashActivity : AppCompatActivity() { 9 | 10 | override fun onCreate(savedInstanceState: Bundle?) { 11 | super.onCreate(savedInstanceState) 12 | 13 | if (FirebaseAuth.getInstance().currentUser == null) 14 | startActivity() 15 | else 16 | startActivity() 17 | finish() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/resocoder/firemessage/fragment/MyAccountFragment.kt: -------------------------------------------------------------------------------- 1 | package com.resocoder.firemessage.fragment 2 | 3 | 4 | import android.app.Activity 5 | import android.content.Intent 6 | import android.graphics.Bitmap 7 | import android.os.Bundle 8 | import android.provider.MediaStore 9 | import android.support.v4.app.Fragment 10 | import android.view.LayoutInflater 11 | import android.view.View 12 | import android.view.ViewGroup 13 | import com.firebase.ui.auth.AuthUI 14 | 15 | import com.resocoder.firemessage.R 16 | import com.resocoder.firemessage.SignInActivity 17 | import com.resocoder.firemessage.glide.GlideApp 18 | import com.resocoder.firemessage.util.FirestoreUtil 19 | import com.resocoder.firemessage.util.StorageUtil 20 | import kotlinx.android.synthetic.main.fragment_my_account.* 21 | import kotlinx.android.synthetic.main.fragment_my_account.view.* 22 | import org.jetbrains.anko.clearTask 23 | import org.jetbrains.anko.newTask 24 | import org.jetbrains.anko.support.v4.intentFor 25 | import org.jetbrains.anko.support.v4.toast 26 | import java.io.ByteArrayOutputStream 27 | 28 | 29 | class MyAccountFragment : Fragment() { 30 | 31 | private val RC_SELECT_IMAGE = 2 32 | private lateinit var selectedImageBytes: ByteArray 33 | private var pictureJustChanged = false 34 | 35 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, 36 | savedInstanceState: Bundle?): View? { 37 | val view = inflater.inflate(R.layout.fragment_my_account, container, false) 38 | 39 | view.apply { 40 | imageView_profile_picture.setOnClickListener { 41 | val intent = Intent().apply { 42 | type = "image/*" 43 | action = Intent.ACTION_GET_CONTENT 44 | putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("image/jpeg", "image/png")) 45 | } 46 | startActivityForResult(Intent.createChooser(intent, "Select Image"), RC_SELECT_IMAGE) 47 | } 48 | 49 | btn_save.setOnClickListener { 50 | if (::selectedImageBytes.isInitialized) 51 | StorageUtil.uploadProfilePhoto(selectedImageBytes) { imagePath -> 52 | FirestoreUtil.updateCurrentUser(editText_name.text.toString(), 53 | editText_bio.text.toString(), imagePath) 54 | } 55 | else 56 | FirestoreUtil.updateCurrentUser(editText_name.text.toString(), 57 | editText_bio.text.toString(), null) 58 | toast("Saving") 59 | } 60 | 61 | btn_sign_out.setOnClickListener { 62 | AuthUI.getInstance() 63 | .signOut(this@MyAccountFragment.context!!) 64 | .addOnCompleteListener { 65 | startActivity(intentFor().newTask().clearTask()) 66 | } 67 | } 68 | } 69 | 70 | return view 71 | } 72 | 73 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 74 | if (requestCode == RC_SELECT_IMAGE && resultCode == Activity.RESULT_OK && 75 | data != null && data.data != null) { 76 | val selectedImagePath = data.data 77 | val selectedImageBmp = MediaStore.Images.Media 78 | .getBitmap(activity?.contentResolver, selectedImagePath) 79 | 80 | val outputStream = ByteArrayOutputStream() 81 | selectedImageBmp.compress(Bitmap.CompressFormat.JPEG, 90, outputStream) 82 | selectedImageBytes = outputStream.toByteArray() 83 | 84 | GlideApp.with(this) 85 | .load(selectedImageBytes) 86 | .into(imageView_profile_picture) 87 | 88 | pictureJustChanged = true 89 | } 90 | } 91 | 92 | override fun onStart() { 93 | super.onStart() 94 | FirestoreUtil.getCurrentUser { user -> 95 | if (this@MyAccountFragment.isVisible) { 96 | editText_name.setText(user.name) 97 | editText_bio.setText(user.bio) 98 | if (!pictureJustChanged && user.profilePicturePath != null) 99 | GlideApp.with(this) 100 | .load(StorageUtil.pathToReference(user.profilePicturePath)) 101 | .placeholder(R.drawable.ic_account_circle_black_24dp) 102 | .into(imageView_profile_picture) 103 | } 104 | } 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /app/src/main/java/com/resocoder/firemessage/fragment/PeopleFragment.kt: -------------------------------------------------------------------------------- 1 | package com.resocoder.firemessage.fragment 2 | 3 | 4 | import android.os.Bundle 5 | import android.support.v4.app.Fragment 6 | import android.support.v7.widget.LinearLayoutManager 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import com.google.firebase.firestore.ListenerRegistration 11 | import com.resocoder.firemessage.AppConstants 12 | import com.resocoder.firemessage.ChatActivity 13 | 14 | import com.resocoder.firemessage.R 15 | import com.resocoder.firemessage.recyclerview.item.PersonItem 16 | import com.resocoder.firemessage.util.FirestoreUtil 17 | import com.xwray.groupie.GroupAdapter 18 | import com.xwray.groupie.OnItemClickListener 19 | import com.xwray.groupie.Section 20 | import com.xwray.groupie.kotlinandroidextensions.Item 21 | import com.xwray.groupie.kotlinandroidextensions.ViewHolder 22 | import kotlinx.android.synthetic.main.fragment_people.* 23 | import org.jetbrains.anko.support.v4.startActivity 24 | 25 | 26 | class PeopleFragment : Fragment() { 27 | 28 | private lateinit var userListenerRegistration: ListenerRegistration 29 | 30 | private var shouldInitRecyclerView = true 31 | 32 | private lateinit var peopleSection: Section 33 | 34 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, 35 | savedInstanceState: Bundle?): View? { 36 | 37 | userListenerRegistration = 38 | FirestoreUtil.addUsersListener(this.activity!!, this::updateRecyclerView) 39 | 40 | return inflater.inflate(R.layout.fragment_people, container, false) 41 | } 42 | 43 | override fun onDestroyView() { 44 | super.onDestroyView() 45 | FirestoreUtil.removeListener(userListenerRegistration) 46 | shouldInitRecyclerView = true 47 | } 48 | 49 | private fun updateRecyclerView(items: List) { 50 | 51 | fun init() { 52 | recycler_view_people.apply { 53 | layoutManager = LinearLayoutManager(this@PeopleFragment.context) 54 | adapter = GroupAdapter().apply { 55 | peopleSection = Section(items) 56 | add(peopleSection) 57 | setOnItemClickListener(onItemClick) 58 | } 59 | } 60 | shouldInitRecyclerView = false 61 | } 62 | 63 | fun updateItems() = peopleSection.update(items) 64 | 65 | if (shouldInitRecyclerView) 66 | init() 67 | else 68 | updateItems() 69 | 70 | } 71 | 72 | private val onItemClick = OnItemClickListener { item, view -> 73 | if (item is PersonItem) { 74 | startActivity( 75 | AppConstants.USER_NAME to item.person.name, 76 | AppConstants.USER_ID to item.userId 77 | ) 78 | } 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/com/resocoder/firemessage/glide/FireMessageGlideModule.kt: -------------------------------------------------------------------------------- 1 | package com.resocoder.firemessage.glide 2 | 3 | import android.content.Context 4 | import com.bumptech.glide.Glide 5 | import com.bumptech.glide.Registry 6 | import com.bumptech.glide.annotation.GlideModule 7 | import com.bumptech.glide.module.AppGlideModule 8 | import com.firebase.ui.storage.images.FirebaseImageLoader 9 | import com.google.firebase.storage.StorageReference 10 | import java.io.InputStream 11 | 12 | 13 | @GlideModule 14 | class FireMessageGlideModule: AppGlideModule() { 15 | override fun registerComponents(context: Context, glide: Glide, registry: Registry) { 16 | registry.append(StorageReference::class.java, InputStream::class.java, 17 | FirebaseImageLoader.Factory()) 18 | } 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/resocoder/firemessage/model/ChatChannel.kt: -------------------------------------------------------------------------------- 1 | package com.resocoder.firemessage.model 2 | 3 | 4 | data class ChatChannel(val userIds: MutableList) { 5 | constructor() : this(mutableListOf()) 6 | } -------------------------------------------------------------------------------- /app/src/main/java/com/resocoder/firemessage/model/ImageMessage.kt: -------------------------------------------------------------------------------- 1 | package com.resocoder.firemessage.model 2 | 3 | import java.util.* 4 | 5 | 6 | data class ImageMessage(val imagePath: String, 7 | override val time: Date, 8 | override val senderId: String, 9 | override val recipientId: String, 10 | override val senderName: String, 11 | override val type: String = MessageType.IMAGE) 12 | : Message { 13 | constructor() : this("", Date(0), "", "", "") 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/resocoder/firemessage/model/Message.kt: -------------------------------------------------------------------------------- 1 | package com.resocoder.firemessage.model 2 | 3 | import java.util.* 4 | 5 | 6 | object MessageType { 7 | const val TEXT = "TEXT" 8 | const val IMAGE = "IMAGE" 9 | } 10 | 11 | interface Message { 12 | val time: Date 13 | val senderId: String 14 | val recipientId: String 15 | val senderName: String 16 | val type: String 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/resocoder/firemessage/model/TextMessage.kt: -------------------------------------------------------------------------------- 1 | package com.resocoder.firemessage.model 2 | 3 | import java.util.* 4 | 5 | 6 | data class TextMessage(val text: String, 7 | override val time: Date, 8 | override val senderId: String, 9 | override val recipientId: String, 10 | override val senderName: String, 11 | override val type: String = MessageType.TEXT) 12 | : Message { 13 | constructor() : this("", Date(0), "", "", "") 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/resocoder/firemessage/model/User.kt: -------------------------------------------------------------------------------- 1 | package com.resocoder.firemessage.model 2 | 3 | 4 | data class User(val name: String, 5 | val bio: String, 6 | val profilePicturePath: String?, 7 | val registrationTokens: MutableList) { 8 | constructor(): this("", "", null, mutableListOf()) 9 | } -------------------------------------------------------------------------------- /app/src/main/java/com/resocoder/firemessage/recyclerview/item/ImageMessageItem.kt: -------------------------------------------------------------------------------- 1 | package com.resocoder.firemessage.recyclerview.item 2 | 3 | import android.content.Context 4 | import com.resocoder.firemessage.R 5 | import com.resocoder.firemessage.glide.GlideApp 6 | import com.resocoder.firemessage.model.ImageMessage 7 | import com.resocoder.firemessage.util.StorageUtil 8 | import com.xwray.groupie.kotlinandroidextensions.ViewHolder 9 | import kotlinx.android.synthetic.main.item_image_message.* 10 | 11 | 12 | class ImageMessageItem(val message: ImageMessage, 13 | val context: Context) 14 | : MessageItem(message) { 15 | 16 | override fun bind(viewHolder: ViewHolder, position: Int) { 17 | super.bind(viewHolder, position) 18 | GlideApp.with(context) 19 | .load(StorageUtil.pathToReference(message.imagePath)) 20 | .placeholder(R.drawable.ic_image_black_24dp) 21 | .into(viewHolder.imageView_message_image) 22 | } 23 | 24 | override fun getLayout() = R.layout.item_image_message 25 | 26 | override fun isSameAs(other: com.xwray.groupie.Item<*>?): Boolean { 27 | if (other !is ImageMessageItem) 28 | return false 29 | if (this.message != other.message) 30 | return false 31 | return true 32 | } 33 | 34 | override fun equals(other: Any?): Boolean { 35 | return isSameAs(other as? ImageMessageItem) 36 | } 37 | 38 | override fun hashCode(): Int { 39 | var result = message.hashCode() 40 | result = 31 * result + context.hashCode() 41 | return result 42 | } 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/resocoder/firemessage/recyclerview/item/MessageItem.kt: -------------------------------------------------------------------------------- 1 | package com.resocoder.firemessage.recyclerview.item 2 | 3 | import android.view.Gravity 4 | import android.widget.FrameLayout 5 | import com.google.firebase.auth.FirebaseAuth 6 | import com.resocoder.firemessage.R 7 | import com.resocoder.firemessage.model.Message 8 | import com.xwray.groupie.kotlinandroidextensions.Item 9 | import com.xwray.groupie.kotlinandroidextensions.ViewHolder 10 | import kotlinx.android.synthetic.main.item_text_message.* 11 | import org.jetbrains.anko.backgroundResource 12 | import org.jetbrains.anko.wrapContent 13 | import java.text.SimpleDateFormat 14 | 15 | 16 | abstract class MessageItem(private val message: Message) 17 | : Item() { 18 | 19 | override fun bind(viewHolder: ViewHolder, position: Int) { 20 | setTimeText(viewHolder) 21 | setMessageRootGravity(viewHolder) 22 | } 23 | 24 | private fun setTimeText(viewHolder: ViewHolder) { 25 | val dateFormat = SimpleDateFormat 26 | .getDateTimeInstance(SimpleDateFormat.SHORT, SimpleDateFormat.SHORT) 27 | viewHolder.textView_message_time.text = dateFormat.format(message.time) 28 | } 29 | 30 | private fun setMessageRootGravity(viewHolder: ViewHolder) { 31 | if (message.senderId == FirebaseAuth.getInstance().currentUser?.uid) { 32 | viewHolder.message_root.apply { 33 | backgroundResource = R.drawable.rect_round_white 34 | val lParams = FrameLayout.LayoutParams(wrapContent, wrapContent, Gravity.END) 35 | this.layoutParams = lParams 36 | } 37 | } 38 | else { 39 | viewHolder.message_root.apply { 40 | backgroundResource = R.drawable.rect_round_primary_color 41 | val lParams = FrameLayout.LayoutParams(wrapContent, wrapContent, Gravity.START) 42 | this.layoutParams = lParams 43 | } 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/java/com/resocoder/firemessage/recyclerview/item/PersonItem.kt: -------------------------------------------------------------------------------- 1 | package com.resocoder.firemessage.recyclerview.item 2 | 3 | import android.content.Context 4 | import com.resocoder.firemessage.R 5 | import com.resocoder.firemessage.glide.GlideApp 6 | import com.resocoder.firemessage.model.User 7 | import com.resocoder.firemessage.util.StorageUtil 8 | import com.xwray.groupie.kotlinandroidextensions.Item 9 | import com.xwray.groupie.kotlinandroidextensions.ViewHolder 10 | import kotlinx.android.synthetic.main.item_person.* 11 | 12 | 13 | class PersonItem(val person: User, 14 | val userId: String, 15 | private val context: Context) 16 | : Item() { 17 | 18 | override fun bind(viewHolder: ViewHolder, position: Int) { 19 | viewHolder.textView_name.text = person.name 20 | viewHolder.textView_bio.text = person.bio 21 | if (person.profilePicturePath != null) 22 | GlideApp.with(context) 23 | .load(StorageUtil.pathToReference(person.profilePicturePath)) 24 | .placeholder(R.drawable.ic_account_circle_black_24dp) 25 | .into(viewHolder.imageView_profile_picture) 26 | } 27 | 28 | override fun getLayout() = R.layout.item_person 29 | } -------------------------------------------------------------------------------- /app/src/main/java/com/resocoder/firemessage/recyclerview/item/TextMessageItem.kt: -------------------------------------------------------------------------------- 1 | package com.resocoder.firemessage.recyclerview.item 2 | 3 | import android.content.Context 4 | import com.resocoder.firemessage.R 5 | import com.resocoder.firemessage.model.TextMessage 6 | import com.xwray.groupie.kotlinandroidextensions.ViewHolder 7 | import kotlinx.android.synthetic.main.item_text_message.* 8 | 9 | 10 | class TextMessageItem(val message: TextMessage, 11 | val context: Context) 12 | : MessageItem(message) { 13 | override fun bind(viewHolder: ViewHolder, position: Int) { 14 | viewHolder.textView_message_text.text = message.text 15 | super.bind(viewHolder, position) 16 | } 17 | 18 | override fun getLayout() = R.layout.item_text_message 19 | 20 | override fun isSameAs(other: com.xwray.groupie.Item<*>?): Boolean { 21 | if (other !is TextMessageItem) 22 | return false 23 | if (this.message != other.message) 24 | return false 25 | return true 26 | } 27 | 28 | override fun equals(other: Any?): Boolean { 29 | return isSameAs(other as? TextMessageItem) 30 | } 31 | 32 | override fun hashCode(): Int { 33 | var result = message.hashCode() 34 | result = 31 * result + context.hashCode() 35 | return result 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/resocoder/firemessage/service/MyFirebaseInstanceIDService.kt: -------------------------------------------------------------------------------- 1 | package com.resocoder.firemessage.service 2 | 3 | import com.google.firebase.auth.FirebaseAuth 4 | import com.google.firebase.iid.FirebaseInstanceId 5 | import com.google.firebase.iid.FirebaseInstanceIdService 6 | import com.resocoder.firemessage.util.FirestoreUtil 7 | 8 | 9 | class MyFirebaseInstanceIDService : FirebaseInstanceIdService() { 10 | 11 | override fun onTokenRefresh() { 12 | val newRegistrationToken = FirebaseInstanceId.getInstance().token 13 | 14 | if (FirebaseAuth.getInstance().currentUser != null) 15 | addTokenToFirestore(newRegistrationToken) 16 | } 17 | 18 | companion object { 19 | fun addTokenToFirestore(newRegistrationToken: String?) { 20 | if (newRegistrationToken == null) throw NullPointerException("FCM token is null.") 21 | 22 | FirestoreUtil.getFCMRegistrationTokens { tokens -> 23 | if (tokens.contains(newRegistrationToken)) 24 | return@getFCMRegistrationTokens 25 | 26 | tokens.add(newRegistrationToken) 27 | FirestoreUtil.setFCMRegistrationTokens(tokens) 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/java/com/resocoder/firemessage/service/MyFirebaseMessagingService.kt: -------------------------------------------------------------------------------- 1 | package com.resocoder.firemessage.service 2 | 3 | import android.util.Log 4 | import com.google.firebase.messaging.FirebaseMessagingService 5 | import com.google.firebase.messaging.RemoteMessage 6 | 7 | 8 | class MyFirebaseMessagingService : FirebaseMessagingService() { 9 | 10 | override fun onMessageReceived(remoteMessage: RemoteMessage) { 11 | if (remoteMessage.notification != null) { 12 | //TODO: Show notification if we're not online 13 | Log.d("FCM", remoteMessage.data.toString()) 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/resocoder/firemessage/util/FirestoreUtil.kt: -------------------------------------------------------------------------------- 1 | package com.resocoder.firemessage.util 2 | 3 | import android.content.Context 4 | import android.util.Log 5 | import com.google.firebase.auth.FirebaseAuth 6 | import com.google.firebase.firestore.DocumentReference 7 | import com.google.firebase.firestore.FirebaseFirestore 8 | import com.google.firebase.firestore.ListenerRegistration 9 | import com.resocoder.firemessage.model.* 10 | import com.resocoder.firemessage.recyclerview.item.ImageMessageItem 11 | import com.resocoder.firemessage.recyclerview.item.PersonItem 12 | import com.resocoder.firemessage.recyclerview.item.TextMessageItem 13 | import com.xwray.groupie.kotlinandroidextensions.Item 14 | 15 | 16 | object FirestoreUtil { 17 | private val firestoreInstance: FirebaseFirestore by lazy { FirebaseFirestore.getInstance() } 18 | 19 | private val currentUserDocRef: DocumentReference 20 | get() = firestoreInstance.document("users/${FirebaseAuth.getInstance().currentUser?.uid 21 | ?: throw NullPointerException("UID is null.")}") 22 | 23 | private val chatChannelsCollectionRef = firestoreInstance.collection("chatChannels") 24 | 25 | fun initCurrentUserIfFirstTime(onComplete: () -> Unit) { 26 | currentUserDocRef.get().addOnSuccessListener { documentSnapshot -> 27 | if (!documentSnapshot.exists()) { 28 | val newUser = User(FirebaseAuth.getInstance().currentUser?.displayName ?: "", 29 | "", null, mutableListOf()) 30 | currentUserDocRef.set(newUser).addOnSuccessListener { 31 | onComplete() 32 | } 33 | } 34 | else 35 | onComplete() 36 | } 37 | } 38 | 39 | fun updateCurrentUser(name: String = "", bio: String = "", profilePicturePath: String? = null) { 40 | val userFieldMap = mutableMapOf() 41 | if (name.isNotBlank()) userFieldMap["name"] = name 42 | if (bio.isNotBlank()) userFieldMap["bio"] = bio 43 | if (profilePicturePath != null) 44 | userFieldMap["profilePicturePath"] = profilePicturePath 45 | currentUserDocRef.update(userFieldMap) 46 | } 47 | 48 | fun getCurrentUser(onComplete: (User) -> Unit) { 49 | currentUserDocRef.get() 50 | .addOnSuccessListener { 51 | onComplete(it.toObject(User::class.java)!!) 52 | } 53 | } 54 | 55 | fun addUsersListener(context: Context, onListen: (List) -> Unit): ListenerRegistration { 56 | return firestoreInstance.collection("users") 57 | .addSnapshotListener { querySnapshot, firebaseFirestoreException -> 58 | if (firebaseFirestoreException != null) { 59 | Log.e("FIRESTORE", "Users listener error.", firebaseFirestoreException) 60 | return@addSnapshotListener 61 | } 62 | 63 | val items = mutableListOf() 64 | querySnapshot!!.documents.forEach { 65 | if (it.id != FirebaseAuth.getInstance().currentUser?.uid) 66 | items.add(PersonItem(it.toObject(User::class.java)!!, it.id, context)) 67 | } 68 | onListen(items) 69 | } 70 | } 71 | 72 | fun removeListener(registration: ListenerRegistration) = registration.remove() 73 | 74 | fun getOrCreateChatChannel(otherUserId: String, 75 | onComplete: (channelId: String) -> Unit) { 76 | currentUserDocRef.collection("engagedChatChannels") 77 | .document(otherUserId).get().addOnSuccessListener { 78 | if (it.exists()) { 79 | onComplete(it["channelId"] as String) 80 | return@addOnSuccessListener 81 | } 82 | 83 | val currentUserId = FirebaseAuth.getInstance().currentUser!!.uid 84 | 85 | val newChannel = chatChannelsCollectionRef.document() 86 | newChannel.set(ChatChannel(mutableListOf(currentUserId, otherUserId))) 87 | 88 | currentUserDocRef 89 | .collection("engagedChatChannels") 90 | .document(otherUserId) 91 | .set(mapOf("channelId" to newChannel.id)) 92 | 93 | firestoreInstance.collection("users").document(otherUserId) 94 | .collection("engagedChatChannels") 95 | .document(currentUserId) 96 | .set(mapOf("channelId" to newChannel.id)) 97 | 98 | onComplete(newChannel.id) 99 | } 100 | } 101 | 102 | fun addChatMessagesListener(channelId: String, context: Context, 103 | onListen: (List) -> Unit): ListenerRegistration { 104 | return chatChannelsCollectionRef.document(channelId).collection("messages") 105 | .orderBy("time") 106 | .addSnapshotListener { querySnapshot, firebaseFirestoreException -> 107 | if (firebaseFirestoreException != null) { 108 | Log.e("FIRESTORE", "ChatMessagesListener error.", firebaseFirestoreException) 109 | return@addSnapshotListener 110 | } 111 | 112 | val items = mutableListOf() 113 | querySnapshot!!.documents.forEach { 114 | if (it["type"] == MessageType.TEXT) 115 | items.add(TextMessageItem(it.toObject(TextMessage::class.java)!!, context)) 116 | else 117 | items.add(ImageMessageItem(it.toObject(ImageMessage::class.java)!!, context)) 118 | return@forEach 119 | } 120 | onListen(items) 121 | } 122 | } 123 | 124 | fun sendMessage(message: Message, channelId: String) { 125 | chatChannelsCollectionRef.document(channelId) 126 | .collection("messages") 127 | .add(message) 128 | } 129 | 130 | //region FCM 131 | fun getFCMRegistrationTokens(onComplete: (tokens: MutableList) -> Unit) { 132 | currentUserDocRef.get().addOnSuccessListener { 133 | val user = it.toObject(User::class.java)!! 134 | onComplete(user.registrationTokens) 135 | } 136 | } 137 | 138 | fun setFCMRegistrationTokens(registrationTokens: MutableList) { 139 | currentUserDocRef.update(mapOf("registrationTokens" to registrationTokens)) 140 | } 141 | //endregion FCM 142 | } -------------------------------------------------------------------------------- /app/src/main/java/com/resocoder/firemessage/util/StorageUtil.kt: -------------------------------------------------------------------------------- 1 | package com.resocoder.firemessage.util 2 | 3 | import com.google.firebase.auth.FirebaseAuth 4 | import com.google.firebase.storage.FirebaseStorage 5 | import com.google.firebase.storage.StorageReference 6 | import java.util.* 7 | 8 | 9 | object StorageUtil { 10 | private val storageInstance: FirebaseStorage by lazy { FirebaseStorage.getInstance() } 11 | 12 | private val currentUserRef: StorageReference 13 | get() = storageInstance.reference 14 | .child(FirebaseAuth.getInstance().currentUser?.uid 15 | ?: throw NullPointerException("UID is null.")) 16 | 17 | fun uploadProfilePhoto(imageBytes: ByteArray, 18 | onSuccess: (imagePath: String) -> Unit) { 19 | val ref = currentUserRef.child("profilePictures/${UUID.nameUUIDFromBytes(imageBytes)}") 20 | ref.putBytes(imageBytes) 21 | .addOnSuccessListener { 22 | onSuccess(ref.path) 23 | } 24 | } 25 | 26 | fun uploadMessageImage(imageBytes: ByteArray, 27 | onSuccess: (imagePath: String) -> Unit) { 28 | val ref = currentUserRef.child("messages/${UUID.nameUUIDFromBytes(imageBytes)}") 29 | ref.putBytes(imageBytes) 30 | .addOnSuccessListener { 31 | onSuccess(ref.path) 32 | } 33 | } 34 | 35 | fun pathToReference(path: String) = storageInstance.getReference(path) 36 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_fire_emoji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResoCoder/firebase-firestore-chat-app/6b0de828ee606e9e3eb772e53c89a1bb1c002002/app/src/main/res/drawable-hdpi/ic_fire_emoji.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_fire_emoji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResoCoder/firebase-firestore-chat-app/6b0de828ee606e9e3eb772e53c89a1bb1c002002/app/src/main/res/drawable-mdpi/ic_fire_emoji.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_fire_emoji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResoCoder/firebase-firestore-chat-app/6b0de828ee606e9e3eb772e53c89a1bb1c002002/app/src/main/res/drawable-xhdpi/ic_fire_emoji.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_fire_emoji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResoCoder/firebase-firestore-chat-app/6b0de828ee606e9e3eb772e53c89a1bb1c002002/app/src/main/res/drawable-xxhdpi/ic_fire_emoji.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_fire_emoji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResoCoder/firebase-firestore-chat-app/6b0de828ee606e9e3eb772e53c89a1bb1c002002/app/src/main/res/drawable-xxxhdpi/ic_fire_emoji.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_account_circle_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_image_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_people_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_send_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/rect_oval_white.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/rect_round_primary_color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/rect_round_white.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/splash_screen_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_chat.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 18 | 19 | 29 | 30 | 40 | 41 | 52 | 53 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 20 | 21 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_sign_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 24 | 25 |