1.Required Permission:-
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
2. Splash Screen
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/main"
android:background="@drawable/login_gradient_background"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.SplashScreen">
<ImageView
android:id="@+id/splashLogo"
android:layout_width="180dp"
android:layout_height="180dp"
android:src="@drawable/zegomeet"
android:contentDescription="App Logo"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:scaleType="fitCenter" />
<TextView
android:id="@+id/appName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ZegoMeet"
android:textColor="@color/white"
android:textSize="32sp"
android:fontFamily="@font/poppins_bold"
android:layout_marginTop="24dp"
app:layout_constraintTop_toBottomOf="@id/splashLogo"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/appTagline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Connect. Collaborate. Communicate."
android:textColor="#E0E0E0"
android:textSize="16sp"
android:fontFamily="@font/poppins"
android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="@id/appName"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Kotlin:-
package com.example.zegomeet.view
import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.example.zegomeet.R
import com.google.firebase.auth.FirebaseAuth
class SplashScreen : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_splash_screen)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
Handler(Looper.getMainLooper()).postDelayed({
navigateToNextScreen()
}, 3000) // 2-second delay
}
private fun navigateToNextScreen(){
if (FirebaseAuth.getInstance().currentUser != null) {
startActivity(Intent(this, MainActivity::class.java))
finish()
}
else{
startActivity(Intent(this, Login::class.java))
finish()
}
}
}
3.Login
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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/login_gradient_background"
android:padding="32dp">
<ImageView
android:id="@+id/appLogo"
android:layout_width="120dp"
android:layout_height="120dp"
android:src="@drawable/login"
android:contentDescription="App Logo"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="48dp"/>
<TextView
android:id="@+id/loginTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:fontFamily="@font/poppins_bold"
android:text="Welcome Back"
android:textColor="@color/white"
android:textSize="32sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/appLogo" />
<TextView
android:id="@+id/loginSubtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:fontFamily="@font/poppins"
android:text="Sign in to continue"
android:textColor="#E0E0E0"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/loginTitle" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/emailInputLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
app:layout_constraintTop_toBottomOf="@id/loginSubtitle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
app:startIconDrawable="@android:drawable/sym_action_email"
app:startIconTint="@color/white"
app:hintTextColor="@color/white"
app:boxStrokeColor="@color/white"
android:textColorHint="#E0E0E0">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/loginEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Email Address"
android:inputType="textEmailAddress"
android:textColor="@color/white"
android:fontFamily="@font/poppins"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/passwordInputLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@id/emailInputLayout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
app:startIconDrawable="@android:drawable/ic_lock_lock"
app:startIconTint="@color/white"
app:hintTextColor="@color/white"
app:boxStrokeColor="@color/white"
app:passwordToggleEnabled="true"
app:passwordToggleTint="@color/white"
android:textColorHint="#E0E0E0">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/loginPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Password"
android:inputType="textPassword"
android:textColor="@color/white"
android:fontFamily="@font/poppins"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/loginBtn"
android:layout_width="0dp"
android:layout_height="56dp"
android:layout_marginTop="32dp"
android:backgroundTint="@color/white"
android:fontFamily="@font/poppins_medium"
android:letterSpacing="0.05"
android:text="Sign In"
android:textColor="#1E1E1E"
android:textSize="16sp"
app:cornerRadius="28dp"
app:elevation="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/passwordInputLayout" />
<ProgressBar
android:id="@+id/loginProgressBar"
android:layout_width="24dp"
android:layout_height="24dp"
android:visibility="gone"
android:indeterminateTint="@color/white"
app:layout_constraintTop_toBottomOf="@id/loginBtn"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="16dp"/>
<TextView
android:id="@+id/goToSignup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Don't have an account? Sign Up"
android:textColor="@color/white"
android:textSize="14sp"
android:fontFamily="@font/poppins"
app:layout_constraintTop_toBottomOf="@id/loginProgressBar"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="16dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Kotlin:-
package com.example.zegomeet.view
import android.content.Intent
import android.os.Bundle
import android.util.Patterns
import android.view.View
import android.widget.Button
import android.widget.EditText
import android.widget.ProgressBar
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.example.zegomeet.R
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.firestore.FirebaseFirestore
class Login : AppCompatActivity() {
private lateinit var auth: FirebaseAuth
private lateinit var firestore: FirebaseFirestore
private lateinit var emailInput: EditText
private lateinit var passwordInput: EditText
private lateinit var loginBtn: Button
private lateinit var goToSignup: TextView
private lateinit var progressBar: ProgressBar
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
auth = FirebaseAuth.getInstance()
firestore = FirebaseFirestore.getInstance()
emailInput = findViewById(R.id.loginEmail)
passwordInput = findViewById(R.id.loginPassword)
loginBtn = findViewById(R.id.loginBtn)
goToSignup = findViewById(R.id.goToSignup)
progressBar = findViewById(R.id.loginProgressBar)
loginBtn.setOnClickListener {
if (validateInputs()) {
performLogin()
}
}
goToSignup.setOnClickListener {
startActivity(Intent(this, Signup::class.java))
finish()
}
}
private fun validateInputs(): Boolean {
val email = emailInput.text.toString().trim()
val password = passwordInput.text.toString().trim()
if (email.isEmpty()) {
emailInput.error = "Email is required"
emailInput.requestFocus()
return false
}
if (!Patterns.EMAIL_ADDRESS.matcher(email).matches()) {
emailInput.error = "Please enter a valid email"
emailInput.requestFocus()
return false
}
if (password.isEmpty()) {
passwordInput.error = "Password is required"
passwordInput.requestFocus()
return false
}
if (password.length < 6) {
passwordInput.error = "Password must be at least 6 characters"
passwordInput.requestFocus()
return false
}
return true
}
private fun performLogin() {
val email = emailInput.text.toString().trim()
val password = passwordInput.text.toString().trim()
setLoading(true)
auth.signInWithEmailAndPassword(email, password)
.addOnSuccessListener {
val uid = auth.currentUser!!.uid
firestore.collection("users").document(uid)
.get()
.addOnSuccessListener { document ->
setLoading(false)
if (document.exists()) {
Toast.makeText(this, "Welcome back!", Toast.LENGTH_SHORT).show()
startActivity(Intent(this, MainActivity::class.java))
finish()
} else {
Toast.makeText(this, "User data not found", Toast.LENGTH_SHORT).show()
}
}
.addOnFailureListener { e ->
setLoading(false)
Toast.makeText(this, "Error: ${e.message}", Toast.LENGTH_SHORT).show()
}
}
.addOnFailureListener { e ->
setLoading(false)
when {
e.message?.contains("password") == true -> {
passwordInput.error = "Incorrect password"
passwordInput.requestFocus()
}
e.message?.contains("user") == true -> {
emailInput.error = "No account found with this email"
emailInput.requestFocus()
}
else -> Toast.makeText(this, "Login failed: ${e.message}", Toast.LENGTH_SHORT).show()
}
}
}
private fun setLoading(isLoading: Boolean) {
progressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
loginBtn.isEnabled = !isLoading
emailInput.isEnabled = !isLoading
passwordInput.isEnabled = !isLoading
goToSignup.isEnabled = !isLoading
}
}
4.Signup:-
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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/login_gradient_background"
android:padding="32dp">
<ImageView
android:id="@+id/appLogo"
android:layout_width="120dp"
android:layout_height="120dp"
android:src="@drawable/login"
android:contentDescription="App Logo"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="48dp"/>
<TextView
android:id="@+id/signupTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Create Account"
android:textSize="32sp"
android:textStyle="bold"
android:textColor="@color/white"
android:fontFamily="@font/poppins_bold"
app:layout_constraintTop_toBottomOf="@id/appLogo"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="24dp"/>
<TextView
android:id="@+id/signupSubtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Sign up to get started"
android:textSize="16sp"
android:textColor="#E0E0E0"
android:fontFamily="@font/poppins"
app:layout_constraintTop_toBottomOf="@id/signupTitle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="8dp"/>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/nameInputLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
app:layout_constraintTop_toBottomOf="@id/signupSubtitle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
app:startIconDrawable="@android:drawable/ic_menu_myplaces"
app:startIconTint="@color/white"
app:hintTextColor="@color/white"
app:boxStrokeColor="@color/white"
android:textColorHint="#E0E0E0">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/signupName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Full Name"
android:inputType="textPersonName"
android:textColor="@color/white"
android:fontFamily="@font/poppins"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/emailInputLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@id/nameInputLayout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
app:startIconDrawable="@android:drawable/sym_action_email"
app:startIconTint="@color/white"
app:hintTextColor="@color/white"
app:boxStrokeColor="@color/white"
android:textColorHint="#E0E0E0">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/signupEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Email Address"
android:inputType="textEmailAddress"
android:textColor="@color/white"
android:fontFamily="@font/poppins"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/passwordInputLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@id/emailInputLayout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
app:startIconDrawable="@android:drawable/ic_lock_lock"
app:startIconTint="@color/white"
app:hintTextColor="@color/white"
app:boxStrokeColor="@color/white"
app:passwordToggleEnabled="true"
app:passwordToggleTint="@color/white"
android:textColorHint="#E0E0E0">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/signupPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Password"
android:inputType="textPassword"
android:textColor="@color/white"
android:fontFamily="@font/poppins"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/signupBtn"
android:layout_width="0dp"
android:layout_height="56dp"
android:text="Sign Up"
android:textColor="#1E1E1E"
android:textSize="16sp"
android:backgroundTint="@color/white"
android:fontFamily="@font/poppins_medium"
android:letterSpacing="0.05"
app:cornerRadius="28dp"
app:elevation="4dp"
app:layout_constraintTop_toBottomOf="@id/passwordInputLayout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="32dp"/>
<TextView
android:id="@+id/goToLogin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Already have an account? Log In"
android:textColor="@color/white"
android:textSize="14sp"
android:fontFamily="@font/poppins"
app:layout_constraintTop_toBottomOf="@id/signupBtn"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="24dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Kotlin:-
package com.example.zegomeet.view
import android.content.Intent
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.example.zegomeet.R
import com.google.firebase.Timestamp
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.firestore.FirebaseFirestore
import java.util.UUID
class Signup : AppCompatActivity() {
private lateinit var auth: FirebaseAuth
private lateinit var firestore: FirebaseFirestore
private lateinit var nameInput: EditText
private lateinit var emailInput: EditText
private lateinit var passwordInput: EditText
private lateinit var signupBtn: Button
private lateinit var goToLogin: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_signup)
auth = FirebaseAuth.getInstance()
firestore = FirebaseFirestore.getInstance()
nameInput = findViewById(R.id.signupName)
emailInput = findViewById(R.id.signupEmail)
passwordInput = findViewById(R.id.signupPassword)
signupBtn = findViewById(R.id.signupBtn)
goToLogin = findViewById(R.id.goToLogin)
signupBtn.setOnClickListener {
val name = nameInput.text.toString().trim()
val email = emailInput.text.toString().trim()
val password = passwordInput.text.toString().trim()
if (name.isEmpty() || email.isEmpty() || password.length < 6) {
Toast.makeText(this, "Fill all fields (Password ≥ 6)", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
auth.createUserWithEmailAndPassword(email, password)
.addOnSuccessListener {
val uid = auth.currentUser!!.uid
val userID = UUID.randomUUID().toString()
val userMap = hashMapOf(
"uid" to uid,
"name" to name,
"email" to email,
"userID" to userID,
"createdAt" to Timestamp.now()
)
firestore.collection("users").document(uid)
.set(userMap)
.addOnSuccessListener {
Toast.makeText(this, "Signup Successful", Toast.LENGTH_SHORT).show()
startActivity(Intent(this, UploadProfilePicture::class.java))
finish()
}
}
.addOnFailureListener {
Toast.makeText(this, "Signup Failed: ${it.message}", Toast.LENGTH_SHORT).show()
}
}
goToLogin.setOnClickListener {
startActivity(Intent(this, Login::class.java))
finish()
}
}
}
5.Upload profile picture:-
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"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:background="@drawable/login_gradient_background"
android:padding="32dp"
tools:context=".view.UploadProfilePicture">
<TextView
android:id="@+id/profileTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="48dp"
android:fontFamily="@font/poppins_bold"
android:text="Add Profile Picture"
android:textColor="@color/white"
android:textSize="32sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/profileSubtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:fontFamily="@font/poppins"
android:text="Help others recognize you"
android:textColor="#E0E0E0"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/profileTitle" />
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/profileImageView"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_marginTop="48dp"
android:background="#33FFFFFF"
android:padding="2dp"
android:scaleType="centerCrop"
android:src="@drawable/login"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/profileSubtitle"
app:shapeAppearanceOverlay="@style/CircleImageView" />
<com.google.android.material.button.MaterialButton
android:id="@+id/uploadButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:backgroundTint="@color/white"
android:fontFamily="@font/poppins_medium"
android:text="Choose Photo"
android:textColor="#1E1E1E"
app:cornerRadius="20dp"
app:icon="@android:drawable/ic_menu_camera"
app:iconGravity="textStart"
app:iconTint="#1E1E1E"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/profileImageView" />
<com.google.android.material.button.MaterialButton
android:id="@+id/continueButton"
android:layout_width="0dp"
android:layout_height="56dp"
android:layout_marginTop="32dp"
android:backgroundTint="@color/white"
android:enabled="false"
android:fontFamily="@font/poppins_medium"
android:letterSpacing="0.05"
android:text="Continue"
android:textColor="#1E1E1E"
android:textSize="16sp"
app:cornerRadius="28dp"
app:elevation="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/uploadButton" />
<TextView
android:id="@+id/skipButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:fontFamily="@font/poppins"
android:text="Skip for now"
android:textColor="#E0E0E0"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/continueButton" />
</androidx.constraintlayout.widget.ConstraintLayout>
KOTLIN:-
package com.example.zegomeet.view
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.provider.OpenableColumns
import android.widget.*
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import com.example.zegomeet.R
import com.example.zegomeet.api.ImgbbApi
import com.example.zegomeet.model.ImgbbResponse
import com.google.android.material.button.MaterialButton
import com.google.android.material.imageview.ShapeableImageView
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.firestore.FirebaseFirestore
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody.Companion.asRequestBody
import retrofit2.*
import retrofit2.converter.gson.GsonConverterFactory
import java.io.*
class UploadProfilePicture : AppCompatActivity() {
private lateinit var imageView: ShapeableImageView
private lateinit var uploadBtn: MaterialButton
private lateinit var continueBtn: MaterialButton
private lateinit var skipBtn: TextView
private var selectedImageUri: Uri? = null
private val apiKey = "" // Replace this with your actual key
private val auth = FirebaseAuth.getInstance()
private val firestore = FirebaseFirestore.getInstance()
private val imagePickLauncher = registerForActivityResult(
ActivityResultContracts.GetContent()
) { uri ->
if (uri != null) {
selectedImageUri = uri
imageView.setImageURI(uri)
continueBtn.isEnabled = true
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_profile_picture_upload)
imageView = findViewById(R.id.profileImageView)
uploadBtn = findViewById(R.id.uploadButton)
continueBtn = findViewById(R.id.continueButton)
skipBtn = findViewById(R.id.skipButton)
uploadBtn.setOnClickListener {
imagePickLauncher.launch("image/*")
}
continueBtn.setOnClickListener {
selectedImageUri?.let { uri ->
uploadToImgBB(uri)
}
}
skipBtn.setOnClickListener {
goToHomeScreen()
}
}
private fun uploadToImgBB(imageUri: Uri) {
val file = getFileFromUri(imageUri) ?: return
val requestFile = file.asRequestBody("image/*".toMediaTypeOrNull())
val imagePart = MultipartBody.Part.createFormData("image", file.name, requestFile)
val retrofit = Retrofit.Builder()
.baseUrl("https://api.imgbb.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val api = retrofit.create(ImgbbApi::class.java)
api.uploadImage(apiKey, imagePart).enqueue(object : retrofit2.Callback<ImgbbResponse> {
override fun onResponse(
call: retrofit2.Call<ImgbbResponse>,
response: retrofit2.Response<ImgbbResponse>
) {
if (response.isSuccessful) {
val imageUrl = response.body()?.data?.url
imageUrl?.let { saveImageUrlToFirestore(it) }
} else {
Toast.makeText(this@UploadProfilePicture, "Upload failed", Toast.LENGTH_SHORT)
.show()
}
}
override fun onFailure(
call: retrofit2.Call<ImgbbResponse>,
t: Throwable
) {
Toast.makeText(this@UploadProfilePicture, "Error: ${t.message}", Toast.LENGTH_SHORT)
.show()
}
})
}
private fun saveImageUrlToFirestore(imageUrl: String) {
val uid = auth.currentUser?.uid ?: return
firestore.collection("users").document(uid)
.update("profileImage", imageUrl)
.addOnSuccessListener {
Toast.makeText(this, "Profile updated!", Toast.LENGTH_SHORT).show()
goToHomeScreen()
}
.addOnFailureListener {
Toast.makeText(this, "Failed to update profile", Toast.LENGTH_SHORT).show()
}
}
private fun getFileFromUri(uri: Uri): File? {
val returnCursor = contentResolver.query(uri, null, null, null, null)
val nameIndex = returnCursor?.getColumnIndex(OpenableColumns.DISPLAY_NAME) ?: -1
returnCursor?.moveToFirst()
val fileName = if (nameIndex >= 0 && returnCursor != null) {
returnCursor.getString(nameIndex)
} else {
"temp_file.jpg"
}
returnCursor?.close()
val inputStream = contentResolver.openInputStream(uri) ?: return null
val tempFile = File.createTempFile(fileName, null, cacheDir)
val outputStream = FileOutputStream(tempFile)
inputStream.copyTo(outputStream)
inputStream.close()
outputStream.close()
return tempFile
}
private fun goToHomeScreen() {
startActivity(Intent(this, MainActivity::class.java))
finish()
}
}
6. Main Activity Screen:-
XML:-
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
app:elevation="0dp">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/topAppBar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:background="@color/white"
app:layout_scrollFlags="scroll|enterAlways|snap">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/userAvatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:src="@drawable/login"
app:shapeAppearanceOverlay="@style/CircleImageView"/>
<TextView
android:id="@+id/homeWelcome"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:fontFamily="@font/roboto_medium"
android:text="Hello, User"
android:textColor="#1E1E1E"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/logoutIcon"
app:layout_constraintStart_toEndOf="@id/userAvatar"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/logoutIcon"
android:layout_width="48dp"
android:layout_height="48dp"
app:icon="@drawable/logout"
app:iconTint="#1E1E1E"
android:contentDescription="Logout"
style="@style/Widget.Material3.Button.IconButton"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.MaterialToolbar>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="28dp"
app:cardElevation="0dp"
android:layout_marginBottom="16dp"
app:strokeWidth="1dp"
app:strokeColor="#E0E0E0">
<androidx.appcompat.widget.SearchView
android:id="@+id/searchView"
android:layout_width="match_parent"
android:layout_height="56dp"
android:iconifiedByDefault="false"
android:queryHint="Search users..."
android:theme="@style/SearchViewStyle"/>
</com.google.android.material.card.MaterialCardView>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/usersRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/joinwithcode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:text="Join with code"
app:icon="@android:drawable/presence_video_online"
app:iconTint="@color/white"
android:textColor="#FFFFFF"
app:backgroundTint="@color/whatsapp_green"
app:elevation="6dp"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
KOLTIN:-
package com.example.zegomeet.view
import android.Manifest
import android.app.Application
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SearchView
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.zegomeet.R
import com.example.zegomeet.adapter.UserAdapter
import com.example.zegomeet.model.User
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.firestore.FirebaseFirestore
import com.permissionx.guolindev.PermissionX
import com.zegocloud.uikit.prebuilt.call.ZegoUIKitPrebuiltCallConfig
import com.zegocloud.uikit.prebuilt.call.ZegoUIKitPrebuiltCallFragment
import com.zegocloud.uikit.prebuilt.call.invite.ZegoUIKitPrebuiltCallInvitationConfig
import com.zegocloud.uikit.prebuilt.call.ZegoUIKitPrebuiltCallService
class MainActivity : AppCompatActivity() {
private lateinit var welcomeText: TextView
private lateinit var usersRecyclerView: RecyclerView
private lateinit var createCallFab: ExtendedFloatingActionButton
private lateinit var auth: FirebaseAuth
private lateinit var firestore: FirebaseFirestore
private var userId: String = ""
private var userName: String = ""
private val users = mutableListOf<User>()
private lateinit var fullUserList: MutableList<User>
private val appID: Long = 1485484311
private val appSign: String = "d0cc616a3c7f87dd605d27b2827a5c19ab84c5a4805c92bc11d531f3b1673b9c"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Request SYSTEM_ALERT_WINDOW (if needed for floating call)
PermissionX.init(this).permissions(Manifest.permission.SYSTEM_ALERT_WINDOW).request { _, _, _ -> }
// Initialize Firebase
auth = FirebaseAuth.getInstance()
firestore = FirebaseFirestore.getInstance()
// Bind views
welcomeText = findViewById(R.id.homeWelcome)
usersRecyclerView = findViewById(R.id.usersRecyclerView)
createCallFab = findViewById(R.id.joinwithcode)
// Set up RecyclerView
usersRecyclerView.layoutManager = LinearLayoutManager(this)
usersRecyclerView.adapter = UserAdapter(users) { user ->
startVideoCall(user.userID)
}
// Search view setup
val searchView: SearchView = findViewById(R.id.searchView)
fullUserList = mutableListOf()
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean = false
override fun onQueryTextChange(newText: String?): Boolean {
filterUsers(newText.orEmpty())
return true
}
})
// Load current user and initialize Zego call service
val uid = auth.currentUser?.uid ?: return
firestore.collection("users").document(uid).get().addOnSuccessListener { document ->
if (document.exists()) {
userName = document.getString("name") ?: "User"
userId = document.getString("userID") ?: uid
welcomeText.text = "Hello, $userName"
// Initialize Zego call invitation service
val config = ZegoUIKitPrebuiltCallInvitationConfig()
ZegoUIKitPrebuiltCallService.init(
applicationContext as Application?,
appID,
appSign,
userId,
userName,
config
)
loadUsers()
}
}
// Floating button starts new call
createCallFab.setOnClickListener {
startVideoCall(System.currentTimeMillis().toString())
}
}
override fun onDestroy() {
super.onDestroy()
ZegoUIKitPrebuiltCallService.unInit()
}
private fun checkAndRequestPermissions(): Boolean {
val neededPermissions = mutableListOf<String>()
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
neededPermissions.add(Manifest.permission.CAMERA)
}
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
neededPermissions.add(Manifest.permission.RECORD_AUDIO)
}
return if (neededPermissions.isNotEmpty()) {
ActivityCompat.requestPermissions(this, neededPermissions.toTypedArray(), 1001)
false
} else true
}
private fun loadUsers() {
firestore.collection("users").get().addOnSuccessListener { documents ->
users.clear()
for (document in documents) {
val user = User(
userID = document.getString("userID") ?: document.id,
name = document.getString("name") ?: "Unknown User",
profileImage = document.getString("profileImage") ?: ""
)
if (user.userID != userId) {
users.add(user)
}
}
fullUserList.clear()
fullUserList.addAll(users)
usersRecyclerView.adapter?.notifyDataSetChanged()
}.addOnFailureListener {
Toast.makeText(this, "Failed to load users", Toast.LENGTH_SHORT).show()
}
}
private fun startVideoCall(callID: String) {
if (checkAndRequestPermissions()) {
val config = ZegoUIKitPrebuiltCallConfig.oneOnOneVideoCall()
val intent = Intent(this, VideoCallActivity::class.java)
intent.putExtra("call_id", callID)
intent.putExtra("user_id", userId)
intent.putExtra("user_name", userName)
startActivity(intent)
}
}
private fun filterUsers(query: String) {
val filteredList = fullUserList.filter {
it.name.contains(query, ignoreCase = true)
}
users.clear()
users.addAll(filteredList)
usersRecyclerView.adapter?.notifyDataSetChanged()
}
}
7. Video Call Activity:-
XML:-
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/call_fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000">
</FrameLayout>
Kotlin:-
package com.example.zegomeet.view
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.zegomeet.R
import com.zegocloud.uikit.prebuilt.call.ZegoUIKitPrebuiltCallConfig
import com.zegocloud.uikit.prebuilt.call.ZegoUIKitPrebuiltCallFragment
class VideoCallActivity : AppCompatActivity() {
private val appID: Long = // ๐ Replace with your actual App ID
private val appSign: String = "" // ๐ Replace with your actual App Sign
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_video_call)
val userID = intent.getStringExtra("user_id") ?: return
val userName = intent.getStringExtra("user_name") ?: "User"
val callID = intent.getStringExtra("call_id") ?: return
val config = ZegoUIKitPrebuiltCallConfig.oneOnOneVideoCall()
val callFragment = ZegoUIKitPrebuiltCallFragment.newInstance(
appID, appSign, userID, userName, callID, config
)
supportFragmentManager.beginTransaction()
.replace(R.id.call_fragment_container, callFragment)
.commit()
}
}
8- User Data Class:-
package com.example.zegomeet.model
data class User(
val uid: String = "",
val name: String = "",
val email: String = "",
val userID: String = "",
val profileImage: String? = null
)
9-ImgDb Response:-
package com.example.zegomeet.model
data class ImgbbResponse(
val data: ImgbbData
)
data class ImgbbData(
val url: String
)
10 - ImgDbApi Interface
package com.example.zegomeet.api
import com.example.zegomeet.model.ImgbbResponse
import okhttp3.MultipartBody
import retrofit2.Call
import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.Part
import retrofit2.http.Query
interface ImgbbApi {
@Multipart
@POST("1/upload")
fun uploadImage(
@Query("key") apiKey: String,
@Part image: MultipartBody.Part
): Call<ImgbbResponse>
}
11- UserAdapter:-
package com.example.zegomeet.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.example.zegomeet.R
import com.example.zegomeet.model.User
import com.google.android.material.button.MaterialButton
import com.google.android.material.imageview.ShapeableImageView
class UserAdapter(private val users: List<User>, private val onCallClick: (User) -> Unit) :
RecyclerView.Adapter<UserAdapter.UserViewHolder>() {
class UserViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val profileImage: ShapeableImageView = view.findViewById(R.id.userProfileImage)
val userName: TextView = view.findViewById(R.id.userName)
val callButton: MaterialButton = view.findViewById(R.id.callButton)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_user, parent, false)
return UserViewHolder(view)
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
val user = users[position]
holder.userName.text = user.name
// ✅ Load profile image using Glide
Glide.with(holder.profileImage.context)
.load(user.profileImage)
.placeholder(R.drawable.login) // fallback
.into(holder.profileImage)
// ✅ Handle call button click
holder.callButton.setOnClickListener { onCallClick(user) }
}
override fun getItemCount() = users.size
}
12- Item_user.xml:-
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:layout_marginVertical="4dp"
app:cardCornerRadius="12dp"
app:cardElevation="2dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/userProfileImage"
android:layout_width="48dp"
android:layout_height="48dp"
android:scaleType="centerCrop"
android:src="@drawable/login"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:shapeAppearanceOverlay="@style/CircleImageView" />
<TextView
android:id="@+id/userName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:textSize="16sp"
android:textColor="#1E1E1E"
android:textStyle="bold"
android:fontFamily="@font/poppins"
app:layout_constraintStart_toEndOf="@id/userProfileImage"
app:layout_constraintEnd_toStartOf="@id/callButton"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/callButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Call"
android:textColor="#FFFFFF"
app:icon="@android:drawable/ic_menu_call"
app:iconTint="#FFFFFF"
app:cornerRadius="20dp"
android:minWidth="0dp"
android:minHeight="0dp"
android:paddingHorizontal="16dp"
android:paddingVertical="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
13- Login_gradient_background:-
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:type="linear"
android:angle="270"
android:startColor="#FF2B1B6B"
android:centerColor="#FF3B2B8C"
android:endColor="#FF4B3BAD"
android:centerY="0.5" />
</shape>
14-Logout.xml:-
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M480,840q-75,0 -140.5,-28.5t-114,-77q-48.5,-48.5 -77,-114T120,480q0,-75 28.5,-140.5t77,-114q48.5,-48.5 114,-77T480,120v80q-117,0 -198.5,81.5T200,480q0,117 81.5,198.5T480,760v80ZM640,680 L584,623 687,520L360,520v-80h327L584,336l56,-56 200,200 -200,200Z"
android:fillColor="#e3e3e3"/>
</vector>
15-edittext_bg:-
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#F1F1F1"/>
<corners android:radius="12dp"/>
<padding android:left="12dp" android:right="12dp" android:top="8dp" android:bottom="8dp"/>
</shape>
16- colors:-
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="whatsapp_green">#25D366</color>
<color name="input_bg">#F1F1F1</color>
<color name="text_dark">#1C1C1C</color>
</resources>
17- styles:-
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Circle Image View Style -->
<style name="CircleImageView">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">50%</item>
</style>
<!-- Search View Style -->
<style name="SearchViewStyle">
<item name="android:editTextColor">@color/black</item>
<item name="android:textColorHint">#757575</item>
<item name="queryBackground">@android:color/transparent</item>
</style>
</resources>
Comments
Post a Comment