Skip to main content

Steps to Implement Video Calling app Using ZegoCloud

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

Popular posts from this blog

Unlock Cursor Pro Features: Complete Guide for Windows, Mac, and Linux

๐Ÿš€ Unlocking Cursor Pro Features.md ๐Ÿš€ Unlocking Cursor Pro Features This guide provides instructions for accessing Cursor Pro features through specific modifications to the application’s data. Please choose the method appropriate for your operating system. Ad ๐Ÿ’ป Windows (Semi-Automated Method) Step 1: Sign Out & Close Cursor Sign out of Cursor from both the IDE and any open browser sessions. Completely close the Cursor application. Step 2: Install Latest Cursor Download and install the most recent version of Cursor from its official website. Launch Cursor once and then close it immediately without signing in. Step 3: Run PowerShell Reset Script Download the provided PowerShell reset script. Open PowerShell as administrator. To do this, search for “PowerShell” in the Start Menu, right-click on “Windows PowerShell,” and select “Run as administrator.” Navigate to your Downloads directory in PowerShell by typing: cd $env:USERPROFILE \Downloa...

๐Ÿ”ฅ Cursor & Windsurf Out of Credits? Meet This FREE Claude IDE Alternative!

๐Ÿ”ฅ Cursor & Windsurf Out of Credits? Try This Claude IDE with UNLIMITED Access! (Older Version) Are you frustrated with running out of credits in Cursor or Windsurf IDE just when you're in the coding zone? Meet Trae IDE — a sleek AI-powered IDE that used to offer unlimited Claude access … and guess what? I’ve got the older version that still does ! ๐Ÿ˜ฑ ❗ Important Update (June 2025): The newer versions of Trae IDE have now introduced limited credits — just like Cursor and Windsurf. But here’s the good news: I have the older version installer , which still lets you enjoy: ✅ Unlimited Claude AI Access ✅ No login, no subscription ✅ Fully Free Forever ๐Ÿš€ Why Use Trae IDE (Older Version)? ๐Ÿ”“ Claude integration with unlimited messages ⚡ Fast, clean, and distraction-free interface ๐Ÿ–ฅ️ Supports Windows , macOS , and Linux ๐Ÿ†“ No paywalls, no ads, no login ๐Ÿ” Safe & Easy Setup ๐Ÿ“ File Type: ZIP/Installer ๐Ÿงผ Virus-free, clean and portable ⏱️ Set...

๐Ÿš€ Supercharge Your Coding with Cline AI Agent in VS Code (๐Ÿ”ฅ Unlimited Free Trick Inside)

๐Ÿš€ Supercharge Your Coding with Cline AI Agent in VS Code (๐Ÿ”ฅ Unlimited Free Trick Inside) ๐ŸŽฏ What is Cline AI Coding Agent? Cline AI is an intelligent coding assistant that integrates directly into Visual Studio Code (VS Code) , transforming how you build software. It's more than just an auto-complete tool — it's like having a senior developer assistant right inside your IDE. ๐Ÿ’ป How to Use Cline AI in VS Code Follow these simple steps to get started: Install the Cline Extension Open VS Code → Go to Extensions Marketplace Search “Cline AI Coding Agent” Click Install Log in with Gmail Click the Cline icon in the sidebar Sign in using your Google account Start a New Task Click on “Start New Task” Describe your task (e.g., “Build a weather app in Android”) Cline will generate a detailed plan Approve and Generate Code Review the project structure Cline suggests Click Approve to let it generate boilerplate and logic U...