diff --git a/Demo/app/build.gradle b/Demo/app/build.gradle index 806d1aa..8e3a6a5 100644 --- a/Demo/app/build.gradle +++ b/Demo/app/build.gradle @@ -1,6 +1,7 @@ plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' + id 'kotlin-kapt' } android { @@ -24,26 +25,30 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = '17' } buildFeatures { viewBinding true + dataBinding true } } dependencies { - - implementation 'androidx.core:core-ktx:1.9.0' + def lifecycle_version = "2.6.1" + implementation 'androidx.core:core-ktx:1.10.0' implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'com.google.android.material:material:1.8.0' + implementation 'com.google.android.material:material:1.9.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' - implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1" - implementation 'androidx.fragment:fragment-ktx:1.5.5' + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" + implementation 'androidx.fragment:fragment-ktx:1.5.7' + implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" + implementation "androidx.core:core-splashscreen:1.0.1" + implementation "androidx.preference:preference-ktx:1.2.0" testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' diff --git a/Demo/app/src/main/AndroidManifest.xml b/Demo/app/src/main/AndroidManifest.xml index 3410a78..6a262c5 100644 --- a/Demo/app/src/main/AndroidManifest.xml +++ b/Demo/app/src/main/AndroidManifest.xml @@ -3,14 +3,24 @@ xmlns:tools="http://schemas.android.com/tools"> + + + + + + + diff --git a/Demo/app/src/main/java/com/krunal/demo/DemoApplication.kt b/Demo/app/src/main/java/com/krunal/demo/DemoApplication.kt new file mode 100644 index 0000000..2a7b2b8 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/DemoApplication.kt @@ -0,0 +1,16 @@ +package com.krunal.demo + +import android.app.Application +import com.krunal.demo.uicomponents.helpers.PreferenceHelper + +class DemoApplication: Application() { + + override fun onCreate() { + super.onCreate() + + /** + * Initialize [PreferenceHelper] + */ + PreferenceHelper.initialize(applicationContext) + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/MainActivity.kt b/Demo/app/src/main/java/com/krunal/demo/MainActivity.kt index bd7b820..56439e4 100644 --- a/Demo/app/src/main/java/com/krunal/demo/MainActivity.kt +++ b/Demo/app/src/main/java/com/krunal/demo/MainActivity.kt @@ -12,15 +12,18 @@ import kotlinx.coroutines.launch class MainActivity : AppCompatActivity() { - lateinit var binding: ActivityMainBinding - lateinit var mPlayer: MediaPlayer + private lateinit var binding: ActivityMainBinding + private lateinit var mPlayer: MediaPlayer private val viewModel: MainActivityViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) + + binding.mainViewModel = viewModel + + // Update time without data binding CoroutineScope(Dispatchers.IO).launch { viewModel.timeFlow.collectLatest { time -> runOnUiThread { diff --git a/Demo/app/src/main/java/com/krunal/demo/MainActivityViewModel.kt b/Demo/app/src/main/java/com/krunal/demo/MainActivityViewModel.kt index 31952be..2730fe0 100644 --- a/Demo/app/src/main/java/com/krunal/demo/MainActivityViewModel.kt +++ b/Demo/app/src/main/java/com/krunal/demo/MainActivityViewModel.kt @@ -2,15 +2,16 @@ package com.krunal.demo import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import java.text.SimpleDateFormat -import java.util.* +import java.util.Date class MainActivityViewModel : ViewModel() { - private val _timeFlow = MutableSharedFlow() - val timeFlow: MutableSharedFlow = _timeFlow + private val _timeFlow = MutableStateFlow(null) + val timeFlow: StateFlow = _timeFlow init { start() diff --git a/Demo/app/src/main/java/com/krunal/demo/UIComponentsActivity.kt b/Demo/app/src/main/java/com/krunal/demo/UIComponentsActivity.kt new file mode 100644 index 0000000..a6eb2ff --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/UIComponentsActivity.kt @@ -0,0 +1,32 @@ +package com.krunal.demo + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.app.AppCompatDelegate +import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen +import com.krunal.demo.recyclerview.fragments.ContactDetailFragment +import com.krunal.demo.uicomponents.helpers.ThemeHelper +import com.krunal.demo.uicomponents.models.enums.AccentColor + +class UIComponentsActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + installSplashScreen() + setupTheme() + setContentView(R.layout.activity_uicomponents) + setupFragment() + } + + private fun setupTheme() { + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) +// setTheme(ThemeHelper.getThemeResource(ThemeHelper.getThemeAccent())) + setTheme(ThemeHelper.getThemeResource(AccentColor.BLUE)) + } + + private fun setupFragment() { + supportFragmentManager.beginTransaction() + .replace(R.id.uiComponentsFragment, ContactDetailFragment()) + .commit() + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/javaPractice/Conditions.java b/Demo/app/src/main/java/com/krunal/demo/javaPractice/Conditions.java new file mode 100644 index 0000000..e343b55 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/javaPractice/Conditions.java @@ -0,0 +1,43 @@ +package com.krunal.demo.javaPractice; + +import java.util.Random; + +public class Conditions { + public static void main(String[] args) { + if (false) { + if (4 >= 5) { + System.out.println("4 > 5"); + } else { + System.out.println("5 > 4"); + } + } else if (new Random().nextBoolean()) { + System.out.println("In else-if"); + } else { + System.out.println("In else"); + } + + isVowel('a'); + isVowel('E'); + isVowel('0'); + } + + public static void isVowel(char ch) { + switch (ch) { + case 'a': + case 'e': + case 'i': + case 'o': + case 'u': + System.out.println("small case"); + case 'A': + case 'E': + case 'I': + case 'O': + case 'U': + System.out.println("Vowel"); + break; + default: + System.out.println("Constant"); + } + } +} diff --git a/Demo/app/src/main/java/com/krunal/demo/javaPractice/DifferentObjCreation.java b/Demo/app/src/main/java/com/krunal/demo/javaPractice/DifferentObjCreation.java new file mode 100644 index 0000000..6354b52 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/javaPractice/DifferentObjCreation.java @@ -0,0 +1,57 @@ +package com.krunal.demo.javaPractice; + +import androidx.annotation.NonNull; + +import java.io.FileInputStream; +import java.io.ObjectInputStream; +import java.io.Serializable; + +public class DifferentObjCreation { + public static void main(String[] args) { + System.out.println("Hello World"); + createObjs(); + } + + public static void createObjs() { + Demo2 obj = new Demo2(); + + try { + obj = (Demo2) Class.forName("com.krunal.demo.javaPractice.Demo2").newInstance(); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + + try { + Demo2 obj2 = (Demo2) obj.clone(); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + + try { + ObjectInputStream is = new ObjectInputStream(new FileInputStream("demo2")); + Demo2 obj4 = (Demo2) is.readObject(); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + } +} + +class Demo2 implements Serializable, Cloneable { + static { + System.out.println("Class initialized"); + } + + { + System.out.println("Class obj initialized"); + } + + Demo2() { + System.out.println("Demo2 constructor"); + } + + @NonNull + @Override + protected Object clone() throws CloneNotSupportedException { + return super.clone(); + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/javaPractice/ExceptionHandling.java b/Demo/app/src/main/java/com/krunal/demo/javaPractice/ExceptionHandling.java new file mode 100644 index 0000000..3489466 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/javaPractice/ExceptionHandling.java @@ -0,0 +1,7 @@ +package com.krunal.demo.javaPractice; + +public class ExceptionHandling { + public static void main(String[] args) { + + } +} diff --git a/Demo/app/src/main/java/com/krunal/demo/javaPractice/NumGuessGame.java b/Demo/app/src/main/java/com/krunal/demo/javaPractice/NumGuessGame.java new file mode 100644 index 0000000..30cccc2 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/javaPractice/NumGuessGame.java @@ -0,0 +1,35 @@ +package com.krunal.demo.javaPractice; + +import java.util.Random; +import java.util.Scanner; + +public class NumGuessGame { + public static void main(String[] args) { + new Game().start(); + } +} + +class Game { + private final Scanner sc = new Scanner(System.in); + private final int num = new Random().nextInt(100); + private int tries = 0; + + public void start() { + guess: + while (true) { + System.out.print("Guess no: "); + int guess = sc.nextInt(); + + if (guess == num) { + System.out.println("Guessed correct in " + tries + " try" ); + break guess; + } else if (guess < num) { + System.out.println("Try larger number"); + tries++; + } else { + System.out.println("Try smaller number"); + tries++; + } + } + } +} diff --git a/Demo/app/src/main/java/com/krunal/demo/javaPractice/OopDemo.java b/Demo/app/src/main/java/com/krunal/demo/javaPractice/OopDemo.java new file mode 100644 index 0000000..ef3df55 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/javaPractice/OopDemo.java @@ -0,0 +1,68 @@ +package com.krunal.demo.javaPractice; + +public class OopDemo { + public static void main(String[] args) { + Animal animal = new Animal() { + @Override + public boolean canProtect() { + return false; + } + }; + System.out.println(animal.speak()); + + animal = new Dog(); + ((Dog) animal).setName("Tommy"); + System.out.println(animal.speak()); + + animal = new Lion(); + System.out.println(animal.speak()); + } +} + +interface Animal { + boolean canProtect(); + default String speak() { + System.out.println("Default method"); + return "Hello world!!"; + } +} + +abstract class Domestic implements Animal { + + abstract public String getName(); + + @Override + public String speak() { + return "Hello!!"; + } +} + +class Dog extends Domestic { + private String name = ""; + + @Override + public boolean canProtect() { + return true; + } + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} + +class Lion implements Animal { + @Override + public boolean canProtect() { + return false; + } + + @Override + public String speak() { + return "Roar"; + } +} diff --git a/Demo/app/src/main/java/com/krunal/demo/javaPractice/Operators.java b/Demo/app/src/main/java/com/krunal/demo/javaPractice/Operators.java new file mode 100644 index 0000000..d9c5d0d --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/javaPractice/Operators.java @@ -0,0 +1,64 @@ +package com.krunal.demo.javaPractice; + +public class Operators { + public static void main(String[] args) { + int n1 = 5; + int n2 = 10; + + // Arithmetic Operators + int sum = n1 + n2; + System.out.println("sum: " + sum); + int sub = n1 - n2; + System.out.println("sub: " + sub); + int mul = n1 * n2; + System.out.println("mul: " + mul); + float div = ((float) n1) / n2; + System.out.println("div: " + div); + int mod = n2-- % ++n1; + System.out.println("div: " + mod); + + // Relational Operators + if (n1 == n2) { + System.out.println("Both are same"); + } else if (n1 > n2) { + System.out.println("N1 is larger"); + } else if (n1 <= n2) { + System.out.println("N1 is same or smaller"); + } + + // Logical Operators + if (echo(true) || echo(false)) { + System.out.println("Inside ||"); + } + + if (!echo(true) && echo(false)) { + System.out.println("Inside &&"); + } + + // Assignment Operators + int num = n1; + System.out.println("num: " + num); + num += num; + System.out.println("num: " + num); + num /= --num; + System.out.println("num: " + num); + + // Ternary Operator + System.out.println(n1 > n2 ? "n1" : "n2"); + + // instance of + String s = "Hello"; + if (s instanceof String) { + System.out.println("It's String"); + } + + + } + + static boolean echo(boolean flag) { + System.out.println("flag is " + flag); + return flag; + } +} + + diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/CalculationAdapter.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/CalculationAdapter.kt new file mode 100644 index 0000000..7490208 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/CalculationAdapter.kt @@ -0,0 +1,170 @@ +package com.krunal.demo.recyclerview.adapters + +import android.net.Uri +import android.util.Log +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.krunal.demo.R +import com.krunal.demo.databinding.ItemCalculationBinding +import com.krunal.demo.recyclerview.decorations.SpaceDecoration +import com.krunal.demo.recyclerview.listeners.CalculationDiffCallback +import com.krunal.demo.recyclerview.listeners.OnItemChangeListener +import com.krunal.demo.recyclerview.models.Calculation +import com.krunal.demo.recyclerview.models.Payload + +class CalculationAdapter : ListAdapter(CalculationDiffCallback) { + + var onItemChangeListener: OnItemChangeListener? = null + + class CalculationViewHolder( + private val binding: ItemCalculationBinding + , private val onItemChangeListener: OnItemChangeListener?) : + RecyclerView.ViewHolder(binding.root) { + + fun bind(calculation: Calculation) { + + val imageAdapter = CalculationImagesAdapter().apply { + onLongClick = { imagePosition -> + onItemChangeListener?.onImageRemove(adapterPosition, imagePosition) + } + } + + val valueAdapter = ValueAdapter { position, value -> + onItemChangeListener?.onValueChange(adapterPosition, position, value) + } + valueAdapter.submitList(calculation.additionalNums) + + ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) { + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean = true + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + onItemChangeListener?.onValueRemove(adapterPosition, viewHolder.adapterPosition) + } + }).attachToRecyclerView(binding.rvValue) + + binding.apply { + this.calculation = calculation + rvValue.adapter = valueAdapter + rvImages.adapter = imageAdapter + rvImages.addItemDecoration(SpaceDecoration(SpaceDecoration.HORIZONTAL)) + + btnAddValue.setOnClickListener { + onItemChangeListener?.onValueAdd(adapterPosition, 1) + } + + txtNum1.setOnFocusChangeListener { _, hasFocus -> + if (hasFocus || adapterPosition == -1) return@setOnFocusChangeListener + + txtNum1.text.toString().toDoubleOrNull()?.let { num1 -> + if (num1 != calculation.num1) { + onItemChangeListener?.onNumberChange( + adapterPosition, + num1, + null + ) + } + } + } + + txtNum2.setOnFocusChangeListener { _, hasFocus -> + if (hasFocus || adapterPosition == -1) return@setOnFocusChangeListener + + txtNum2.text.toString().toDoubleOrNull()?.let { num2 -> + if (num2 != calculation.num2) { + onItemChangeListener?.onNumberChange( + adapterPosition, + null, + num2 + ) + } + } + } + + btnRemove.setOnClickListener { + onItemChangeListener?.onCalculationRemove(adapterPosition) + } + + btnAddImage.setOnClickListener { + onItemChangeListener?.onImageAdd(adapterPosition) + } + } + } + + fun addImage(uri: Uri) { + (binding.rvImages.adapter as? CalculationImagesAdapter)?.addImage(uri) + } + + fun removeImage(position: Int) { + (binding.rvImages.adapter as? CalculationImagesAdapter)?.removeImage(position) + } + + fun addValue(value: Int, total: Double) { + (binding.rvValue.adapter as? ValueAdapter)?.addValue(value) + setTotal(total) + } + + fun removeValue(position: Int, total: Double) { + (binding.rvValue.adapter as? ValueAdapter)?.removeValue(position) + setTotal(total) + } + + fun bindNumbers(num1: Double, num2: Double, total: Double) { + binding.txtNum1.setText(num1.toString()) + binding.txtNum2.setText(num2.toString()) + setTotal(total) + } + + fun changeValue(position: Int, value: Int, total: Double) { + (binding.rvImages.adapter as? ValueAdapter)?.changeValue(position, value) + setTotal(total) + } + + private fun setTotal(total: Double) { + binding.tvTotal.text = binding.root.context.getString(R.string.total, total) + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CalculationViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = ItemCalculationBinding.inflate(layoutInflater) + return CalculationViewHolder(binding, onItemChangeListener) + } + + override fun getItemCount(): Int = currentList.count() + + override fun onBindViewHolder(holder: CalculationViewHolder, position: Int) { + holder.bind(currentList[position]) + } + + override fun onBindViewHolder( + holder: CalculationViewHolder, + position: Int, + payloads: MutableList + ) { + + (payloads.firstOrNull() as? Payload)?.let { payload -> + when (payload) { + is Payload.ChangeNumber -> holder.bindNumbers(payload.num1, payload.num2, currentList[position].total) + + is Payload.AddImage -> holder.addImage(payload.uri) + + is Payload.RemoveImage -> holder.removeImage(payload.position) + + is Payload.AddValue -> holder.addValue(payload.value, currentList[position].total) + + is Payload.RemoveValue -> holder.removeValue(payload.position, currentList[position].total) + + is Payload.ChangeValue -> holder.changeValue(payload.position, payload.value, currentList[position].total) + } + return + } + super.onBindViewHolder(holder, position, payloads) + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/CalculationImagesAdapter.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/CalculationImagesAdapter.kt new file mode 100644 index 0000000..ce5d7eb --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/CalculationImagesAdapter.kt @@ -0,0 +1,51 @@ +package com.krunal.demo.recyclerview.adapters + +import android.net.Uri +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.krunal.demo.databinding.ImageLayoutBinding + +class CalculationImagesAdapter : RecyclerView.Adapter() { + + private val images: MutableList = mutableListOf() + var onLongClick: ((position: Int) -> Unit)? = null + + class ImageViewHolder(private val binding: ImageLayoutBinding, private val onLongClick: ((position: Int) -> Unit)?): RecyclerView.ViewHolder(binding.root) { + fun bind(uri: Uri) { + binding.imgView.setImageURI(uri) + binding.root.setOnLongClickListener { + onLongClick?.invoke(adapterPosition) + true + } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = ImageLayoutBinding.inflate(layoutInflater) + return ImageViewHolder(binding, onLongClick) + } + + override fun getItemCount(): Int = images.count() + + override fun onBindViewHolder(holder: ImageViewHolder, position: Int) { + holder.bind(images[position]) + } + + fun submitList(list: List) { + images.clear() + images.addAll(list) + notifyDataSetChanged() + } + + fun addImage(uri: Uri) { + images.add(uri) + notifyItemInserted(images.count() - 1) + } + + fun removeImage(position: Int) { + images.removeAt(position) + notifyItemRemoved(position) + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/ChatAdapter.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/ChatAdapter.kt new file mode 100644 index 0000000..9cc6816 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/ChatAdapter.kt @@ -0,0 +1,87 @@ +package com.krunal.demo.recyclerview.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import com.krunal.demo.databinding.ItemMessageReceiveBinding +import com.krunal.demo.databinding.ItemMessageSendBinding +import com.krunal.demo.recyclerview.listeners.ChatDiffCallback +import com.krunal.demo.recyclerview.models.Message +import com.krunal.demo.recyclerview.models.enums.MessageType + +class ChatAdapter : RecyclerView.Adapter() { + + private val messages: MutableList = mutableListOf() + + class SendMessageViewHolder(private val binding: ItemMessageSendBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bind(message: Message, onRemove: (Message) -> Unit) { + binding.message = message + binding.root.setOnLongClickListener { + onRemove(message) + true + } + } + } + + class ReceiveMessageViewHolder(private val binding: ItemMessageReceiveBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bind(message: Message, onRemove: (Message) -> Unit) { + binding.message = message + binding.root.setOnLongClickListener { + onRemove(message) + true + } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + + return when (MessageType.values()[viewType]) { + MessageType.SEND -> { + val binding = ItemMessageSendBinding.inflate(layoutInflater) + SendMessageViewHolder(binding) + } + + MessageType.RECEIVE -> { + val binding = ItemMessageReceiveBinding.inflate(layoutInflater) + ReceiveMessageViewHolder(binding) + } + } + } + + override fun getItemCount(): Int = messages.count() + + override fun getItemViewType(position: Int): Int = messages[position].messageType.ordinal + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + val onRemove: (Message) -> Unit = { message -> + submitList(messages.filterNot { it == message }) + } + + when (holder) { + is SendMessageViewHolder -> { + holder.bind(messages[position], onRemove) + } + + is ReceiveMessageViewHolder -> { + holder.bind(messages[position], onRemove) + } + } + } + + fun submitList(list: List) { + val diffUtil = ChatDiffCallback(this.messages, list) + val diffResult = DiffUtil.calculateDiff(diffUtil) + + messages.clear() + messages.addAll(list) + + diffResult.dispatchUpdatesTo(this) + } +} + diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/CommentAdapter.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/CommentAdapter.kt new file mode 100644 index 0000000..ccc2c5c --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/CommentAdapter.kt @@ -0,0 +1,51 @@ +package com.krunal.demo.recyclerview.adapters + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.krunal.demo.databinding.ItemCommentBinding +import com.krunal.demo.databinding.ItemRecommendationBinding +import com.krunal.demo.recyclerview.models.Comment + +class CommentAdapter : RecyclerView.Adapter() { + + private val comments: MutableList = mutableListOf() + var isExpanded: Boolean = false + + class CommentViewHolder(val binding: ItemCommentBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bind(comment: Comment) { + binding.comment = comment + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CommentViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = ItemCommentBinding.inflate(layoutInflater, parent, false) + return CommentViewHolder(binding) + } + + override fun getItemCount(): Int = if (isExpanded) comments.count() else 1 + + override fun onBindViewHolder(holder: CommentViewHolder, position: Int) { + holder.bind(comments[position]) + } + + fun toggleExpanded(expand: Boolean) { + isExpanded = expand + if (isExpanded) { + notifyItemRangeInserted(1, comments.count() - 1) + } else { + notifyItemRangeRemoved(1, comments.count() - 1) + } + } + + @SuppressLint("NotifyDataSetChanged") + fun submitComments(list: List) { + comments.clear() + comments.addAll(list) + notifyDataSetChanged() + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/ContactDetailsAdapter.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/ContactDetailsAdapter.kt new file mode 100644 index 0000000..16d40e0 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/ContactDetailsAdapter.kt @@ -0,0 +1,73 @@ +package com.krunal.demo.recyclerview.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.krunal.demo.databinding.ItemContactDetailBinding +import com.krunal.demo.recyclerview.models.ContactDetail + +class ContactDetailsAdapter : + RecyclerView.Adapter() { + + private val contactDetails: MutableList = mutableListOf() + private var currentExpandedPosition: Int = SELECTED_NONE + + class ContactDetailsViewHolder(private val binding: ItemContactDetailBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bind(contactDetail: ContactDetail, onClick: () -> Unit) { + binding.contactDetail = contactDetail + binding.root.setOnClickListener { + onClick() + } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ContactDetailsViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = ItemContactDetailBinding.inflate(layoutInflater) + return ContactDetailsViewHolder(binding) + } + + override fun getItemCount(): Int = contactDetails.count() + + override fun onBindViewHolder(holder: ContactDetailsViewHolder, position: Int) { + contactDetails[position].let { detail -> + holder.bind(detail) { + detail.isExpanded = detail.isExpanded.not() + notifyItemChanged(position) + + when (currentExpandedPosition) { + SELECTED_NONE -> currentExpandedPosition = holder.adapterPosition + + holder.adapterPosition -> currentExpandedPosition = SELECTED_NONE + + else -> { + contactDetails[currentExpandedPosition].isExpanded = false + notifyItemChanged(currentExpandedPosition) + currentExpandedPosition = holder.adapterPosition + } + } + } + } + + } + + fun submitList(list: List) { + contactDetails.clear() + contactDetails.addAll(list) + notifyDataSetChanged() + } + + fun collapseAll() { + if (currentExpandedPosition != SELECTED_NONE) { + contactDetails[currentExpandedPosition].isExpanded = false + notifyItemChanged(currentExpandedPosition) + currentExpandedPosition = SELECTED_NONE + } + } + + companion object { + const val SELECTED_NONE = -1 + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/FeedAdapter.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/FeedAdapter.kt new file mode 100644 index 0000000..7a1abd8 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/FeedAdapter.kt @@ -0,0 +1,143 @@ +package com.krunal.demo.recyclerview.adapters + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.LinearSnapHelper +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.tabs.TabLayoutMediator +import com.krunal.demo.databinding.CommunityPostLayoutBinding +import com.krunal.demo.databinding.FeedVideoLayoutBinding +import com.krunal.demo.databinding.ItemRecommendationBinding +import com.krunal.demo.databinding.ShortVideoLayoutBinding +import com.krunal.demo.recyclerview.models.CommunityPost +import com.krunal.demo.recyclerview.models.Feed +import com.krunal.demo.recyclerview.models.enums.FeedType +import com.krunal.demo.recyclerview.models.Recommendation +import com.krunal.demo.recyclerview.models.VideoDetails +import com.krunal.demo.recyclerview.viewholders.VideoViewHolder + +class FeedAdapter : RecyclerView.Adapter() { + + private val feeds: MutableList = mutableListOf() + + class ShortVideoViewHolder(val binding: ShortVideoLayoutBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bind(videoDetails: VideoDetails) { + binding.videoDetail = videoDetails + } + } + + class RecommendationViewHolder(val context: Context, val binding: ItemRecommendationBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bind(recommendation: Recommendation) { + binding.recommendation = recommendation + binding.rvRecommendation.apply { + layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) + adapter = RecommendationAdapter().apply { submitList(recommendation.videos) } + onFlingListener = null + + addItemDecoration(object : RecyclerView.ItemDecoration() { + override fun onDraw( + c: Canvas, parent: RecyclerView, state: RecyclerView.State + ) { + c.drawCircle(10f, 10f, 10F, Paint().apply { color = Color.RED }) + } + }) + + LinearSnapHelper().also { snapHelper -> + snapHelper.attachToRecyclerView(this) + } + } + } + } + + class CommunityPostViewHolder(val binding: CommunityPostLayoutBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bind(communityPost: CommunityPost) { + binding.communityPost = communityPost + (binding.vpImage.getChildAt(0) as? RecyclerView)?.clearOnChildAttachStateChangeListeners() + binding.vpImage.adapter = ImagesAdapter().apply { + submitList(communityPost.images) + } + + TabLayoutMediator(binding.tlIndicator, binding.vpImage) { _, _ -> }.attach() + val commentAdapter = CommentAdapter().apply { + submitComments(communityPost.comments) + } + binding.rvComments.adapter = commentAdapter + + binding.imgBtnComment.setOnCheckedChangeListener { _, isExpanded -> + commentAdapter.toggleExpanded(isExpanded) + } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + + return when (FeedType.values()[viewType]) { + FeedType.VIDEO -> { + val binding = FeedVideoLayoutBinding.inflate(layoutInflater, parent, false) + VideoViewHolder(binding) + } + + FeedType.SHORT_VIDEO -> { + val binding = ShortVideoLayoutBinding.inflate(layoutInflater, parent, false) + ShortVideoViewHolder(binding) + } + + FeedType.RECOMMENDATION -> { + val binding = ItemRecommendationBinding.inflate(layoutInflater, parent, false) + RecommendationViewHolder(parent.context, binding) + } + + FeedType.COMMUNITY_POST -> { + val binding = CommunityPostLayoutBinding.inflate(layoutInflater, parent, false) + CommunityPostViewHolder(binding) + } + } + } + + override fun getItemCount(): Int = feeds.count() + + override fun getItemViewType(position: Int): Int { + return feeds[position].type.ordinal + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + val details = feeds[position] + when (holder) { + is VideoViewHolder -> { + holder.bind(details as VideoDetails) + } + + is ShortVideoViewHolder -> { + holder.bind(details as VideoDetails) + } + + is RecommendationViewHolder -> { + holder.bind(details as Recommendation) + } + + is CommunityPostViewHolder -> { + holder.bind(details as CommunityPost) + } + } + } + + @SuppressLint("NotifyDataSetChanged") + fun submitFeeds(details: List) { + feeds.clear() + feeds.addAll(details) + notifyDataSetChanged() + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/ImagesAdapter.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/ImagesAdapter.kt new file mode 100644 index 0000000..c716c3b --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/ImagesAdapter.kt @@ -0,0 +1,40 @@ +package com.krunal.demo.recyclerview.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.krunal.demo.databinding.ImageLayoutBinding + +class ImagesAdapter : RecyclerView.Adapter() { + + private val images: MutableList = mutableListOf() + var onLongClick: ((position: Int) -> Unit)? = null + + class ImageViewHolder(private val binding: ImageLayoutBinding, private val onLongClick: ((position: Int) -> Unit)?): RecyclerView.ViewHolder(binding.root) { + fun bind(imageResource: Int) { + binding.imgView.setImageResource(imageResource) + binding.root.setOnLongClickListener { + onLongClick?.invoke(adapterPosition) + true + } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = ImageLayoutBinding.inflate(layoutInflater) + return ImageViewHolder(binding, onLongClick) + } + + override fun getItemCount(): Int = images.count() + + override fun onBindViewHolder(holder: ImageViewHolder, position: Int) { + holder.bind(images[position]) + } + + fun submitList(list: List) { + images.clear() + images.addAll(list) + notifyDataSetChanged() + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/LibraryAdapter.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/LibraryAdapter.kt new file mode 100644 index 0000000..6eb2b0c --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/LibraryAdapter.kt @@ -0,0 +1,127 @@ +package com.krunal.demo.recyclerview.adapters + +import android.animation.AnimatorSet +import android.animation.ObjectAnimator +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.krunal.demo.databinding.ItemHistoryVideoBinding +import com.krunal.demo.databinding.ItemLoadingBinding +import com.krunal.demo.recyclerview.models.enums.HistoryViewType +import com.krunal.demo.recyclerview.models.VideoDetails + +class LibraryAdapter : RecyclerView.Adapter() { + + private val videoDetails: MutableList = mutableListOf() + var isLoading: Boolean = false + var onAttach = true + + class HistoryViewHolder(val binding: ItemHistoryVideoBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bind(videoDetails: VideoDetails) { + binding.videoDetail = videoDetails + } + } + + class LoadingViewHolder(val binding: ItemLoadingBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bind(isLoading: Boolean) { + binding.isLoading = isLoading + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + + return when (HistoryViewType.values()[viewType]) { + HistoryViewType.VIDEO -> { + val binding = ItemHistoryVideoBinding.inflate(layoutInflater, parent, false) + HistoryViewHolder(binding) + } + + HistoryViewType.LOADING -> { + val binding = ItemLoadingBinding.inflate(layoutInflater, parent, false) + LoadingViewHolder(binding) + } + } + + } + + override fun getItemCount(): Int = if (isLoading) { + videoDetails.count() + 1 + } else { + videoDetails.count() + } + + override fun getItemViewType(position: Int): Int = + (if (isLoading && position == videoDetails.count()) HistoryViewType.LOADING else HistoryViewType.VIDEO).ordinal + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is HistoryViewHolder -> holder.bind(videoDetails[position]) + is LoadingViewHolder -> holder.bind(isLoading) + } + } + + override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { + recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { + + override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + onAttach = false + super.onScrollStateChanged(recyclerView, newState) + } + }) + + super.onAttachedToRecyclerView(recyclerView) + } + + fun showLoading() { + isLoading = true + notifyItemInserted(videoDetails.count()) + } + + fun hideLoading() { + isLoading = false + notifyItemRemoved(videoDetails.count()) + } + + fun submitList(list: List) { + videoDetails.clear() + videoDetails.addAll(list) + notifyDataSetChanged() + } + + fun addList(list: List) { + videoDetails.addAll(list) +// notifyItemRangeInserted(videoDetails.count() - list.count(), list.count()) + notifyDataSetChanged() + } + + + private fun setAnimation(itemView: View, position: Int) { + var i = position + if (!onAttach) { + i = -1 + } + val isNotFirstItem = i == -1 + i++ + itemView.translationX = itemView.x + 400 + itemView.alpha = 0f + val animatorSet = AnimatorSet() + val animatorTranslateY = + ObjectAnimator.ofFloat(itemView, "translationX", itemView.x + 400, 0f) + val animatorAlpha = ObjectAnimator.ofFloat(itemView, "alpha", 1f) + ObjectAnimator.ofFloat(itemView, "alpha", 0f).start() + animatorTranslateY.startDelay = if (isNotFirstItem) ANIMATION_DURATION else i * ANIMATION_DURATION + animatorTranslateY.duration = (if (isNotFirstItem) 2 else 1) * ANIMATION_DURATION + animatorSet.playTogether(animatorTranslateY, animatorAlpha) + animatorSet.start() + } + + companion object { + private const val ANIMATION_DURATION = 500L + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/ProfileImagesAdapter.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/ProfileImagesAdapter.kt new file mode 100644 index 0000000..4dfbf64 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/ProfileImagesAdapter.kt @@ -0,0 +1,91 @@ +package com.krunal.demo.recyclerview.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.krunal.demo.databinding.ItemPlusCountBinding +import com.krunal.demo.databinding.ItemProfileImageBinding +import com.krunal.demo.recyclerview.listeners.OnChatChangeListener +import com.krunal.demo.recyclerview.models.enums.ProfileViewType +import kotlin.math.min + +class ProfileImagesAdapter : RecyclerView.Adapter() { + + private val profiles: MutableList = mutableListOf() + var onChatChangeListener: OnChatChangeListener? = null + var shouldTruncate: Boolean = false + + class ImageViewHolder( + private val binding: ItemProfileImageBinding, + private val onChatChangeListener: OnChatChangeListener? + ) : RecyclerView.ViewHolder(binding.root) { + + fun bind(image: Int) { + binding.imgViewProfile.setImageResource(image) + binding.root.setOnClickListener { + onChatChangeListener?.onProfileClick(adapterPosition) + } + } + } + + class CountViewHolder( + private val binding: ItemPlusCountBinding, + private val onExpand: () -> Unit + ) : RecyclerView.ViewHolder(binding.root) { + + fun bind(count: Int) { + binding.count = count + binding.root.setOnClickListener { + onExpand() + } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + + return when (ProfileViewType.values()[viewType]) { + ProfileViewType.IMAGE -> { + val binding = ItemProfileImageBinding.inflate(layoutInflater) + ImageViewHolder(binding, onChatChangeListener) + } + + ProfileViewType.COUNT -> { + val binding = ItemPlusCountBinding.inflate(layoutInflater) + return CountViewHolder(binding) { + shouldTruncate = false + notifyItemRangeChanged(MAXIMUM_CHAT_COUNT, profiles.count()) + } + } + } + + } + + override fun getItemCount(): Int = + if (shouldTruncate) min(profiles.count(), MAXIMUM_CHAT_COUNT) else profiles.count() + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is ImageViewHolder -> { + holder.bind(profiles[position]) + } + + is CountViewHolder -> { + holder.bind(profiles.count() - MAXIMUM_CHAT_COUNT) + } + } + } + + override fun getItemViewType(position: Int): Int = + (if (shouldTruncate && profiles.count() - 1 != position && position == MAXIMUM_CHAT_COUNT - 1) ProfileViewType.COUNT else ProfileViewType.IMAGE).ordinal + + fun submitList(list: List) { + profiles.clear() + profiles.addAll(list) + notifyDataSetChanged() + } + + companion object { + const val MAXIMUM_CHAT_COUNT = 4 + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/RecommendationAdapter.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/RecommendationAdapter.kt new file mode 100644 index 0000000..be19f1f --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/RecommendationAdapter.kt @@ -0,0 +1,31 @@ +package com.krunal.demo.recyclerview.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.krunal.demo.databinding.FeedVideoLayoutBinding +import com.krunal.demo.recyclerview.models.VideoDetails +import com.krunal.demo.recyclerview.viewholders.VideoViewHolder + +class RecommendationAdapter : RecyclerView.Adapter() { + + private val videos: MutableList = mutableListOf() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VideoViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = FeedVideoLayoutBinding.inflate(layoutInflater, parent, false) + return VideoViewHolder(binding) + } + + override fun getItemCount(): Int = videos.count() + + override fun onBindViewHolder(holder: VideoViewHolder, position: Int) { + holder.bind(videos[position]) + } + + fun submitList(list: List) { + videos.clear() + videos.addAll(list) + notifyDataSetChanged() + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/ValueAdapter.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/ValueAdapter.kt new file mode 100644 index 0000000..3e95ef0 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/ValueAdapter.kt @@ -0,0 +1,73 @@ +package com.krunal.demo.recyclerview.adapters + +import android.content.Context +import android.net.Uri +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.AdapterView +import android.widget.AdapterView.OnItemSelectedListener +import android.widget.ArrayAdapter +import androidx.recyclerview.widget.RecyclerView +import com.krunal.demo.databinding.ItemChooseValueBinding + +class ValueAdapter(private val onValueChange: (position: Int, value: Int) -> Unit) : + RecyclerView.Adapter() { + + private val additionValues: MutableList = mutableListOf() + + class ValueViewHolder( + private val binding: ItemChooseValueBinding, private val context: Context + ) : RecyclerView.ViewHolder(binding.root) { + + private val values: List = (1..10).toList() + + fun bind(number: Int, onValueChange: (Int, Int) -> Unit) { + val adapter = + ArrayAdapter(context, android.R.layout.simple_spinner_dropdown_item, values) + binding.number = number + + binding.spinnerValue.apply { + setAdapter(adapter) + setText(number.toString(), false) + setOnItemClickListener { _, _, position, _ -> + onValueChange(adapterPosition, values[position]) + } + } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ValueViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val binding = ItemChooseValueBinding.inflate(layoutInflater) + return ValueViewHolder(binding, parent.context) + } + + override fun getItemCount(): Int = additionValues.count() + + override fun onBindViewHolder(holder: ValueViewHolder, position: Int) { + holder.bind(additionValues[position], onValueChange) + } + + fun submitList(list: List) { + additionValues.clear() + additionValues.addAll(list) + notifyDataSetChanged() + } + + + fun addValue(value: Int) { + additionValues.add(value) + notifyItemInserted(additionValues.count() - 1) + } + + fun removeValue(position: Int) { + additionValues.removeAt(position) + notifyItemRemoved(position) + } + + fun changeValue(position: Int, value: Int) { + additionValues[position] = value + notifyItemChanged(position) + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/YoutubeFragmentAdapter.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/YoutubeFragmentAdapter.kt new file mode 100644 index 0000000..bcf10d1 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/adapters/YoutubeFragmentAdapter.kt @@ -0,0 +1,14 @@ +package com.krunal.demo.recyclerview.adapters + +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.viewpager2.adapter.FragmentStateAdapter + +class YoutubeFragmentAdapter( + private val fragments: List, fragmentActivity: FragmentActivity +) : FragmentStateAdapter(fragmentActivity) { + + override fun getItemCount(): Int = fragments.count() + + override fun createFragment(position: Int): Fragment = fragments[position] +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/decorations/ChatItemDecoration.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/decorations/ChatItemDecoration.kt new file mode 100644 index 0000000..0099516 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/decorations/ChatItemDecoration.kt @@ -0,0 +1,94 @@ +package com.krunal.demo.recyclerview.decorations + +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.graphics.Path +import android.graphics.Rect +import android.graphics.RectF +import android.view.View +import androidx.core.view.children +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.ItemDecoration +import com.google.android.material.textview.MaterialTextView +import com.krunal.demo.R +import com.krunal.demo.recyclerview.models.enums.MessageType + +class ChatItemDecoration : ItemDecoration() { + + var bottomSpacing = 40 + + private val paint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + var colorSender = Color.GREEN + var colorReceiver = Color.CYAN + + init { + setupPaint() + } + + private fun setupPaint() { + paint.apply { + strokeCap = Paint.Cap.ROUND + style = Paint.Style.FILL + } + } + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + parent.children.forEach { view -> + val viewRect = Rect() + parent.getDecoratedBoundsWithMargins(view, viewRect) + + val txtView = view.findViewById(R.id.tvMessage) + val position = parent.getChildAdapterPosition(view) + + if (position == -1) return@forEach + + val viewType = parent.adapter?.getItemViewType(position) ?: return@forEach + + val rectF = RectF( + viewRect.left + txtView.left.toFloat() - DECORATION_PADDING, + viewRect.top.toFloat(), + viewRect.left + txtView.right.toFloat() + DECORATION_PADDING, + viewRect.bottom - bottomSpacing.toFloat() + ) + + val path = when (MessageType.values()[viewType]) { + MessageType.SEND -> { + paint.color = colorSender + Path().apply { + moveTo(rectF.right + 40F, rectF.bottom) + lineTo(rectF.left, rectF.bottom) + lineTo(rectF.left, rectF.top) + lineTo(rectF.right, rectF.top) + quadTo(rectF.right, (rectF.top + rectF.bottom) / 2, rectF.right + 40F, rectF.bottom) + close() + } + } + + MessageType.RECEIVE -> { + paint.color = colorReceiver + Path().apply { + moveTo(rectF.left - CURVE_LENGTH, rectF.bottom) + lineTo(rectF.right, rectF.bottom) + lineTo(rectF.right, rectF.top) + lineTo(rectF.left, rectF.top) + quadTo(rectF.left, (rectF.top + rectF.bottom) / 2, rectF.left - 20F, rectF.bottom) + close() + } + } + } + canvas.drawPath(path, paint) + } + } + + override fun getItemOffsets( + outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State + ) { + outRect.bottom += bottomSpacing + } + + companion object { + const val DECORATION_PADDING = 30F + private const val CURVE_LENGTH = 30F + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/decorations/ContactDecoration.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/decorations/ContactDecoration.kt new file mode 100644 index 0000000..a148ab2 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/decorations/ContactDecoration.kt @@ -0,0 +1,22 @@ +package com.krunal.demo.recyclerview.decorations + +import android.content.Context +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.RecyclerView + +class ContactDecoration(context: Context, orientation: Int): DividerItemDecoration(context, orientation) { + + var horizontalMargin = 16 + + override fun getItemOffsets( + outRect: Rect, + view: View, + parent: RecyclerView, + state: RecyclerView.State + ) { + outRect.top = horizontalMargin + outRect.bottom = horizontalMargin + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/decorations/SpaceDecoration.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/decorations/SpaceDecoration.kt new file mode 100644 index 0000000..2441dd0 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/decorations/SpaceDecoration.kt @@ -0,0 +1,31 @@ +package com.krunal.demo.recyclerview.decorations + +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.ItemDecoration + +class SpaceDecoration(val orientation: Int = VERTICAL) : ItemDecoration() { + + var spacing = 10 + + override fun getItemOffsets( + outRect: Rect, + view: View, + parent: RecyclerView, + state: RecyclerView.State + ) { + if (orientation == VERTICAL) { + outRect.top = spacing + outRect.bottom = spacing + } else { + outRect.left = spacing + outRect.right = spacing + } + } + + companion object { + const val VERTICAL = 0 + const val HORIZONTAL = 1 + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/extentions/RecyclerViewExtentions.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/extentions/RecyclerViewExtentions.kt new file mode 100644 index 0000000..75b7e01 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/extentions/RecyclerViewExtentions.kt @@ -0,0 +1,12 @@ +package com.krunal.demo.recyclerview.extentions + +import androidx.recyclerview.widget.RecyclerView + +private const val SCROLL_DIRECTION_RIGHT = 1 +private const val SCROLL_DIRECTION_LEFT = -1 + +val RecyclerView.canScrollRight: Boolean + get() = canScrollHorizontally(SCROLL_DIRECTION_RIGHT) + +val RecyclerView.canScrollLeft: Boolean + get() = canScrollHorizontally(SCROLL_DIRECTION_LEFT) \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/fragments/CalculateFragment.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/fragments/CalculateFragment.kt new file mode 100644 index 0000000..59d0ac4 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/fragments/CalculateFragment.kt @@ -0,0 +1,95 @@ +package com.krunal.demo.recyclerview.fragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import com.krunal.demo.databinding.FragmentCalculateBinding +import com.krunal.demo.recyclerview.adapters.CalculationAdapter +import com.krunal.demo.recyclerview.decorations.SpaceDecoration +import com.krunal.demo.recyclerview.helpers.ImagePickerHelper +import com.krunal.demo.recyclerview.listeners.OnItemChangeListener +import com.krunal.demo.recyclerview.viewmodels.CalculateViewModel +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch + +class CalculateFragment : Fragment(), OnItemChangeListener { + + private lateinit var binding: FragmentCalculateBinding + private val viewModel: CalculateViewModel by viewModels() + private lateinit var imagePickerHelper: ImagePickerHelper + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentCalculateBinding.inflate(inflater, container, false) + binding.lifecycleOwner = viewLifecycleOwner + binding.viewModel = viewModel + imagePickerHelper = ImagePickerHelper(requireActivity()) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setupCalculations() + } + + private fun setupCalculations() { + val adapter = CalculationAdapter().apply { + onItemChangeListener = this@CalculateFragment + } + + binding.rvMain.apply { + this.adapter = adapter + addItemDecoration(SpaceDecoration()) + } + + binding.btnAddCalculation.setOnClickListener { + viewModel.addCalculation() + } + + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.CREATED) { + viewModel.calculations.collectLatest { list -> + adapter.submitList(list) + } + } + } + } + + override fun onCalculationRemove(position: Int) { + viewModel.removeCalculation(position) + } + + override fun onNumberChange(position: Int, num1: Double?, num2: Double?) { + viewModel.updateNumber(position, num1, num2) + } + + override fun onValueRemove(position: Int, valuePosition: Int) { + viewModel.removeValue(position, valuePosition) + } + + override fun onValueAdd(position: Int, value: Int) { + viewModel.addValue(position, value) + } + + override fun onValueChange(position: Int, valuePosition: Int, value: Int) { + viewModel.updateValue(position, valuePosition, value) + } + + override fun onImageAdd(position: Int) { + imagePickerHelper.pickImage { uri -> + viewModel.addImage(position, uri) + } + } + + override fun onImageRemove(position: Int, imagePosition: Int) { + viewModel.removeImage(position, imagePosition) + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/fragments/ChatDetailFragment.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/fragments/ChatDetailFragment.kt new file mode 100644 index 0000000..05e40de --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/fragments/ChatDetailFragment.kt @@ -0,0 +1,95 @@ +package com.krunal.demo.recyclerview.fragments + +import android.graphics.Rect +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.ItemDecoration +import com.krunal.demo.databinding.FragmentChatDetailBinding +import com.krunal.demo.recyclerview.adapters.ProfileImagesAdapter +import com.krunal.demo.recyclerview.decorations.SpaceDecoration +import com.krunal.demo.recyclerview.listeners.OnChatChangeListener +import com.krunal.demo.recyclerview.viewmodels.ChatDetailsViewModel +import com.krunal.demo.uicomponents.extentions.dpFormat +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch + +class ChatDetailFragment : Fragment(), OnChatChangeListener { + + private lateinit var binding: FragmentChatDetailBinding + private val viewModel: ChatDetailsViewModel by viewModels() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentChatDetailBinding.inflate(inflater, container, false) + binding.lifecycleOwner = viewLifecycleOwner + binding.mainViewModel = viewModel + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setupUI() + } + + private fun setupUI() { + val profileAdapter = ProfileImagesAdapter().apply { + onChatChangeListener = this@ChatDetailFragment + } + + val chatsAdapter = ProfileImagesAdapter().apply { + shouldTruncate = true + } + + binding.apply { + rvProfile.adapter = profileAdapter + rvProfile.addItemDecoration(SpaceDecoration(SpaceDecoration.HORIZONTAL)) + + rvChats.adapter = chatsAdapter + rvChats.addItemDecoration(object : ItemDecoration() { + override fun getItemOffsets( + outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State + ) { + if (parent.getChildAdapterPosition(view) == 0) return + + outRect.offset(-(12).dpFormat(requireContext()), 0) + } + }) + } + + + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.CREATED) { + launch { + viewModel.chatDetails.collectLatest { list -> + profileAdapter.submitList(list.map { + it.contactDetail.profileImage + }) + } + } + + launch { + viewModel.currentDetail.collectLatest { detail -> + detail?.chats?.let { chats -> + chatsAdapter.shouldTruncate = true + chatsAdapter.submitList(chats.map { it.profileImage }) + } + } + } + } + } + } + + override fun onProfileClick(position: Int) { + viewModel.changeContactDetail(position) + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/fragments/ChattingFragment.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/fragments/ChattingFragment.kt new file mode 100644 index 0000000..46a2a24 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/fragments/ChattingFragment.kt @@ -0,0 +1,72 @@ +package com.krunal.demo.recyclerview.fragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.krunal.demo.databinding.FragmentChattingBinding +import com.krunal.demo.recyclerview.adapters.ChatAdapter +import com.krunal.demo.recyclerview.decorations.ChatItemDecoration +import com.krunal.demo.recyclerview.models.Message +import com.krunal.demo.recyclerview.viewmodels.ChattingFragmentViewModel +import kotlinx.coroutines.launch + +class ChattingFragment : Fragment() { + + private lateinit var binding: FragmentChattingBinding + private val viewModel: ChattingFragmentViewModel by viewModels() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentChattingBinding.inflate(layoutInflater) + binding.lifecycleOwner = viewLifecycleOwner + binding.viewModel = viewModel + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupUI() + } + + private fun setupUI() { + setupRecyclerView() + } + + private fun setupRecyclerView() { + val chatAdapter = ChatAdapter() + chatAdapter.submitList(Message.dummyData) + + binding.rvChat.apply { + adapter = chatAdapter + layoutManager = LinearLayoutManager(requireContext()).apply { + stackFromEnd = true + } + setHasFixedSize(true) + addItemDecoration(ChatItemDecoration()) + } + + chatAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { + override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) { + super.onItemRangeRemoved(positionStart, itemCount) + viewModel.removeMessage(positionStart) + } + }) + + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.CREATED) { + viewModel.messages.collect { messages -> + chatAdapter.submitList(messages) + } + } + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/fragments/ContactDetailFragment.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/fragments/ContactDetailFragment.kt new file mode 100644 index 0000000..c9d00ef --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/fragments/ContactDetailFragment.kt @@ -0,0 +1,64 @@ +package com.krunal.demo.recyclerview.fragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import androidx.recyclerview.widget.DividerItemDecoration +import com.krunal.demo.databinding.FragmentContactDetailBinding +import com.krunal.demo.recyclerview.adapters.ContactDetailsAdapter +import com.krunal.demo.recyclerview.decorations.ContactDecoration +import com.krunal.demo.recyclerview.viewmodels.ContactDetailsViewModel +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch + +class ContactDetailFragment : Fragment() { + + private lateinit var binding: FragmentContactDetailBinding + private val viewModel: ContactDetailsViewModel by viewModels() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentContactDetailBinding.inflate(inflater, container, false) + binding.lifecycleOwner = viewLifecycleOwner + binding.viewModel = viewModel + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setupContactDetails() + } + + private fun setupContactDetails() { + val adapter = ContactDetailsAdapter() + binding.rvContactDetails.apply { + this.adapter = adapter + addItemDecoration(ContactDecoration(requireContext(), DividerItemDecoration.VERTICAL)) + setOnScrollChangeListener { _, _, scrollY, _, oldScrollY -> + if (scrollY - oldScrollY > SCROLL_THRESHOLD) { + adapter.collapseAll() + } + } + } + + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.CREATED) { + viewModel.contactDetail.collectLatest { list -> + adapter.submitList(list) + } + } + } + } + + companion object { + const val SCROLL_THRESHOLD = 10 + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/fragments/YoutubeFragment.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/fragments/YoutubeFragment.kt new file mode 100644 index 0000000..7faa1cf --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/fragments/YoutubeFragment.kt @@ -0,0 +1,69 @@ +package com.krunal.demo.recyclerview.fragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.viewpager2.widget.ViewPager2 +import com.krunal.demo.R +import com.krunal.demo.databinding.FragmentYoutubeBinding +import com.krunal.demo.recyclerview.adapters.YoutubeFragmentAdapter +import com.krunal.demo.recyclerview.viewmodels.YoutubeFragmentViewModel + +class YoutubeFragment : Fragment() { + + private lateinit var binding: FragmentYoutubeBinding + private val viewModel: YoutubeFragmentViewModel by viewModels() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentYoutubeBinding.inflate(layoutInflater) + binding.lifecycleOwner = viewLifecycleOwner + binding.viewModel = viewModel + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupUI() + } + + private fun setupUI() { + setupViewPager() + setupBottomNav() + } + + private fun setupViewPager() { + binding.vpFragment.adapter = YoutubeFragmentAdapter( + listOf(YoutubeHomeFragment(), YoutubeHomeFragment(), YoutubeLibraryFragment()), + requireActivity() + ) + + binding.vpFragment.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { + override fun onPageSelected(position: Int) { + super.onPageSelected(position) + binding.bottomNavigation.menu.findItem( + when (position) { + 0 -> R.id.actionHome + 1 -> R.id.actionSubscription + else -> R.id.actionLibrary + } + ).isChecked = true + } + }) + } + + private fun setupBottomNav() { + binding.bottomNavigation.setOnItemSelectedListener { + when (it.itemId) { + R.id.actionHome -> binding.vpFragment.currentItem = 0 + R.id.actionSubscription -> binding.vpFragment.currentItem = 1 + R.id.actionLibrary -> binding.vpFragment.currentItem = 2 + } + true + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/fragments/YoutubeHomeFragment.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/fragments/YoutubeHomeFragment.kt new file mode 100644 index 0000000..acc613e --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/fragments/YoutubeHomeFragment.kt @@ -0,0 +1,66 @@ +package com.krunal.demo.recyclerview.fragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import androidx.recyclerview.widget.DividerItemDecoration +import com.krunal.demo.databinding.FragmentYoutubeHomeBinding +import com.krunal.demo.recyclerview.adapters.FeedAdapter +import com.krunal.demo.recyclerview.listeners.NestedScrollListener +import com.krunal.demo.recyclerview.viewmodels.YoutubeHomeFragmentViewModel +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch + +class YoutubeHomeFragment : Fragment() { + + private lateinit var binding: FragmentYoutubeHomeBinding + private val viewModel: YoutubeHomeFragmentViewModel by viewModels() + private lateinit var feedAdapter: FeedAdapter + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentYoutubeHomeBinding.inflate(layoutInflater) + binding.lifecycleOwner = viewLifecycleOwner + binding.viewModel = viewModel + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupUI() + } + + private fun setupUI() { + setupRecyclerView() + } + + private fun setupRecyclerView() { + feedAdapter = FeedAdapter() + feedAdapter.submitFeeds(viewModel.videoDetails.value) + binding.rvHome.adapter = feedAdapter + binding.rvHome.addItemDecoration( + DividerItemDecoration( + context, + DividerItemDecoration.VERTICAL + ) + ) + binding.rvHome.smoothScrollToPosition(4) + + binding.rvHome.addOnItemTouchListener(NestedScrollListener()) + + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.CREATED) { + viewModel.videoDetails.collectLatest { feeds -> + feedAdapter.submitFeeds(feeds) + } + } + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/fragments/YoutubeLibraryFragment.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/fragments/YoutubeLibraryFragment.kt new file mode 100644 index 0000000..f6c4d2d --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/fragments/YoutubeLibraryFragment.kt @@ -0,0 +1,100 @@ +package com.krunal.demo.recyclerview.fragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager +import com.krunal.demo.R +import com.krunal.demo.databinding.FragmentYoutubeLibraryBinding +import com.krunal.demo.recyclerview.adapters.LibraryAdapter +import com.krunal.demo.recyclerview.listeners.PaginationListener +import com.krunal.demo.recyclerview.viewmodels.YoutubeLibraryFragmentViewModel +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch + + +class YoutubeLibraryFragment : Fragment() { + + private lateinit var binding: FragmentYoutubeLibraryBinding + private val viewModel: YoutubeLibraryFragmentViewModel by viewModels() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentYoutubeLibraryBinding.inflate(layoutInflater) + binding.lifecycleOwner = viewLifecycleOwner + binding.viewModel = viewModel + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupUI() + } + + private fun setupUI() { + setupToolbar() + setupRecyclerView() + } + + private fun setupToolbar() { + binding.toolBar.setOnMenuItemClickListener { + if (it.itemId == R.id.actionToggleLayout) { + it.isChecked = it.isChecked.not() + + binding.rvLibrary.layoutManager = if (it.isChecked) GridLayoutManager( + requireContext(), SPAN_COUNT + ) else LinearLayoutManager(requireContext()) + } + true + } + } + + private fun setupRecyclerView() { + val libraryAdapter = LibraryAdapter() + binding.rvLibrary.adapter = libraryAdapter + val layoutManager = LinearLayoutManager(requireContext()) + + binding.rvLibrary.layoutManager = layoutManager + binding.rvLibrary.addOnScrollListener(object : PaginationListener(layoutManager) { + + override val isLastPage: Boolean + get() = viewModel.isLastPage + override val isLoading: Boolean + get() = viewModel.isLoading.value + + override fun loadMoreItems() { + viewModel.loadMoreData() + } + }) + + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.CREATED) { + launch { + viewModel.videoDetails.collectLatest { details -> + libraryAdapter.addList(details) + } + } + + launch { + viewModel.isLoading.collectLatest { isLoading -> + if (isLoading) libraryAdapter.showLoading() else libraryAdapter.hideLoading() + } + } + } + } + + viewModel.loadMoreData() + } + + companion object { + private const val SPAN_COUNT = 2 + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/helpers/ImagePickerHelper.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/helpers/ImagePickerHelper.kt new file mode 100644 index 0000000..dee0d0b --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/helpers/ImagePickerHelper.kt @@ -0,0 +1,33 @@ +package com.krunal.demo.recyclerview.helpers + +import android.net.Uri +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.fragment.app.FragmentActivity + +class ImagePickerHelper(private val activity: FragmentActivity) { + + private val activityResultLauncher: ActivityResultLauncher = + registerActivityResultLauncher() + private var onImagePick: ((uri: Uri) -> Unit)? = null + + private fun registerActivityResultLauncher(): ActivityResultLauncher { + return activity.registerForActivityResult(ActivityResultContracts.GetContent()) { uri -> + if (uri != null) { + onImagePick?.invoke(uri) + } +// uri?.let(onImagePick) + } + } + + fun pickImage(onPick: (uri: Uri) -> Unit) { + onImagePick = onPick + activityResultLauncher.launch(ANY_IMAGE_TYPE) + } + + companion object { + + const val IMAGE_TYPE = "image" + const val ANY_IMAGE_TYPE = "$IMAGE_TYPE/*" + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/listeners/CalculationDiffCallback.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/listeners/CalculationDiffCallback.kt new file mode 100644 index 0000000..89dfc99 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/listeners/CalculationDiffCallback.kt @@ -0,0 +1,42 @@ +package com.krunal.demo.recyclerview.listeners + +import androidx.recyclerview.widget.DiffUtil +import com.krunal.demo.recyclerview.models.Calculation +import com.krunal.demo.recyclerview.models.Payload + +object CalculationDiffCallback : DiffUtil.ItemCallback() { + + override fun areItemsTheSame(oldItem: Calculation, newItem: Calculation): Boolean = + oldItem.id == newItem.id + + override fun areContentsTheSame(oldItem: Calculation, newItem: Calculation): Boolean = + oldItem == newItem + + override fun getChangePayload(oldItem: Calculation, newItem: Calculation): Any? = when { + newItem.num1 != oldItem.num1 -> Payload.ChangeNumber( + newItem.num1, oldItem.num2 + ) + + newItem.num2 != oldItem.num2 -> Payload.ChangeNumber(oldItem.num1, newItem.num2) + + newItem.images.count() == oldItem.images.count() - 1 -> Payload.RemoveImage( + oldItem.images.withIndex().indexOfFirst { (index, value) -> + value != newItem.images.elementAtOrNull(index) + }) + + newItem.images.count() == oldItem.images.count() + 1 -> Payload.AddImage(newItem.images.last()) + + newItem.additionalNums.count() == oldItem.additionalNums.count() - 1 -> null + + newItem.additionalNums.count() == oldItem.additionalNums.count() + 1 -> Payload.AddValue(1) + + newItem.additionalNums != oldItem.additionalNums -> { + val index = newItem.additionalNums.withIndex().indexOfFirst { (index, value) -> + value != oldItem.additionalNums[index] + } + Payload.ChangeValue(index, newItem.additionalNums[index]) + } + + else -> null + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/listeners/ChatDiffCallback.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/listeners/ChatDiffCallback.kt new file mode 100644 index 0000000..ca9bc36 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/listeners/ChatDiffCallback.kt @@ -0,0 +1,18 @@ +package com.krunal.demo.recyclerview.listeners + +import androidx.recyclerview.widget.DiffUtil +import com.krunal.demo.recyclerview.models.Message + +class ChatDiffCallback( + private val oldMessages: List, + private val newMessages: List +): DiffUtil.Callback() { + + override fun getOldListSize(): Int = oldMessages.count() + + override fun getNewListSize(): Int = newMessages.count() + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean = oldMessages[oldItemPosition] === newMessages[newItemPosition] + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean = oldMessages[oldItemPosition] == newMessages[newItemPosition] +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/listeners/ExpansionListener.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/listeners/ExpansionListener.kt new file mode 100644 index 0000000..9012d00 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/listeners/ExpansionListener.kt @@ -0,0 +1,8 @@ +package com.krunal.demo.recyclerview.listeners + +interface ExpansionListener { + + fun onExpend() + + fun onCollapse() +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/listeners/NestedScrollListener.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/listeners/NestedScrollListener.kt new file mode 100644 index 0000000..b6a3860 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/listeners/NestedScrollListener.kt @@ -0,0 +1,38 @@ +package com.krunal.demo.recyclerview.listeners + +import android.view.MotionEvent +import androidx.recyclerview.widget.RecyclerView +import com.krunal.demo.recyclerview.extentions.canScrollLeft +import com.krunal.demo.recyclerview.extentions.canScrollRight + +class NestedScrollListener : RecyclerView.SimpleOnItemTouchListener() { + + var startX = 0f + + override fun onInterceptTouchEvent( + recyclerView: RecyclerView, event: MotionEvent + ): Boolean { + when (event.action) { + MotionEvent.ACTION_DOWN -> { + startX = event.x + } + + MotionEvent.ACTION_MOVE -> { + val isScrollingRight = event.x < startX + val scrollItemsToRight = isScrollingRight && recyclerView.canScrollRight + val scrollItemsToLeft = !isScrollingRight && recyclerView.canScrollLeft + val disallowIntercept = scrollItemsToRight || scrollItemsToLeft + recyclerView.parent.requestDisallowInterceptTouchEvent( + disallowIntercept + ) + } + + MotionEvent.ACTION_UP -> { + startX = 0f + } + + else -> Unit + } + return false + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/listeners/OnChatChangeListener.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/listeners/OnChatChangeListener.kt new file mode 100644 index 0000000..4c22c80 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/listeners/OnChatChangeListener.kt @@ -0,0 +1,6 @@ +package com.krunal.demo.recyclerview.listeners + +interface OnChatChangeListener { + + fun onProfileClick(position: Int) +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/listeners/OnItemChangeListener.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/listeners/OnItemChangeListener.kt new file mode 100644 index 0000000..9d1d043 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/listeners/OnItemChangeListener.kt @@ -0,0 +1,21 @@ +package com.krunal.demo.recyclerview.listeners + +import android.net.Uri +import androidx.annotation.DrawableRes + +interface OnItemChangeListener { + + fun onCalculationRemove(position: Int) + + fun onNumberChange(position: Int, num1: Double?, num2: Double?) + + fun onValueRemove(position: Int, valuePosition: Int) + + fun onValueAdd(position: Int, value: Int) + + fun onValueChange(position: Int, valuePosition: Int, value: Int) + + fun onImageRemove(position: Int, imagePosition: Int) + + fun onImageAdd(position: Int) +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/listeners/PaginationListener.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/listeners/PaginationListener.kt new file mode 100644 index 0000000..7ba5de1 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/listeners/PaginationListener.kt @@ -0,0 +1,32 @@ +package com.krunal.demo.recyclerview.listeners + +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView + +abstract class PaginationListener(private val layoutManager: LinearLayoutManager) : + RecyclerView.OnScrollListener() { + + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + super.onScrolled(recyclerView, dx, dy) + + val visibleItemCount = layoutManager.childCount + val totalItemCount = layoutManager.itemCount + val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition() + + if (isLoading || isLastPage) return + + if (visibleItemCount + firstVisibleItemPosition >= totalItemCount && firstVisibleItemPosition >= 0 && totalItemCount >= PAGE_SIZE) { + loadMoreItems() + } + } + + abstract val isLastPage: Boolean + + abstract val isLoading: Boolean + + abstract fun loadMoreItems() + + companion object { + const val PAGE_SIZE = 14 + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/Calculation.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/Calculation.kt new file mode 100644 index 0000000..f6653b4 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/Calculation.kt @@ -0,0 +1,27 @@ +package com.krunal.demo.recyclerview.models + +import android.net.Uri + +data class Calculation( + val num1: Double, + val num2: Double, + val additionalNums: List = emptyList(), + val images: List = emptyList(), + val id: Int = currentId +) { + + val total: Double + get() = num1 * num2 + additionalNums.sum() + + companion object { + + private var currentId: Int = -1 + get() = ++field + + val dummyData: List = listOf( + Calculation( + 50.0, 90.0, listOf(1) + ) + ) + } +} diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/ChatDetail.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/ChatDetail.kt new file mode 100644 index 0000000..9785451 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/ChatDetail.kt @@ -0,0 +1,24 @@ +package com.krunal.demo.recyclerview.models + +data class ChatDetail( + val contactDetail: ContactDetail, val chats: List = emptyList() +) { + + companion object { + + val dummyDetails: List = buildList { + repeat(20) { + add( + ChatDetail(ContactDetail.dummyData.random(), List(3 * it) { + ContactDetail.dummyData.random() + }) + ) + add( + ChatDetail(ContactDetail.dummyData.random(), List(2 * it) { + ContactDetail.dummyData.random() + }) + ) + } + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/Comment.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/Comment.kt new file mode 100644 index 0000000..0f07c5e --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/Comment.kt @@ -0,0 +1,25 @@ +package com.krunal.demo.recyclerview.models + +import androidx.annotation.DrawableRes +import com.krunal.demo.R + +data class Comment( + val name: String, @DrawableRes val profileImage: Int, val text: String, val uploadDate: String +) { + companion object { + val dummyComments: List = buildList { + repeat(4) { + add( + Comment( + "Movie Snaps", R.drawable.android_dev, "Great song", "10 days ago" + ) + ) + add( + Comment( + "Nossen Kanter Snaps", R.drawable.profile, "0:23 best part", "1 week ago" + ) + ) + } + } + } +} diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/ContactDetail.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/ContactDetail.kt new file mode 100644 index 0000000..4b5552b --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/ContactDetail.kt @@ -0,0 +1,45 @@ +package com.krunal.demo.recyclerview.models + +import androidx.annotation.DrawableRes +import com.krunal.demo.R +import com.krunal.demo.recyclerview.models.enums.Gender + +data class ContactDetail( + val name: String, + @DrawableRes val profileImage: Int, + val age: Int, + val gender: Gender, + val email: String, + var isExpanded: Boolean = false +) { + + companion object { + val dummyData: List = buildList { + repeat(20) { + add( + ContactDetail( + "Krunal Patel", R.drawable.profile, 20, Gender.MALE, "krunal@protonmail.com" + ) + ) + add( + ContactDetail( + "Harsh Mehta", + R.drawable.thumbnail1, + 21, + Gender.MALE, + "harsh@gmail.com" + ) + ) + add( + ContactDetail( + "Ankur Gamit", + R.drawable.dark_forest, + 23, + Gender.MALE, + "ankur@gmail.com" + ) + ) + } + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/Feed.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/Feed.kt new file mode 100644 index 0000000..bf28d9a --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/Feed.kt @@ -0,0 +1,172 @@ +package com.krunal.demo.recyclerview.models + +import androidx.annotation.DrawableRes +import com.krunal.demo.R +import com.krunal.demo.recyclerview.listeners.PaginationListener +import com.krunal.demo.recyclerview.models.enums.FeedType + +sealed interface Feed { + val type: FeedType + + companion object { + val dummyData: List + get() = buildList { + repeat(10) { + add( + VideoDetails( + "Running Up That Hill (Kate Bush)", + 4000, + R.drawable.running_up_that_hill, + "Netflix", + "2.5M views", + "11 months ago", + R.drawable.netflix_logo, + FeedType.VIDEO + ) + ) + add( + VideoDetails( + "Kaise Ab Kahein | Gutar Gu", + 134, + R.drawable.thumbnail1, + "Amazon miniTV", + "430K views", + "1 months ago", + R.drawable.netflix_logo, + FeedType.VIDEO + ) + ) + add( + VideoDetails( + "Running Up That Hill (Kate Bush)", + 4000, + R.drawable.running_up_that_hill, + "Netflix", + "2.5M views", + "11 months ago", + R.drawable.netflix_logo, + FeedType.VIDEO + ) + ) + add( + CommunityPost( + "Android Developers", + """ + πŸŽͺ Coming to you live from Shoreline, it's #GoogleIO! + + Join developers around the world for thoughtful discussions, interactive learning, and a first look at our latest product updates. + """.trimIndent(), + R.drawable.android_dev, + "12 days ago", + 235, + -1, + listOf( + R.drawable.google_io, + R.drawable.google_io, + R.drawable.google_io, + R.drawable.google_io + ), + Comment.dummyComments + ) + ) + add( + VideoDetails( + "Running Up That Hill (Kate Bush)", + 4000, + R.drawable.running_up_that_hill, + "Netflix", + "2.5M views", + "11 months ago", + R.drawable.netflix_logo, + FeedType.SHORT_VIDEO + ) + ) + add( + VideoDetails( + "Kaise Ab Kahein | Gutar Gu", + 134, + R.drawable.thumbnail1, + "Amazon miniTV", + "430K views", + "1 months ago", + R.drawable.netflix_logo, + FeedType.VIDEO + ) + ) + add(Recommendation("Suggtion for you", List(4) { + VideoDetails( + "Kaise Ab Kahein | Gutar Gu", + 134, + R.drawable.thumbnail1, + "Amazon miniTV", + "430K views", + "1 months ago", + R.drawable.netflix_logo, + FeedType.VIDEO + ) + })) + } + } + } +} + +data class VideoDetails( + val title: String, + val time: Long, + @DrawableRes val thumbnail: Int, + val author: String, + val views: String, + val uploadDate: String, + @DrawableRes val profileImage: Int, + override val type: FeedType +) : Feed { + + companion object { + val dummyData: List = buildList { + repeat(PaginationListener.PAGE_SIZE / 2) { + add( + VideoDetails( + "[$it]: Running Up That Hill (Kate Bush)", + 4000, + R.drawable.running_up_that_hill, + "Netflix", + "2.5M views", + "11 months ago", + R.drawable.netflix_logo, + FeedType.SHORT_VIDEO + ) + ) + add( + VideoDetails( + "[$it]: Kaise Ab Kahein | Gutar Gu", + 134, + R.drawable.thumbnail1, + "Amazon miniTV", + "430K views", + "1 months ago", + R.drawable.netflix_logo, + FeedType.VIDEO + ) + ) + } + } + } +} + +data class CommunityPost( + val name: String, + val description: String?, + @DrawableRes val profileImage: Int, + val uploadDate: String, + val likes: Int, + val dislikes: Int, + val images: List = emptyList(), + val comments: List = emptyList(), + override val type: FeedType = FeedType.COMMUNITY_POST +) : Feed + +data class Recommendation( + val name: String, + val videos: List, + override val type: FeedType = FeedType.RECOMMENDATION +) : Feed diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/Message.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/Message.kt new file mode 100644 index 0000000..0114b5d --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/Message.kt @@ -0,0 +1,20 @@ +package com.krunal.demo.recyclerview.models + +import androidx.annotation.DrawableRes +import com.krunal.demo.R +import com.krunal.demo.recyclerview.models.enums.MessageType + +data class Message( + val name: String, + @DrawableRes val profileImage: Int, + val text: String, + val messageType: MessageType +) { + + companion object { + val dummyData: List = listOf( + Message("Sender", R.drawable.profile, "Hi", MessageType.SEND), + Message("Receiver", R.drawable.android_dev, "Hello", MessageType.RECEIVE) + ) + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/Payload.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/Payload.kt new file mode 100644 index 0000000..733ce7f --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/Payload.kt @@ -0,0 +1,18 @@ +package com.krunal.demo.recyclerview.models + +import android.net.Uri + +sealed interface Payload { + + data class AddImage(val uri: Uri): Payload + + data class RemoveImage(val position: Int): Payload + + data class ChangeNumber(val num1: Double, val num2: Double): Payload + + data class AddValue(val value: Int): Payload + + data class RemoveValue(val position: Int): Payload + + data class ChangeValue(val position: Int, val value: Int): Payload +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/enums/FeedType.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/enums/FeedType.kt new file mode 100644 index 0000000..a06dd11 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/enums/FeedType.kt @@ -0,0 +1,5 @@ +package com.krunal.demo.recyclerview.models.enums + +enum class FeedType { + VIDEO, SHORT_VIDEO, RECOMMENDATION, COMMUNITY_POST +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/enums/Gender.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/enums/Gender.kt new file mode 100644 index 0000000..503c683 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/enums/Gender.kt @@ -0,0 +1,11 @@ +package com.krunal.demo.recyclerview.models.enums + +import java.util.Locale + +enum class Gender { + + MALE, FEMALE, OTHER; + + val titleName: String = + name.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } +} diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/enums/HistoryViewType.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/enums/HistoryViewType.kt new file mode 100644 index 0000000..1446090 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/enums/HistoryViewType.kt @@ -0,0 +1,5 @@ +package com.krunal.demo.recyclerview.models.enums + +enum class HistoryViewType { + VIDEO, LOADING +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/enums/MessageType.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/enums/MessageType.kt new file mode 100644 index 0000000..5925ca6 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/enums/MessageType.kt @@ -0,0 +1,5 @@ +package com.krunal.demo.recyclerview.models.enums + +enum class MessageType { + SEND, RECEIVE +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/enums/ProfileViewType.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/enums/ProfileViewType.kt new file mode 100644 index 0000000..e3d34fd --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/models/enums/ProfileViewType.kt @@ -0,0 +1,5 @@ +package com.krunal.demo.recyclerview.models.enums + +enum class ProfileViewType { + IMAGE, COUNT +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/viewholders/BaseViewHolder.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/viewholders/BaseViewHolder.kt new file mode 100644 index 0000000..99c5f45 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/viewholders/BaseViewHolder.kt @@ -0,0 +1,10 @@ +package com.krunal.demo.recyclerview.viewholders + +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.krunal.demo.databinding.FragmentChattingBinding + +abstract class BaseViewHolder(view: View): RecyclerView.ViewHolder(view) { + + +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/viewholders/VideoViewHolder.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/viewholders/VideoViewHolder.kt new file mode 100644 index 0000000..f9c5978 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/viewholders/VideoViewHolder.kt @@ -0,0 +1,12 @@ +package com.krunal.demo.recyclerview.viewholders + +import androidx.recyclerview.widget.RecyclerView +import com.krunal.demo.databinding.FeedVideoLayoutBinding +import com.krunal.demo.recyclerview.models.VideoDetails + +class VideoViewHolder(val binding: FeedVideoLayoutBinding) : RecyclerView.ViewHolder(binding.root) { + + fun bind(videoDetails: VideoDetails) { + binding.videoDetail = videoDetails + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/viewmodels/CalculateViewModel.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/viewmodels/CalculateViewModel.kt new file mode 100644 index 0000000..aaa7e81 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/viewmodels/CalculateViewModel.kt @@ -0,0 +1,111 @@ +package com.krunal.demo.recyclerview.viewmodels + +import android.net.Uri +import androidx.annotation.DrawableRes +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.krunal.demo.recyclerview.models.Calculation +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch + +class CalculateViewModel : ViewModel() { + + private val _calculations: MutableStateFlow> = MutableStateFlow(emptyList()) + val calculations: StateFlow> = _calculations + + init { + setupInitialValues() + } + + private fun setupInitialValues() { + viewModelScope.launch { + _calculations.emit(Calculation.dummyData) + } + } + + fun updateCalculationList(calculations: List) { + viewModelScope.launch { + _calculations.emit(calculations) + } + } + + fun updateNumber(position: Int, num1: Double?, num2: Double?) { + viewModelScope.launch { + val list = calculations.value.toMutableList() + if (num1 != null ) { + list[position] = list[position].copy(num1 = num1) + } else if (num2 != null) { + list[position] = list[position].copy(num2 = num2) + } + _calculations.emit(list) + } + } + + fun addValue(position: Int, value: Int) { + viewModelScope.launch { + val list = calculations.value.toMutableList() + list[position] = + list[position].copy(additionalNums = list[position].additionalNums + value) + _calculations.emit(list) + } + } + + fun removeValue(position: Int, valuePosition: Int) { + viewModelScope.launch { + val list = calculations.value.toMutableList() + list[position] = list[position].copy( + additionalNums = list[position].additionalNums - list[position].additionalNums[valuePosition] + ) + _calculations.emit(list) + } + } + + fun updateValue(position: Int, valuePosition: Int, value: Int) { + viewModelScope.launch { + val list = calculations.value.toMutableList() + val additionalNums = list[position].additionalNums.toMutableList() + additionalNums[valuePosition] = value + list[position] = list[position].copy(additionalNums = additionalNums) + _calculations.emit(list) + } + } + + fun addCalculation() { + viewModelScope.launch { + calculations.value.toMutableList().also { list -> + list.add( + Calculation(0.0, 0.0) + ) + _calculations.emit(list) + } + } + } + + fun removeCalculation(position: Int) { + viewModelScope.launch { + calculations.value.toMutableList().also { list -> + list.removeAt(position) + _calculations.emit(list) + } + } + } + + fun addImage(position: Int, uri: Uri) { + viewModelScope.launch { + val list = calculations.value.toMutableList() + list[position] = list[position].copy(images = list[position].images + uri) + _calculations.emit(list) + } + } + + fun removeImage(position: Int, imagePosition: Int) { + viewModelScope.launch { + val list = calculations.value.toMutableList() + list[position] = list[position].copy( + images = list[position].images - list[position].images[imagePosition] + ) + _calculations.emit(list) + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/viewmodels/ChatDetailsViewModel.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/viewmodels/ChatDetailsViewModel.kt new file mode 100644 index 0000000..04f9944 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/viewmodels/ChatDetailsViewModel.kt @@ -0,0 +1,35 @@ +package com.krunal.demo.recyclerview.viewmodels + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.krunal.demo.recyclerview.models.ChatDetail +import com.krunal.demo.recyclerview.models.ContactDetail +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch + +class ChatDetailsViewModel : ViewModel() { + + private val _chatDetails: MutableStateFlow> = + MutableStateFlow(emptyList()) + val chatDetails: StateFlow> = _chatDetails + + private val _currentDetail: MutableStateFlow = MutableStateFlow(null) + val currentDetail: StateFlow = _currentDetail + + init { + setupInitialValues() + } + + private fun setupInitialValues() { + viewModelScope.launch { + _chatDetails.emit(ChatDetail.dummyDetails) + } + } + + fun changeContactDetail(position: Int) { + viewModelScope.launch { + _currentDetail.emit(chatDetails.value[position]) + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/viewmodels/ChattingFragmentViewModel.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/viewmodels/ChattingFragmentViewModel.kt new file mode 100644 index 0000000..518be88 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/viewmodels/ChattingFragmentViewModel.kt @@ -0,0 +1,60 @@ +package com.krunal.demo.recyclerview.viewmodels + +import android.view.View +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.krunal.demo.R +import com.krunal.demo.recyclerview.models.Message +import com.krunal.demo.recyclerview.models.enums.MessageType +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.launch + +class ChattingFragmentViewModel : ViewModel() { + + private val _messages: MutableStateFlow> = MutableStateFlow(emptyList()) + val messages: SharedFlow> = _messages + + val message: MutableStateFlow = MutableStateFlow("") + + private val sender = "Krunal" + private val receiver = "Harsh" + + init { + setupMessages() + } + + private fun setupMessages() { + viewModelScope.launch { + _messages.emit(Message.dummyData) + } + } + + fun sendMessage(view: View) { + viewModelScope.launch { + _messages.emit( + _messages.value + Message( + sender, R.drawable.profile, message.value, MessageType.SEND + ) + ) + message.value = "" + } + } + + fun receiveMessage(view: View) { + viewModelScope.launch { + _messages.emit( + _messages.value + Message( + receiver, R.drawable.android_dev, message.value, MessageType.RECEIVE + ) + ) + message.value = "" + } + } + + fun removeMessage(position: Int) { + viewModelScope.launch { + _messages.emit(_messages.value - _messages.value[position]) + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/viewmodels/ContactDetailsViewModel.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/viewmodels/ContactDetailsViewModel.kt new file mode 100644 index 0000000..23d76e0 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/viewmodels/ContactDetailsViewModel.kt @@ -0,0 +1,25 @@ +package com.krunal.demo.recyclerview.viewmodels + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.krunal.demo.recyclerview.models.ContactDetail +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch + +class ContactDetailsViewModel : ViewModel() { + + private val _contactDetails: MutableStateFlow> = + MutableStateFlow(emptyList()) + val contactDetail: StateFlow> = _contactDetails + + init { + setupInitialValues() + } + + private fun setupInitialValues() { + viewModelScope.launch { + _contactDetails.emit(ContactDetail.dummyData) + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/viewmodels/YoutubeFragmentViewModel.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/viewmodels/YoutubeFragmentViewModel.kt new file mode 100644 index 0000000..84a1f31 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/viewmodels/YoutubeFragmentViewModel.kt @@ -0,0 +1,7 @@ +package com.krunal.demo.recyclerview.viewmodels + +import androidx.lifecycle.ViewModel + +class YoutubeFragmentViewModel: ViewModel() { + +} diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/viewmodels/YoutubeHomeFragmentViewModel.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/viewmodels/YoutubeHomeFragmentViewModel.kt new file mode 100644 index 0000000..313e11f --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/viewmodels/YoutubeHomeFragmentViewModel.kt @@ -0,0 +1,29 @@ +package com.krunal.demo.recyclerview.viewmodels + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.krunal.demo.recyclerview.models.Feed +import com.krunal.demo.recyclerview.models.VideoDetails +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch + +class YoutubeHomeFragmentViewModel : ViewModel() { + + private val _videoDetails: MutableStateFlow> = MutableStateFlow(emptyList()) + val videoDetails: StateFlow> = _videoDetails + + init { + setupInitialValue() + } + + private fun setupInitialValue() { + viewModelScope.launch { + delay(1000) + _videoDetails.emit( + Feed.dummyData + ) + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/recyclerview/viewmodels/YoutubeLibraryFragmentViewModel.kt b/Demo/app/src/main/java/com/krunal/demo/recyclerview/viewmodels/YoutubeLibraryFragmentViewModel.kt new file mode 100644 index 0000000..cb782c1 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/recyclerview/viewmodels/YoutubeLibraryFragmentViewModel.kt @@ -0,0 +1,36 @@ +package com.krunal.demo.recyclerview.viewmodels + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.krunal.demo.recyclerview.listeners.PaginationListener +import com.krunal.demo.recyclerview.models.VideoDetails +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch + +class YoutubeLibraryFragmentViewModel : ViewModel() { + + + private val _videoDetails: MutableSharedFlow> = MutableSharedFlow() + val videoDetails: SharedFlow> = _videoDetails + + private val _isLoading: MutableStateFlow = MutableStateFlow(false) + val isLoading: StateFlow = _isLoading + val isLastPage: Boolean + get() = false //videoDetails.value.size / PaginationListener.PAGE_SIZE < currentPage + var currentPage: Int = 0 + private set + + fun loadMoreData() { + viewModelScope.launch { + _isLoading.emit(true) + delay(1500) + _videoDetails.emit(VideoDetails.dummyData) + currentPage++ + _isLoading.emit(false) + } + } +} diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/AppBarFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/AppBarFragment.kt new file mode 100644 index 0000000..c7fe0e0 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/AppBarFragment.kt @@ -0,0 +1,36 @@ +package com.krunal.demo.uicomponents + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.widget.SearchView +import androidx.fragment.app.Fragment +import com.google.android.material.color.MaterialColors +import com.krunal.demo.R +import com.krunal.demo.databinding.FragmentAppBarBinding +import com.krunal.demo.uicomponents.extentions.getThemeColor + +class AppBarFragment : Fragment(R.layout.fragment_app_bar) { + + private lateinit var binding: FragmentAppBarBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentAppBarBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupAppBars() + } + + private fun setupAppBars() { + binding.tbItems.inflateMenu(R.menu.toolbar_menu) + val searchItem = binding.tbSearch.menu.findItem(R.id.miSearch) + val searchView = searchItem.actionView as SearchView + searchView.isIconified = false + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/ButtonFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/ButtonFragment.kt new file mode 100644 index 0000000..9d3dd7f --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/ButtonFragment.kt @@ -0,0 +1,57 @@ +package com.krunal.demo.uicomponents + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.View.OnClickListener +import android.view.ViewGroup +import android.widget.Toast +import androidx.core.view.children +import androidx.fragment.app.Fragment +import com.google.android.material.button.MaterialButton +import com.krunal.demo.R +import com.krunal.demo.databinding.FragmentButtonBinding + +class ButtonFragment : Fragment(R.layout.fragment_button), OnClickListener { + + private lateinit var binding: FragmentButtonBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentButtonBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupClickListener() + } + + override fun onClick(view: View?) { + (view as? MaterialButton)?.let { + Toast.makeText(requireContext(), "${it.text} clicked", Toast.LENGTH_SHORT).show() + } + } + + private fun setupClickListener() { + + binding.btnNormal.setOnClickListener(this) + binding.btnBordered.setOnClickListener(this) + binding.appCompatButton.setOnClickListener(this) + binding.btnDisabled.setOnClickListener(this) + binding.btnOutlined.setOnClickListener(this) + binding.btnText.setOnClickListener(this) + binding.imgBtnImage.setOnClickListener(this) + binding.btnInfo.setOnClickListener(this) + binding.btnGradient.setOnClickListener(this) + + binding.switchEnable.setOnCheckedChangeListener { btn, checked -> + binding.root.children + .filterNot { it == binding.switchEnable } + .forEach { it.isEnabled = checked } + btn.text = + if (checked) getString(R.string.enabled_switch) else getString(R.string.disabled_switch) + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/CheckboxFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/CheckboxFragment.kt new file mode 100644 index 0000000..329c6dd --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/CheckboxFragment.kt @@ -0,0 +1,58 @@ +package com.krunal.demo.uicomponents + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.CompoundButton +import android.widget.CompoundButton.OnCheckedChangeListener +import android.widget.Toast +import androidx.fragment.app.Fragment +import com.krunal.demo.R +import com.krunal.demo.databinding.FragmentCheckboxBinding + +class CheckboxFragment : Fragment(R.layout.fragment_checkbox), OnCheckedChangeListener { + + private lateinit var binding: FragmentCheckboxBinding + private var selectedLanguages = mutableListOf() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentCheckboxBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupListeners() + } + + private fun setupListeners() { + selectedLanguages.add("English") + selectedLanguages.add("Hindi") + binding.cbEnglish.setOnCheckedChangeListener(this) + binding.cbHindi.setOnCheckedChangeListener(this) + binding.cbGujarati.setOnCheckedChangeListener(this) + binding.cbSpanish.setOnCheckedChangeListener(this) + binding.btnSaveChanges.setOnClickListener { + Toast.makeText(requireContext(), selectedLanguages.joinToString(), Toast.LENGTH_SHORT) + .show() + } + } + + override fun onCheckedChanged(btn: CompoundButton?, isChecked: Boolean) { + val language = when (btn) { + binding.cbEnglish -> "English" + binding.cbHindi -> "Hindi" + binding.cbGujarati -> "Gujarati" + binding.cbSpanish -> "Spanish" + else -> "" + } + if (isChecked) { + selectedLanguages.add(language) + } else { + selectedLanguages.remove(language) + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/ChipFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/ChipFragment.kt new file mode 100644 index 0000000..e1986a4 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/ChipFragment.kt @@ -0,0 +1,55 @@ +package com.krunal.demo.uicomponents + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.widget.addTextChangedListener +import androidx.fragment.app.Fragment +import com.google.android.material.chip.Chip +import com.krunal.demo.R +import com.krunal.demo.databinding.FragmentChipBinding + +class ChipFragment : Fragment(R.layout.fragment_chip) { + + private lateinit var binding: FragmentChipBinding + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = FragmentChipBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupChips() + } + + private fun setupChips() { + binding.etChip.addTextChangedListener { + binding.tilChip.isEndIconVisible = binding.etChip.text.isNullOrEmpty().not() + } + + binding.etChip.setOnEditorActionListener { _, _, _ -> + addChip() + true + } + + binding.tilChip.setEndIconOnClickListener { + addChip() + } + } + + private fun addChip() { + val chip = Chip(requireContext()) + chip.text = binding.etChip.text + chip.setOnCloseIconClickListener { binding.cgProgrammatically.removeView(it) } + chip.setCloseIconResource(R.drawable.ic_cross) + chip.isCloseIconVisible = true + binding.cgProgrammatically.addView(chip) + binding.etChip.text?.clear() + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/CoordinatorLayoutFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/CoordinatorLayoutFragment.kt new file mode 100644 index 0000000..246e9ba --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/CoordinatorLayoutFragment.kt @@ -0,0 +1,45 @@ +package com.krunal.demo.uicomponents + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import androidx.fragment.app.Fragment +import com.google.android.material.behavior.SwipeDismissBehavior +import com.google.android.material.snackbar.BaseTransientBottomBar +import com.google.android.material.snackbar.Snackbar +import com.krunal.demo.databinding.FragmentCoordinatorLayoutBinding + +class CoordinatorLayoutFragment : Fragment() { + + private lateinit var binding: FragmentCoordinatorLayoutBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentCoordinatorLayoutBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupUI() + } + + private fun setupUI() { + setupSnackBar() + } + + private fun setupSnackBar() { + binding.btnSwipeableSnackBar.setOnClickListener { + Snackbar.make(requireView(), (it as Button).text, Snackbar.LENGTH_LONG).apply { + behavior = BaseTransientBottomBar.Behavior() + animationMode = BaseTransientBottomBar.ANIMATION_MODE_SLIDE + behavior.setSwipeDirection(SwipeDismissBehavior.SWIPE_DIRECTION_ANY) + show() + } + } + } + +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/CustomViewFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/CustomViewFragment.kt new file mode 100644 index 0000000..04fd5cf --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/CustomViewFragment.kt @@ -0,0 +1,6 @@ +package com.krunal.demo.uicomponents + +import androidx.fragment.app.Fragment +import com.krunal.demo.R + +class CustomViewFragment : Fragment(R.layout.fragment_custom_view) \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/EditTextFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/EditTextFragment.kt new file mode 100644 index 0000000..a2eca4f --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/EditTextFragment.kt @@ -0,0 +1,67 @@ +package com.krunal.demo.uicomponents + +import android.os.Bundle +import android.util.Patterns +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.EditText +import androidx.core.view.children +import androidx.core.widget.addTextChangedListener +import androidx.fragment.app.Fragment +import com.google.android.material.textfield.TextInputLayout +import com.krunal.demo.R +import com.krunal.demo.databinding.FragmentEditTextBinding +import com.krunal.demo.uicomponents.extentions.hideKeyboard + + +class EditTextFragment : Fragment(R.layout.fragment_edit_text) { + + private lateinit var binding: FragmentEditTextBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentEditTextBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupListeners() + } + + private fun setupListeners() { + binding.root.setOnFocusChangeListener { view, isFocused -> + if (isFocused) view.hideKeyboard() + } + + binding.etEmail.setOnFocusChangeListener { emailView, isFocused -> + val email = binding.etEmail.text.toString() + binding.emailContainer.error = if (!isFocused && !isValidEmail(email)) { + "Invalid email address" + } else { + null + } + } + + binding.etEmail.addTextChangedListener { + if (it?.length == 0) binding.emailContainer.error = null + } + + binding.btnClear.setOnClickListener { + binding.root.children.forEach { + if (it is EditText) { + it.text.clear() + } else if (it is TextInputLayout) { + it.editText?.text?.clear() + } + } + } + } + + private fun isValidEmail(email: String): Boolean { + val pattern = Patterns.EMAIL_ADDRESS + return pattern.matcher(email).matches() + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/FabFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/FabFragment.kt new file mode 100644 index 0000000..e935178 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/FabFragment.kt @@ -0,0 +1,69 @@ +package com.krunal.demo.uicomponents + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.google.android.material.snackbar.Snackbar +import com.krunal.demo.R +import com.krunal.demo.databinding.FragmentFabBinding + +class FabFragment : Fragment(R.layout.fragment_fab) { + + private lateinit var binding: FragmentFabBinding + private var visible: Boolean = false + private lateinit var snackBar: Snackbar + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentFabBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding.fabAdd.setOnClickListener { + changeVisibility() + if (visible) snackBar.show() + } + setupExpandableFab() + setupSnackBar() + } + + private fun changeVisibility() { + visible = visible.not() + binding.fabAdd.setImageResource( + if (visible) R.drawable.ic_cross + else R.drawable.ic_add + ) + binding.fabEdit.visibility = if (visible) View.VISIBLE else View.INVISIBLE + binding.fabImage.visibility = if (visible) View.VISIBLE else View.INVISIBLE + } + + private fun setupExpandableFab() { + binding.fabExtended1.isChecked = true + binding.fabExtended2.isChecked = true + + binding.fabExtended1.addOnCheckedChangeListener { _, isChecked -> + binding.fabExtended1.isExtended = isChecked + } + + binding.fabExtended2.addOnCheckedChangeListener { _, isChecked -> + binding.fabExtended2.isExtended = isChecked + } + + binding.fabExtended3.setOnClickListener { + binding.fabExtended3.isExtended = binding.fabExtended3.isExtended.not() + } + } + + private fun setupSnackBar() { + snackBar = Snackbar.make(binding.root, "Fab button opened", Snackbar.LENGTH_SHORT) + snackBar.anchorView = binding.fabImage + snackBar.setAction("Close") { + changeVisibility() + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/FrameLayoutFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/FrameLayoutFragment.kt new file mode 100644 index 0000000..c833914 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/FrameLayoutFragment.kt @@ -0,0 +1,8 @@ +package com.krunal.demo.uicomponents + +import androidx.fragment.app.Fragment +import com.krunal.demo.R + +class FrameLayoutFragment : Fragment(R.layout.fragment_frame_layout) { + +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/LinearLayoutFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/LinearLayoutFragment.kt new file mode 100644 index 0000000..00d1a6b --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/LinearLayoutFragment.kt @@ -0,0 +1,8 @@ +package com.krunal.demo.uicomponents + +import androidx.fragment.app.Fragment +import com.krunal.demo.R + +class LinearLayoutFragment : Fragment(R.layout.fragment_linear_layout) { + +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/ListViewFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/ListViewFragment.kt new file mode 100644 index 0000000..3b6d556 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/ListViewFragment.kt @@ -0,0 +1,33 @@ +package com.krunal.demo.uicomponents + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.krunal.demo.databinding.FragmentListViewBinding +import com.krunal.demo.uicomponents.adapters.ContactAdapter +import com.krunal.demo.uicomponents.models.Contact + +class ListViewFragment : Fragment() { + + private lateinit var binding: FragmentListViewBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentListViewBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupUI() + } + + private fun setupUI() { + val adapter = ContactAdapter() + adapter.submitList(Contact.dummyContacts) + binding.contactList.adapter = adapter + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/ProgressBarFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/ProgressBarFragment.kt new file mode 100644 index 0000000..3c6a3cd --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/ProgressBarFragment.kt @@ -0,0 +1,41 @@ +package com.krunal.demo.uicomponents + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.krunal.demo.databinding.FragmentProgressBarBinding +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +class ProgressBarFragment : Fragment() { + + private lateinit var binding: FragmentProgressBarBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentProgressBarBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupProgress() + } + + private fun setupProgress() { + CoroutineScope(Dispatchers.Main).launch { + repeat(10) { + delay(1000) + binding.pbCircularDeterminate.incrementProgressBy(5) + binding.pbCircularIndeterminate1.incrementProgressBy(5) + binding.pbCircularIndeterminate2.incrementProgressBy(5) + binding.pbHorizontalDeterminate.incrementProgressBy(-5) + } + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/RadioFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/RadioFragment.kt new file mode 100644 index 0000000..2b1e07c --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/RadioFragment.kt @@ -0,0 +1,52 @@ +package com.krunal.demo.uicomponents + +import android.os.Bundle +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.fragment.app.Fragment +import com.google.android.material.radiobutton.MaterialRadioButton +import com.krunal.demo.R +import com.krunal.demo.databinding.FragmentRadioBinding + +class RadioFragment : Fragment(R.layout.fragment_radio) { + + private lateinit var binding: FragmentRadioBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentRadioBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + showSelected() + } + + private fun showSelected() { + val toast = Toast(requireContext()) + toast.duration = Toast.LENGTH_SHORT + toast.setGravity(Gravity.CENTER_HORIZONTAL, 0, 0) + + binding.rgGender.setOnCheckedChangeListener { _, id -> + toast.setText( + when (id) { + binding.radioMale.id -> "Male" + binding.radioFemale.id -> "Female" + else -> "Other" + } + ) + toast.show() + } + binding.rgHouse.setOnCheckedChangeListener { _, id -> + toast.setText( + binding.root.findViewById(id).text + ) + toast.show() + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/RelativeLayoutFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/RelativeLayoutFragment.kt new file mode 100644 index 0000000..871b5cf --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/RelativeLayoutFragment.kt @@ -0,0 +1,8 @@ +package com.krunal.demo.uicomponents + +import androidx.fragment.app.Fragment +import com.krunal.demo.R + +class RelativeLayoutFragment : Fragment(R.layout.fragment_relative_layout) { + +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/SliderFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/SliderFragment.kt new file mode 100644 index 0000000..e79bbf7 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/SliderFragment.kt @@ -0,0 +1,50 @@ +package com.krunal.demo.uicomponents + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.krunal.demo.R +import com.krunal.demo.databinding.FragmentSliderBinding + +class SliderFragment : Fragment(R.layout.fragment_slider) { + + private lateinit var binding: FragmentSliderBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentSliderBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupBars() + } + + private fun setupBars() { + binding.sliderNormal.addOnChangeListener { _, value, _ -> + binding.tvSliderNormal.text = getString(R.string.slider_value, value) + } + + binding.sliderDiscrete.addOnChangeListener { _, value, _ -> + binding.tvSliderDiscrete.text = getString(R.string.slider_value, value) + } + + binding.sliderRange.addOnChangeListener { _, _, _ -> + val values = binding.sliderRange.values + binding.tvSliderRange.text = getString( + R.string.slider_range_value, values[0], values[1] + ) + } + + binding.sliderRangeDiscrete.addOnChangeListener { _, _, _ -> + val values = binding.sliderRangeDiscrete.values + binding.tvSliderRangeDiscrete.text = getString( + R.string.slider_range_value, values[0], values[1] + ) + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/SnackBarFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/SnackBarFragment.kt new file mode 100644 index 0000000..269a034 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/SnackBarFragment.kt @@ -0,0 +1,69 @@ +package com.krunal.demo.uicomponents + +import android.graphics.Color +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import androidx.fragment.app.Fragment +import com.google.android.material.behavior.SwipeDismissBehavior +import com.google.android.material.snackbar.BaseTransientBottomBar.ANIMATION_MODE_SLIDE +import com.google.android.material.snackbar.BaseTransientBottomBar.Behavior +import com.google.android.material.snackbar.Snackbar +import com.krunal.demo.R +import com.krunal.demo.databinding.FragmentSnackbarBinding + +class SnackBarFragment : Fragment(R.layout.fragment_snackbar) { + + private lateinit var binding: FragmentSnackbarBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentSnackbarBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupSnackBars() + } + + private fun setupSnackBars() { + binding.btnShortSnackBar.setOnClickListener { + Snackbar.make(requireView(), (it as Button).text, Snackbar.LENGTH_SHORT).show() + } + + binding.btnLongSnackBar.setOnClickListener { + Snackbar.make(requireView(), (it as Button).text, Snackbar.LENGTH_LONG).show() + } + + binding.btnMultiLineSnackBar.setOnClickListener { + Snackbar.make(requireView(), (it as Button).text, Snackbar.LENGTH_SHORT).show() + } + + binding.btnActionSnackBar.setOnClickListener { + val snackBar = Snackbar.make(requireView(), (it as Button).text, Snackbar.LENGTH_LONG) + .setBackgroundTint(Color.GRAY).setActionTextColor(Color.GREEN) + snackBar.setAction("Red") { + snackBar.setBackgroundTint(Color.RED) + } + snackBar.show() + } + + binding.btnAnchorSnackBar.setOnClickListener { + Snackbar.make(requireView(), (it as Button).text, Snackbar.LENGTH_LONG) + .setAnchorView(binding.btnShortSnackBar).show() + } + + binding.btnSwipeableSnackBar.setOnClickListener { + Snackbar.make(requireView(), (it as Button).text, Snackbar.LENGTH_LONG).apply { + behavior = Behavior() + animationMode = ANIMATION_MODE_SLIDE + behavior.setSwipeDirection(SwipeDismissBehavior.SWIPE_DIRECTION_ANY) + show() + } + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/SpanFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/SpanFragment.kt new file mode 100644 index 0000000..c392e46 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/SpanFragment.kt @@ -0,0 +1,103 @@ +package com.krunal.demo.uicomponents + +import android.graphics.Color +import android.os.Bundle +import android.text.Spannable +import android.text.SpannableStringBuilder +import android.text.method.LinkMovementMethod +import android.text.style.ForegroundColorSpan +import android.text.style.StrikethroughSpan +import android.text.style.URLSpan +import android.text.style.UnderlineSpan +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.core.text.bold +import androidx.core.text.italic +import androidx.core.text.toSpannable +import androidx.fragment.app.Fragment +import com.krunal.demo.R +import com.krunal.demo.databinding.FragmentSpanBinding +import com.krunal.demo.uicomponents.extentions.addTextView +import com.krunal.demo.uicomponents.views.MyClickableSpan + +class SpanFragment : Fragment() { + + private lateinit var binding: FragmentSpanBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentSpanBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupSpans() + } + + private fun setupSpans() { + // Normal foreground color span + val textNormal = binding.tvColorSpan.text + val spanNormal = textNormal.toSpannable().apply { + setSpan( + ForegroundColorSpan(Color.RED), 8, 11, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + setSpan( + ForegroundColorSpan(Color.GREEN), 16, 21, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + } + binding.tvColorSpan.text = spanNormal + + // Underlined and LineThrough + val textUnderlineLineThrough = binding.tvUnderlineLineThroughSpan.text + val spanUnderlineLineThrough = textUnderlineLineThrough.toSpannable().apply { + setSpan( + UnderlineSpan(), 8, 18, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + setSpan( + StrikethroughSpan(), 23, 34, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + } + binding.tvUnderlineLineThroughSpan.text = spanUnderlineLineThrough + + // Clickable Link + val spanLink = SpannableStringBuilder("This is my ").bold { + italic { + append( + "Portfolio", + URLSpan("https://krunalpatel.me"), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + } + }.append(" or you can visit my ").bold { + italic { + append( + "Blog", + URLSpan("https://blog.krunalpatel.me"), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + } + } + binding.tvLink.text = spanLink + binding.tvLink.movementMethod = LinkMovementMethod.getInstance() + + // Clickable span + val clickableText = "You can click on any word" + + SpannableStringBuilder().also { spanBuilder -> + clickableText.split(" ").forEach { word -> + spanBuilder.append("$word ", MyClickableSpan { + Toast.makeText( + requireContext(), + getString(R.string.click_toast_text, word), + Toast.LENGTH_SHORT + ).show() + }, Spannable.SPAN_INCLUSIVE_EXCLUSIVE) + } + binding.root.addTextView(spanBuilder) + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/SpinnerFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/SpinnerFragment.kt new file mode 100644 index 0000000..3f4b669 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/SpinnerFragment.kt @@ -0,0 +1,64 @@ +package com.krunal.demo.uicomponents + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.Toast +import androidx.fragment.app.Fragment +import com.krunal.demo.R +import com.krunal.demo.databinding.FragmentSpinnerBinding +import com.krunal.demo.uicomponents.adapters.TimezoneAdapter + +class SpinnerFragment : Fragment(), AdapterView.OnItemSelectedListener { + + private lateinit var binding: FragmentSpinnerBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentSpinnerBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupSpinners() + } + + private fun setupSpinners() { + ArrayAdapter.createFromResource( + requireContext(), R.array.timezones, android.R.layout.simple_spinner_item + ).also { adapter -> + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + binding.spinnerNormal.adapter = adapter + binding.spinnerDialog.adapter = adapter + binding.spinnerUnderlined.adapter = adapter + binding.spinnerBordered.adapter = adapter + binding.autoCompleteTimezone.setAdapter(adapter) + binding.spinnerDisabled.adapter = adapter + } + + binding.spinnerBordered.setSelection(4, true) + binding.spinnerNormal.onItemSelectedListener = this + binding.spinnerDisabled.isEnabled = false + + val adapter = TimezoneAdapter(requireContext(), resources.getStringArray(R.array.timezones)) + binding.spinnerCustom.adapter = adapter + } + + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + Toast.makeText( + requireContext(), + binding.spinnerNormal.selectedItem.toString(), + Toast.LENGTH_SHORT + ).show() + } + + override fun onNothingSelected(parent: AdapterView<*>?) { + Toast.makeText(requireContext(), getString(R.string.nothing_selected), Toast.LENGTH_SHORT) + .show() + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/TabLayoutFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/TabLayoutFragment.kt new file mode 100644 index 0000000..838a944 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/TabLayoutFragment.kt @@ -0,0 +1,41 @@ +package com.krunal.demo.uicomponents + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.google.android.material.tabs.TabLayout +import com.google.android.material.tabs.TabLayoutMediator +import com.krunal.demo.R +import com.krunal.demo.databinding.FragmentTabLayoutBinding +import com.krunal.demo.uicomponents.adapters.ViewPagerAdapter + +class TabLayoutFragment : Fragment(R.layout.fragment_tab_layout) { + + private lateinit var binding: FragmentTabLayoutBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentTabLayoutBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupTabsWithPager() + } + + private fun setupTabsWithPager() { + val viewPagerAdapter = ViewPagerAdapter(requireActivity()) + binding.viewPager.adapter = viewPagerAdapter + TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position -> + tab.text = when (position) { + 0 -> "Home" + 1 -> "Chat" + else -> "Call" + } + }.attach() + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/ThemeFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/ThemeFragment.kt new file mode 100644 index 0000000..7fc27ed --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/ThemeFragment.kt @@ -0,0 +1,57 @@ +package com.krunal.demo.uicomponents + +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import com.krunal.demo.databinding.FragmentThemeBinding +import com.krunal.demo.uicomponents.adapters.ThemeAdapter +import com.krunal.demo.uicomponents.extentions.isDarkMode +import com.krunal.demo.uicomponents.helpers.ThemeHelper +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +class ThemeFragment : Fragment() { + + private lateinit var binding: FragmentThemeBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentThemeBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupTheme() + } + + private fun setupTheme() { + val themes = ThemeHelper.getThemes(requireContext(), requireActivity().isDarkMode) + val themeAdapter = ThemeAdapter( + requireContext(), themes + ) + + binding.gvTheme.adapter = themeAdapter + binding.gvTheme.setItemChecked(ThemeHelper.getThemeAccent().ordinal, true) + lifecycleScope.launch { + delay(100) + + binding.gvTheme.setItemChecked(ThemeHelper.getThemeAccent().ordinal, true) + binding.gvTheme.setSelection(ThemeHelper.getThemeAccent().ordinal) + Log.d("Tag", ThemeHelper.getThemeAccent().ordinal.toString()) + Log.d("Tag", binding.gvTheme.selectedItemPosition.toString()) + } + // binding.gvTheme.selectedView.performClick() // selectedView is null + + binding.gvTheme.setOnItemClickListener { _, _, position, _ -> + ThemeHelper.setThemeAccent(themes[position].accentColor) + // TODO: Restart activity +// activity?.recreate() + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/ToastFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/ToastFragment.kt new file mode 100644 index 0000000..b6822f5 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/ToastFragment.kt @@ -0,0 +1,74 @@ +package com.krunal.demo.uicomponents + +import android.graphics.Color +import android.os.Bundle +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import android.widget.Toast +import androidx.core.content.res.ResourcesCompat +import androidx.fragment.app.Fragment +import com.krunal.demo.R +import com.krunal.demo.databinding.FragmentToastBinding + +class ToastFragment : Fragment(R.layout.fragment_toast) { + private lateinit var binding: FragmentToastBinding + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = FragmentToastBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupButtons() + } + + // Set click listeners for buttons to show toast + private fun setupButtons() { + // Short toast + binding.btnShortToast.setOnClickListener { + Toast.makeText(requireContext(), "This is short Toast", Toast.LENGTH_SHORT).show() + } + + // Long toast + binding.btnLongToast.setOnClickListener { + Toast.makeText(requireContext(), "This is long Toast", Toast.LENGTH_LONG).show() + } + + // Custom short success toast + binding.btnSuccessToast.setOnClickListener { + showToast(R.drawable.ic_check, "Custom success toast", backgroundColor = Color.GREEN) + } + + // Custom long error toast + binding.btnErrorToast.setOnClickListener { + showToast(R.drawable.ic_cross, "Custom error toast", Toast.LENGTH_LONG, Color.RED) + } + } + + // Make and show custom toast + private fun showToast(iconId: Int, text: String, length: Int = Toast.LENGTH_SHORT, backgroundColor: Int = Color.GRAY) { + val toastView = layoutInflater.inflate(R.layout.custom_toast, requireActivity().findViewById(R.id.customToast)) + val imgViewStatus = toastView.findViewById(R.id.imgViewStatus) + val tvMessage = toastView.findViewById(R.id.tvMessage) + + toastView.setBackgroundColor(backgroundColor) + imgViewStatus.setImageResource(iconId) + tvMessage.text = text + + Toast(requireContext()).apply { + setGravity(Gravity.CENTER, 0, 0) + duration = length + view = toastView + show() + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/adapters/ContactAdapter.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/adapters/ContactAdapter.kt new file mode 100644 index 0000000..c681ec6 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/adapters/ContactAdapter.kt @@ -0,0 +1,44 @@ +package com.krunal.demo.uicomponents.adapters + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.BaseAdapter +import androidx.databinding.DataBindingUtil +import com.krunal.demo.databinding.ContactLayoutBinding +import com.krunal.demo.uicomponents.models.Contact + +class ContactAdapter : BaseAdapter() { + + private val contacts: MutableList = + Contact.dummyContacts.toMutableList()// mutableListOf() + + override fun getCount(): Int = contacts.count() + + override fun getItem(position: Int): Any = contacts[position] + + override fun getItemId(position: Int): Long = 0 + + override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { + val inflater = LayoutInflater.from(parent?.context) +// val binding: ContactLayoutBinding = if (convertView != null) { +// DataBindingUtil.getBinding(convertView) ?: ContactLayoutBinding.inflate( +// inflater, parent, false +// ) +// } else { +// ContactLayoutBinding.inflate(inflater, parent, false) +// } + val binding = ContactLayoutBinding.inflate(inflater, parent, false) + binding.contact = contacts[position] + return binding.root + } + + fun submitList(list: List) { + contacts.clear() + contacts.addAll(list) + notifyDataSetChanged() + } + + // If not using data binding + // class ViewHolder(val itemView: View) +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/adapters/ThemeAdapter.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/adapters/ThemeAdapter.kt new file mode 100644 index 0000000..cc739e1 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/adapters/ThemeAdapter.kt @@ -0,0 +1,33 @@ +package com.krunal.demo.uicomponents.adapters + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.BaseAdapter +import android.widget.TextView +import com.google.android.material.card.MaterialCardView +import com.krunal.demo.R +import com.krunal.demo.uicomponents.models.Theme + +class ThemeAdapter( + private val context: Context, + private val themes: List +) : BaseAdapter() { + override fun getCount(): Int = themes.size + + override fun getItem(position: Int): Any? = null + + override fun getItemId(position: Int): Long = position.toLong() + + override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { + val inflater = LayoutInflater.from(context) + val view = inflater.inflate(R.layout.theme_layout, null) + val tvName = view.findViewById(R.id.tvThemeName) + val viewTheme = view.findViewById(R.id.cardThemeColor) + + tvName.text = themes[position].name + viewTheme.setCardBackgroundColor(themes[position].color) + return view + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/adapters/TimezoneAdapter.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/adapters/TimezoneAdapter.kt new file mode 100644 index 0000000..4f445da --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/adapters/TimezoneAdapter.kt @@ -0,0 +1,29 @@ +package com.krunal.demo.uicomponents.adapters + +import android.annotation.SuppressLint +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.BaseAdapter +import android.widget.TextView +import com.krunal.demo.R + +class TimezoneAdapter(private val context: Context, private val timezones: Array): BaseAdapter() { + + override fun getCount() = timezones.size + + override fun getItem(position: Int) = null + + override fun getItemId(position: Int): Long = 0 + + @SuppressLint("ViewHolder") + override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { + val inflater = LayoutInflater.from(context) + val view = inflater.inflate(R.layout.timezone_spinner, null) + val tvName = view.findViewById(R.id.tvTimezone) + tvName.text = timezones[position] + + return view + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/adapters/ViewBindingAdapter.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/adapters/ViewBindingAdapter.kt new file mode 100644 index 0000000..0b3bd0d --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/adapters/ViewBindingAdapter.kt @@ -0,0 +1,87 @@ +package com.krunal.demo.uicomponents.adapters + +import android.graphics.Color +import android.graphics.drawable.GradientDrawable +import android.view.View +import android.view.ViewGroup +import android.view.ViewGroup.LayoutParams +import android.widget.ImageView +import android.widget.TextView +import androidx.annotation.ColorRes +import androidx.annotation.DrawableRes +import androidx.appcompat.content.res.AppCompatResources +import androidx.core.graphics.ColorUtils +import androidx.core.view.marginStart +import androidx.core.view.updateLayoutParams +import androidx.databinding.BindingAdapter + +@BindingAdapter("drawableResource") +fun ImageView.setDrawableResource(@DrawableRes drawableResource: Int) { + setImageResource(drawableResource) +} + +@BindingAdapter("drawableResource") +fun TextView.setStartDrawableResource(@DrawableRes drawableResource: Int) { + setCompoundDrawablesRelativeWithIntrinsicBounds( + AppCompatResources.getDrawable(context, drawableResource), null, null, null + ) +} + +@BindingAdapter("drawableColorResource") +fun TextView.setStartColorResource(@ColorRes colorResource: Int) { + compoundDrawablesRelative.filterNotNull().forEach { drawable -> + drawable.mutate().setTint(resources.getColor(colorResource, context.theme)) + } +} + +@BindingAdapter("textColorResource") +fun TextView.setTextColorResource(@ColorRes colorResource: Int) { + setTextColor(resources.getColor(colorResource, context.theme)) +} + +@BindingAdapter("textColor") +fun TextView.setTextColor(color: Int) { + setTextColor(color) +} + +@BindingAdapter("backgroundColor") +fun View.setViewBackgroundColor(color: Int) { + setBackgroundColor(color) +} + +@BindingAdapter("gradientStartColor", "gradientEndColor", "angle", requireAll = false) +fun View.setGradientBackground(gradientStartColor: Int?, gradientEndColor: Int?, angle: Int?) { + gradientStartColor?.let { it -> + ColorUtils.setAlphaComponent(it, 0x4f).let { color -> + GradientDrawable( + GradientDrawable.Orientation.TR_BL, + intArrayOf(color, gradientEndColor ?: Color.TRANSPARENT) + ).also { + it.cornerRadius = 0f + background = it + } + } + } +} + + +@BindingAdapter("android:layout_marginStart") +fun View.setStartMargin(margin: Float) { + updateLayoutParams { + marginStart = margin.toInt() + } +} + +@BindingAdapter("layout_width") +fun View.setWidth(width: Float) { + updateLayoutParams { + this.width = width.toInt() + } +} + +@BindingAdapter("layout_height") +fun View.setHeight(height: Float) { + updateLayoutParams { + this.height = height.toInt() + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/adapters/ViewPagerAdapter.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/adapters/ViewPagerAdapter.kt new file mode 100644 index 0000000..b88918c --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/adapters/ViewPagerAdapter.kt @@ -0,0 +1,16 @@ +package com.krunal.demo.uicomponents.adapters + +import androidx.fragment.app.FragmentActivity +import androidx.viewpager2.adapter.FragmentStateAdapter +import com.krunal.demo.uicomponents.tablayoutfragments.CallsFragment +import com.krunal.demo.uicomponents.tablayoutfragments.ChatFragment +import com.krunal.demo.uicomponents.tablayoutfragments.HomeFragment + +class ViewPagerAdapter(fa: FragmentActivity): FragmentStateAdapter(fa) { + + private val fragments = listOf(HomeFragment(), ChatFragment(), CallsFragment()) + + override fun getItemCount() = fragments.count() + + override fun createFragment(position: Int) = fragments[position] +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/binding/DataBindingFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/binding/DataBindingFragment.kt new file mode 100644 index 0000000..c89d361 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/binding/DataBindingFragment.kt @@ -0,0 +1,26 @@ +package com.krunal.demo.uicomponents.binding + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import com.krunal.demo.databinding.FragmentDataBindingBinding + +class DataBindingFragment : Fragment() { + + private lateinit var binding: FragmentDataBindingBinding + private val viewModel: DataBingingViewModel by viewModels() + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = FragmentDataBindingBinding.inflate(inflater) + binding.viewModel = viewModel + binding.lifecycleOwner = viewLifecycleOwner + return binding.root + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/binding/DataBingingViewModel.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/binding/DataBingingViewModel.kt new file mode 100644 index 0000000..ced1218 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/binding/DataBingingViewModel.kt @@ -0,0 +1,34 @@ +package com.krunal.demo.uicomponents.binding + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.krunal.demo.uicomponents.models.Name +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch + +class DataBingingViewModel : ViewModel() { + + private val _nameFlow = MutableStateFlow(null) + val nameFlow: StateFlow = _nameFlow + + init { + randomName() + } + + /** Generate and emmit random names to [nameFlow] */ + private fun randomName() { + // List of random names + val names = listOf( + Name("Harry", "Potter") + ) + + viewModelScope.launch { + while (true) { + delay(3000) + _nameFlow.emit(names.first()) + } + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/cardscreen/CardFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/cardscreen/CardFragment.kt new file mode 100644 index 0000000..f296686 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/cardscreen/CardFragment.kt @@ -0,0 +1,117 @@ +package com.krunal.demo.uicomponents.cardscreen + +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import com.google.android.material.chip.Chip +import com.google.android.material.navigation.NavigationBarView +import com.krunal.demo.R +import com.krunal.demo.UIComponentsActivity +import com.krunal.demo.databinding.CardLayoutBinding +import com.krunal.demo.databinding.FragmentCardBinding +import com.krunal.demo.uicomponents.dialogs.MyDatePickerDialog +import com.krunal.demo.uicomponents.extentions.dpFormat +import com.krunal.demo.uicomponents.models.CardDetail +import com.krunal.demo.uicomponents.sheets.OperationsBottomSheetFragment +import kotlinx.coroutines.launch + +class CardFragment : Fragment() { + + private lateinit var binding: FragmentCardBinding + private val viewModel: CardFragmentViewModel by viewModels() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentCardBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupUI() + } + + private fun setupUI() { + setupBottomBar() + (activity as? UIComponentsActivity)?.supportActionBar?.apply { + title = getString(R.string.my_cards) + setBackgroundDrawable(ColorDrawable(Color.parseColor("#2F7CEF"))) + elevation = 0f + // TODO: Make title center + } + + setupCardChips() + + setupCard() + + binding.cardOperations.btnDetails.setOnClickListener { + OperationsBottomSheetFragment().show(childFragmentManager, null) + } + + binding.cardOperations.btnCalendar.setOnClickListener { + MyDatePickerDialog().show(childFragmentManager, null) + } + } + + private fun setupCardChips() { + viewModel.dummyCardDetails.forEach { card -> + (layoutInflater.inflate(R.layout.card_chip, null) as? Chip)?.apply { + text = getString(R.string.card_name, card.type, card.number) + isChecked = card.isSelected + setOnCheckedChangeListener { _, isChecked -> + viewModel.updateCardSelection( + card, + isChecked + ) + } + }.also { cardChip -> + binding.cgCard.addView(cardChip) + } + } + } + + private fun setupCard() { + addSelectedCards(viewModel.dummyCardDetails) + lifecycleScope.launch { + viewModel.selectedCards.collect { cardList -> + binding.llCards.removeViews(1, binding.llCards.childCount - 1) + addSelectedCards(cardList) + } + } + } + + private fun addSelectedCards(cardList: List) { + cardList + .filter { it.isSelected } + .forEach { card -> + val cardBinding = CardLayoutBinding.inflate(layoutInflater) + cardBinding.card = card + + val layoutParams = LinearLayout.LayoutParams( + 300.dpFormat(requireContext()), + 200.dpFormat(requireContext()), + ).apply { + marginEnd = 18.dpFormat(requireContext()) + } + + cardBinding.root.layoutParams = layoutParams + binding.llCards.addView(cardBinding.root) + } + } + + private fun setupBottomBar() { + binding.bottomNavigation.apply { + selectedItemId = menu.getItem(1).itemId + labelVisibilityMode = NavigationBarView.LABEL_VISIBILITY_SELECTED + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/cardscreen/CardFragmentViewModel.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/cardscreen/CardFragmentViewModel.kt new file mode 100644 index 0000000..2c76f8e --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/cardscreen/CardFragmentViewModel.kt @@ -0,0 +1,29 @@ +package com.krunal.demo.uicomponents.cardscreen + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.krunal.demo.uicomponents.models.CardDetail +import com.krunal.demo.uicomponents.models.enums.CardType +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.launch + +class CardFragmentViewModel : ViewModel() { + + private val _selectedCards = MutableSharedFlow>() + val selectedCards: SharedFlow> = _selectedCards + + var dummyCardDetails: List = buildList { + add(CardDetail(CardType.DEBIT, 1963, "$2,983.78", true)) + add(CardDetail(CardType.DEBIT, 1822, "$1,002.02")) + add(CardDetail(CardType.CREDIT, 2291, "$540.00")) + } + + fun updateCardSelection(card: CardDetail, isSelected: Boolean) { + dummyCardDetails.find { it.number == card.number }?.isSelected = + isSelected + viewModelScope.launch { + _selectedCards.emit(dummyCardDetails) + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/constraintLayouts/ChainBiasFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/constraintLayouts/ChainBiasFragment.kt new file mode 100644 index 0000000..dfb8893 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/constraintLayouts/ChainBiasFragment.kt @@ -0,0 +1,5 @@ +package com.krunal.demo.uicomponents.constraintLayouts + +import androidx.fragment.app.Fragment + +class ChainBiasFragment : Fragment() \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/constraintLayouts/CircularFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/constraintLayouts/CircularFragment.kt new file mode 100644 index 0000000..2f8fbe6 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/constraintLayouts/CircularFragment.kt @@ -0,0 +1,32 @@ +package com.krunal.demo.uicomponents.constraintLayouts + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.krunal.demo.R +import com.krunal.demo.databinding.FragmentClCircularBinding + +class CircularFragment : Fragment(R.layout.fragment_cl_circular) { + + private lateinit var binding: FragmentClCircularBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentClCircularBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupUI() + } + + private fun setupUI() { + binding.imgBtnMain.setOnCheckedChangeListener { _, isChecked -> + binding.groupEmojis.visibility = if (isChecked) View.VISIBLE else View.INVISIBLE + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/constraintLayouts/GuidelineBarrierFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/constraintLayouts/GuidelineBarrierFragment.kt new file mode 100644 index 0000000..64d7c05 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/constraintLayouts/GuidelineBarrierFragment.kt @@ -0,0 +1,5 @@ +package com.krunal.demo.uicomponents.constraintLayouts + +import androidx.fragment.app.Fragment + +class GuidelineBarrierFragment : Fragment() \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/constraintLayouts/RelativeFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/constraintLayouts/RelativeFragment.kt new file mode 100644 index 0000000..18dac3a --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/constraintLayouts/RelativeFragment.kt @@ -0,0 +1,6 @@ +package com.krunal.demo.uicomponents.constraintLayouts + +import androidx.fragment.app.Fragment +import com.krunal.demo.R + +class RelativeFragment : Fragment(R.layout.fragment_cl_relative) \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/dialogs/MyDatePickerDialog.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/dialogs/MyDatePickerDialog.kt new file mode 100644 index 0000000..369e675 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/dialogs/MyDatePickerDialog.kt @@ -0,0 +1,21 @@ +package com.krunal.demo.uicomponents.dialogs + +import android.app.DatePickerDialog +import android.app.Dialog +import android.os.Bundle +import androidx.fragment.app.DialogFragment +import java.util.Calendar + +class MyDatePickerDialog : DialogFragment() { + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val mCalendar: Calendar = Calendar.getInstance() + val year = mCalendar.get(Calendar.YEAR) + val month = mCalendar.get(Calendar.MONTH) + val dayOfMonth = mCalendar.get(Calendar.DAY_OF_MONTH) + + return DatePickerDialog(requireActivity(), null, year, month, dayOfMonth).apply { + datePicker.maxDate = Calendar.getInstance().timeInMillis + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/extentions/ActivityExtentions.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/extentions/ActivityExtentions.kt new file mode 100644 index 0000000..eaf702e --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/extentions/ActivityExtentions.kt @@ -0,0 +1,20 @@ +package com.krunal.demo.uicomponents.extentions + +import android.app.Activity +import android.content.res.Configuration +import android.util.TypedValue + + +fun Activity.getThemeColor(resId: Int): Int { + val typedValue = TypedValue() + theme.resolveAttribute(resId, typedValue, true) + return typedValue.data +} + +val Activity.isDarkMode: Boolean + get() = when (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) { + Configuration.UI_MODE_NIGHT_YES -> true + Configuration.UI_MODE_NIGHT_NO -> false + Configuration.UI_MODE_NIGHT_UNDEFINED -> false + else -> false + } \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/extentions/LinearLayoutExtentions.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/extentions/LinearLayoutExtentions.kt new file mode 100644 index 0000000..4ee254f --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/extentions/LinearLayoutExtentions.kt @@ -0,0 +1,29 @@ +package com.krunal.demo.uicomponents.extentions + +import android.text.Spanned +import android.text.method.LinkMovementMethod +import android.widget.LinearLayout +import android.widget.TextView +import androidx.core.text.toSpanned + +// Add text to the LinearLayout by creating new TextView +fun LinearLayout.addTextView(text: String) { + addTextView(text.toSpanned()) +} + +// Add spanned text to the LinearLayout by creating new TextView +fun LinearLayout.addTextView(spanText: Spanned) { + TextView(context).apply { + layoutParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT, + ).apply { + topMargin = 24.dpFormat(context) + } + text = spanText + textSize = 18f + movementMethod = LinkMovementMethod.getInstance() + }.also { textView -> + addView(textView) + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/extentions/NumberExtentions.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/extentions/NumberExtentions.kt new file mode 100644 index 0000000..43bc8c3 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/extentions/NumberExtentions.kt @@ -0,0 +1,10 @@ +package com.krunal.demo.uicomponents.extentions + +import android.content.Context +import android.util.DisplayMetrics +import kotlin.math.roundToInt + +fun Int.dpFormat(context: Context): Int { + val displayMetrics = context.resources.displayMetrics + return (this * (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT)).roundToInt() +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/extentions/ViewExtentions.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/extentions/ViewExtentions.kt new file mode 100644 index 0000000..856ac34 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/extentions/ViewExtentions.kt @@ -0,0 +1,10 @@ +package com.krunal.demo.uicomponents.extentions + +import android.app.Activity +import android.view.View +import android.view.inputmethod.InputMethodManager + +fun View.hideKeyboard() { + val inputMethodManager = context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager + inputMethodManager.hideSoftInputFromWindow(windowToken, 0) +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/helpers/PreferenceHelper.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/helpers/PreferenceHelper.kt new file mode 100644 index 0000000..4c7cca8 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/helpers/PreferenceHelper.kt @@ -0,0 +1,69 @@ +package com.krunal.demo.uicomponents.helpers + +import android.content.Context +import android.content.SharedPreferences +import androidx.preference.PreferenceManager + +object PreferenceHelper { + + private lateinit var preferences: SharedPreferences + private lateinit var editor: SharedPreferences.Editor + + /** + * set the context that is being used to access the shared preferences + */ + fun initialize(context: Context) { + preferences = getDefaultSharedPreferences(context) + editor = preferences.edit() + } + + fun putString(key: String, value: String) { + editor.putString(key, value).commit() + } + + fun putBoolean(key: String, value: Boolean) { + editor.putBoolean(key, value).commit() + } + + fun putInt(key: String, value: Int) { + editor.putInt(key, value).commit() + } + + fun putFloat(key: String, value: Float) { + editor.putFloat(key, value).commit() + } + + fun putLong(key: String, value: Long) { + editor.putLong(key, value).commit() + } + + fun getString(key: String?, defValue: String): String { + return preferences.getString(key, defValue) ?: defValue + } + + fun getBoolean(key: String?, defValue: Boolean): Boolean { + return preferences.getBoolean(key, defValue) + } + + fun getInt(key: String?, defValue: Int): Int { + return runCatching { + preferences.getInt(key, defValue) + }.getOrElse { preferences.getLong(key, defValue.toLong()).toInt() } + } + + fun getLong(key: String?, defValue: Long): Long { + return preferences.getLong(key, defValue) + } + + fun getFloat(key: String?, defValue: Float): Float { + return preferences.getFloat(key, defValue) + } + + fun clearPreferences() { + editor.clear().apply() + } + + private fun getDefaultSharedPreferences(context: Context): SharedPreferences { + return PreferenceManager.getDefaultSharedPreferences(context) + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/helpers/ThemeHelper.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/helpers/ThemeHelper.kt new file mode 100644 index 0000000..2bedaae --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/helpers/ThemeHelper.kt @@ -0,0 +1,79 @@ +package com.krunal.demo.uicomponents.helpers + +import android.content.Context +import com.krunal.demo.R +import com.krunal.demo.uicomponents.models.Theme +import com.krunal.demo.uicomponents.models.enums.AccentColor +import com.krunal.demo.uicomponents.models.enums.ThemeMode +import com.krunal.demo.uicomponents.utils.PreferenceKeys + +object ThemeHelper { + fun getThemes(context: Context, isDark: Boolean = false): List { + val accentValues = AccentColor.values() + val accent = context.resources.getStringArray(R.array.accents) + + return buildList { + for (i in accentValues.indices) { + add( + Theme( + accent[i], accentValues[i], getAccentColor(context, accentValues[i], isDark) + ) + ) + } + } + } + + fun getThemeResource(accentColor: AccentColor): Int { + return when (accentColor) { + AccentColor.RED -> R.style.Theme_Red + AccentColor.BLUE -> R.style.Theme_Blue + AccentColor.YELLOW -> R.style.Theme_Yellow + AccentColor.GREEN -> R.style.Theme_Green + AccentColor.PURPLE -> R.style.Theme_Purple + AccentColor.VIOLET -> R.style.Theme_Violet + } + } + + fun getThemeMode(): ThemeMode { + return ThemeMode.valueOf( + PreferenceHelper.getString( + PreferenceKeys.THEME_MODE, + ThemeMode.AUTO.name + ) + ) + } + + fun setThemeMode(mode: ThemeMode) { + PreferenceHelper.putString(PreferenceKeys.THEME_MODE, mode.name) + } + + fun getThemeAccent(): AccentColor { + return AccentColor.valueOf( + PreferenceHelper.getString( + PreferenceKeys.ACCENT_COLOR, + AccentColor.VIOLET.name + ) + ) + } + + fun setThemeAccent(color: AccentColor) { + PreferenceHelper.putString(PreferenceKeys.ACCENT_COLOR, color.name) + } + + private fun getAccentColor( + context: Context, + accentColor: AccentColor, + isDarkMode: Boolean + ): Int { + return context.getColor( + when (accentColor) { + AccentColor.RED -> if (isDarkMode) R.color.red_md_theme_dark_primary else R.color.red_md_theme_light_primary + AccentColor.BLUE -> if (isDarkMode) R.color.blue_md_theme_dark_primary else R.color.blue_md_theme_light_primary + AccentColor.YELLOW -> if (isDarkMode) R.color.yellow_md_theme_dark_primary else R.color.yellow_md_theme_light_primary + AccentColor.GREEN -> if (isDarkMode) R.color.green_md_theme_dark_primary else R.color.green_md_theme_light_primary + AccentColor.PURPLE -> if (isDarkMode) R.color.purple_md_theme_dark_primary else R.color.purple_md_theme_light_primary + AccentColor.VIOLET -> if (isDarkMode) R.color.violet_theme_dark_primary else R.color.violet_theme_light_primary + } + ) + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/CardDetail.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/CardDetail.kt new file mode 100644 index 0000000..d910dbf --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/CardDetail.kt @@ -0,0 +1,18 @@ +package com.krunal.demo.uicomponents.models + +import com.krunal.demo.uicomponents.models.enums.CardType + +data class CardDetail( + val type: CardType, val number: Int, val amount: String = "$0" +) { + + var isSelected: Boolean = false + + constructor(type: CardType, number: Int, amount: String, isSelected: Boolean) : this( + type, + number, + amount + ) { + this.isSelected = isSelected + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/Contact.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/Contact.kt new file mode 100644 index 0000000..ff6cf19 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/Contact.kt @@ -0,0 +1,17 @@ +package com.krunal.demo.uicomponents.models + +import androidx.annotation.DrawableRes +import com.krunal.demo.R + +data class Contact( + val name: String, val number: String, @DrawableRes val profile: Int +) { + companion object { + val dummyContacts: List = buildList { + repeat(100) { + add(Contact("Krunal Patel", "+91 9920304837", R.drawable.profile)) + add(Contact("Harsh Mehta", "+91 6023949309", R.drawable.profile)) + } + } + } +} diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/DrawableResource.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/DrawableResource.kt new file mode 100644 index 0000000..9f3f4fb --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/DrawableResource.kt @@ -0,0 +1,8 @@ +package com.krunal.demo.uicomponents.models + +import android.graphics.Bitmap as AndroidBitmap + +sealed interface DrawableResource { + data class Drawable(val id: Int): DrawableResource + data class Bitmap(val bitmap: AndroidBitmap): DrawableResource +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/Name.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/Name.kt new file mode 100644 index 0000000..cb56627 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/Name.kt @@ -0,0 +1,3 @@ +package com.krunal.demo.uicomponents.models + +data class Name(val firstName: String, val lastName: String) diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/Theme.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/Theme.kt new file mode 100644 index 0000000..400d7fa --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/Theme.kt @@ -0,0 +1,11 @@ +package com.krunal.demo.uicomponents.models + +import androidx.annotation.ColorRes +import com.krunal.demo.uicomponents.models.enums.AccentColor + +data class Theme( + val name: String, + val accentColor: AccentColor, + val color: Int, + val isDarkMode: Boolean = false +) diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/enums/AccentColor.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/enums/AccentColor.kt new file mode 100644 index 0000000..944286c --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/enums/AccentColor.kt @@ -0,0 +1,5 @@ +package com.krunal.demo.uicomponents.models.enums + +enum class AccentColor { + RED, BLUE, YELLOW, GREEN, PURPLE, VIOLET +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/enums/CardType.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/enums/CardType.kt new file mode 100644 index 0000000..d3ac2a3 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/enums/CardType.kt @@ -0,0 +1,11 @@ +package com.krunal.demo.uicomponents.models.enums + +import java.util.Locale + +enum class CardType { + DEBIT, CREDIT; + + override fun toString(): String { + return name.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.ROOT) else it.toString() } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/enums/ThemeMode.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/enums/ThemeMode.kt new file mode 100644 index 0000000..4b7b2f5 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/enums/ThemeMode.kt @@ -0,0 +1,5 @@ +package com.krunal.demo.uicomponents.models.enums + +enum class ThemeMode { + AUTO, DARK, LIGHT +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/picker/DatePickerFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/picker/DatePickerFragment.kt new file mode 100644 index 0000000..0c78ada --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/picker/DatePickerFragment.kt @@ -0,0 +1,31 @@ +package com.krunal.demo.uicomponents.picker + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.krunal.demo.databinding.FragmentDatePickerBinding +import java.util.Date + +class DatePickerFragment : Fragment() { + + private lateinit var binding: FragmentDatePickerBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentDatePickerBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupPicker() + } + + private fun setupPicker() { + binding.dpCalender.maxDate = Date().time + binding.dpCalender.updateDate(2023, 1, 1) + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/picker/TimePickerFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/picker/TimePickerFragment.kt new file mode 100644 index 0000000..8f99181 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/picker/TimePickerFragment.kt @@ -0,0 +1,62 @@ +package com.krunal.demo.uicomponents.picker + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TimePicker +import android.widget.TimePicker.OnTimeChangedListener +import androidx.fragment.app.Fragment +import com.krunal.demo.R +import com.krunal.demo.databinding.FragmentTimePickerBinding + +class TimePickerFragment : Fragment(), OnTimeChangedListener { + + private lateinit var binding: FragmentTimePickerBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentTimePickerBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupPicker() + } + + private fun setupPicker() { + // Set is 24 hour format + binding.switch24hour.setOnCheckedChangeListener { _, isChecked -> + binding.tpClock.setIs24HourView(isChecked) + binding.tpSpinner.setIs24HourView(isChecked) + } + + binding.tpClock.setOnTimeChangedListener(this) + binding.tpSpinner.setOnTimeChangedListener(this) + } + + override fun onTimeChanged(timePicker: TimePicker, hourOfDay: Int, minute: Int) { + // Sync both time pickers + if (timePicker == binding.tpClock) { + binding.tpSpinner.hour = hourOfDay + binding.tpSpinner.minute = minute + } else { + binding.tpClock.hour = hourOfDay + binding.tpClock.minute = minute + } + + // Update time text view + binding.tvTime.text = if (timePicker.is24HourView) { + getString(R.string.time, hourOfDay, minute, "") + } else { + getString( + R.string.time, + if (hourOfDay % 12 == 0) 12 else hourOfDay, + minute, + if (hourOfDay < 12) "AM" else "PM" + ) + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/sheets/OperationsBottomSheetFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/sheets/OperationsBottomSheetFragment.kt new file mode 100644 index 0000000..693b651 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/sheets/OperationsBottomSheetFragment.kt @@ -0,0 +1,35 @@ +package com.krunal.demo.uicomponents.sheets + +import android.annotation.SuppressLint +import android.app.Dialog +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import com.krunal.demo.databinding.FragmentOperationsBottomSheetBinding + +class OperationsBottomSheetFragment : BottomSheetDialogFragment() { + + private lateinit var binding: FragmentOperationsBottomSheetBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentOperationsBottomSheetBinding.inflate(layoutInflater) + return binding.root + } + + @SuppressLint("RestrictedApi", "VisibleForTests") + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val dialog = super.onCreateDialog(savedInstanceState) + + (dialog as BottomSheetDialog).behavior.apply { + isDraggable = true + state = BottomSheetBehavior.STATE_EXPANDED + } + return dialog + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/tablayoutfragments/CallsFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/tablayoutfragments/CallsFragment.kt new file mode 100644 index 0000000..9c9e82c --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/tablayoutfragments/CallsFragment.kt @@ -0,0 +1,6 @@ +package com.krunal.demo.uicomponents.tablayoutfragments + +import androidx.fragment.app.Fragment +import com.krunal.demo.R + +class CallsFragment : Fragment(R.layout.fragment_calls) \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/tablayoutfragments/ChatFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/tablayoutfragments/ChatFragment.kt new file mode 100644 index 0000000..9d9f346 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/tablayoutfragments/ChatFragment.kt @@ -0,0 +1,11 @@ +package com.krunal.demo.uicomponents.tablayoutfragments + +import android.os.Bundle +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.krunal.demo.R + +class ChatFragment : Fragment(R.layout.fragment_chat) { +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/tablayoutfragments/HomeFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/tablayoutfragments/HomeFragment.kt new file mode 100644 index 0000000..8d124ab --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/tablayoutfragments/HomeFragment.kt @@ -0,0 +1,66 @@ +package com.krunal.demo.uicomponents.tablayoutfragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.google.android.material.tabs.TabLayout +import com.google.android.material.tabs.TabLayout.OnTabSelectedListener +import com.krunal.demo.R +import com.krunal.demo.databinding.FragmentHomeBinding + +class HomeFragment : Fragment(R.layout.fragment_home) { + + private lateinit var binding: FragmentHomeBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentHomeBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupTabs() + } + + private fun setupTabs() { + // Text tabs programmatically + listOf("Tab1", "Tab2", "Tab3", "Tab4").forEach { + binding.tabLayoutText.apply { + addTab(newTab().setText(it)) + } + } + + // Show/Hide lable + binding.tabLayoutSelectedText.apply { + for (i in 0..tabCount) { + if (i != selectedTabPosition) { + getTabAt(i)?.tabLabelVisibility = TabLayout.TAB_LABEL_VISIBILITY_UNLABELED + } + } + } + binding.tabLayoutSelectedText.addOnTabSelectedListener(object : OnTabSelectedListener { + override fun onTabSelected(tab: TabLayout.Tab?) { + tab?.tabLabelVisibility = TabLayout.TAB_LABEL_VISIBILITY_LABELED + } + + override fun onTabUnselected(tab: TabLayout.Tab?) { + tab?.tabLabelVisibility = TabLayout.TAB_LABEL_VISIBILITY_UNLABELED + } + + override fun onTabReselected(tab: TabLayout.Tab?) { + + } + }) + + // Scrollable tabs + listOf("Tab1", "Tab2", "Tab3").forEach { + binding.tabLayoutScrollable.apply { + addTab(newTab().setText(it)) + } + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/utils/PreferenceKeys.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/utils/PreferenceKeys.kt new file mode 100644 index 0000000..70f4061 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/utils/PreferenceKeys.kt @@ -0,0 +1,11 @@ +package com.krunal.demo.uicomponents.utils + +object PreferenceKeys { + + /** + * Appearance + */ + const val THEME_MODE = "theme_mode" + const val ACCENT_COLOR = "accent_color" + +} diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/views/CustomView.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/views/CustomView.kt new file mode 100644 index 0000000..02e24ce --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/views/CustomView.kt @@ -0,0 +1,41 @@ +package com.krunal.demo.uicomponents.views + +import android.content.Context +import android.content.res.TypedArray +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.util.AttributeSet +import android.view.View +import com.krunal.demo.R + +class CustomView(context: Context, attrs: AttributeSet): View(context, attrs) { + + private val paint = Paint() + private var centerOfX = 340F + private var centerOfY = 340F + private val radiusOfCircleView = 140F + private var isCenter = false + + init { + val attributeArray: TypedArray = context.theme.obtainStyledAttributes( + attrs, R.styleable.CustomButton, 0, 0 + ) + + paint.apply { + color = attributeArray.getColor(R.styleable.CustomButton_circleColor, Color.RED) + strokeWidth = attributeArray.getFloat(R.styleable.CustomButton_strokeSize, 20f) + style = Paint.Style.STROKE + } + isCenter = attributeArray.getBoolean(R.styleable.CustomButton_onCenter, isCenter) + } + + override fun onDraw(canvas: Canvas?) { + if (isCenter) { + centerOfX = (width / 2).toFloat() + centerOfY = (height / 2).toFloat() + } + canvas?.drawCircle(centerOfX, centerOfY, radiusOfCircleView, paint) + super.onDraw(canvas) + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/views/HistoryLineView.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/views/HistoryLineView.kt new file mode 100644 index 0000000..3fd28d1 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/views/HistoryLineView.kt @@ -0,0 +1,70 @@ +package com.krunal.demo.uicomponents.views + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.util.AttributeSet +import android.view.View +import com.krunal.demo.R + +class HistoryLineView(context: Context, attrs: AttributeSet) : View(context, attrs) { + + private val thumbPaint = Paint(Paint.ANTI_ALIAS_FLAG) + private val linePaint = Paint(Paint.ANTI_ALIAS_FLAG) + private var thumbColor = Color.BLUE + private var lineColor = thumbColor + private var thumbRadius = 10F + private var lineWidth = 2F + + init { + + setupAttributes(attrs) + setupPaint() + } + + private fun setupAttributes(attrs: AttributeSet) { + val typedArray = + context.theme.obtainStyledAttributes(attrs, R.styleable.HistoryLineView, 0, 0) + thumbColor = typedArray.getColor(R.styleable.HistoryLineView_thumbColor, thumbColor) + lineColor = typedArray.getColor(R.styleable.HistoryLineView_lineColor, lineColor) + thumbRadius = typedArray.getDimension(R.styleable.HistoryLineView_thumbRadius, thumbRadius) + lineWidth = typedArray.getDimension(R.styleable.HistoryLineView_lineWidth, lineWidth) + +// typedArray.recycle() + } + + private fun setupPaint() { + thumbPaint.apply { + color = thumbColor + style = Paint.Style.FILL + } + + linePaint.apply { + color = lineColor + strokeWidth = lineWidth + style = Paint.Style.STROKE + strokeCap = Paint.Cap.ROUND + } + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + drawThumb(canvas) + drawLine(canvas) + } + + private fun drawThumb(canvas: Canvas) { + canvas.drawCircle((width / 2).toFloat(), thumbRadius, thumbRadius, thumbPaint) + } + + private fun drawLine(canvas: Canvas) { + val xPosition = (width / 2).toFloat() + val yStartPosition = (thumbRadius * 2) + lineWidth + val yEndPosition = height - yStartPosition + canvas.drawLine( + xPosition, yStartPosition, xPosition, yEndPosition, linePaint + ) + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/views/MyClickableSpan.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/views/MyClickableSpan.kt new file mode 100644 index 0000000..81d9176 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/views/MyClickableSpan.kt @@ -0,0 +1,18 @@ +package com.krunal.demo.uicomponents.views + +import android.text.TextPaint +import android.text.style.ClickableSpan +import android.view.View + +class MyClickableSpan( + private val onTextClick: () -> Unit +) : ClickableSpan() { + override fun onClick(view: View) { + onTextClick() + } + + override fun updateDrawState(ds: TextPaint) { + super.updateDrawState(ds) + ds.isUnderlineText = false + } +} \ No newline at end of file diff --git a/Demo/app/src/main/res/color/chip_card.xml b/Demo/app/src/main/res/color/chip_card.xml new file mode 100644 index 0000000..ed1c356 --- /dev/null +++ b/Demo/app/src/main/res/color/chip_card.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Demo/app/src/main/res/color/chip_card_text.xml b/Demo/app/src/main/res/color/chip_card_text.xml new file mode 100644 index 0000000..447acf5 --- /dev/null +++ b/Demo/app/src/main/res/color/chip_card_text.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Demo/app/src/main/res/color/chip_operations_time.xml b/Demo/app/src/main/res/color/chip_operations_time.xml new file mode 100644 index 0000000..cd4dbcd --- /dev/null +++ b/Demo/app/src/main/res/color/chip_operations_time.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Demo/app/src/main/res/drawable/android_dev.jpeg b/Demo/app/src/main/res/drawable/android_dev.jpeg new file mode 100644 index 0000000..24197b7 Binary files /dev/null and b/Demo/app/src/main/res/drawable/android_dev.jpeg differ diff --git a/Demo/app/src/main/res/drawable/calendar_banner.jpeg b/Demo/app/src/main/res/drawable/calendar_banner.jpeg new file mode 100644 index 0000000..6b784d4 Binary files /dev/null and b/Demo/app/src/main/res/drawable/calendar_banner.jpeg differ diff --git a/Demo/app/src/main/res/drawable/circle_image.xml b/Demo/app/src/main/res/drawable/circle_image.xml new file mode 100644 index 0000000..0a93ebf --- /dev/null +++ b/Demo/app/src/main/res/drawable/circle_image.xml @@ -0,0 +1,4 @@ + + + back + \ No newline at end of file diff --git a/Demo/app/src/main/res/drawable/dark_forest.jpeg b/Demo/app/src/main/res/drawable/dark_forest.jpeg new file mode 100644 index 0000000..bd25b0d Binary files /dev/null and b/Demo/app/src/main/res/drawable/dark_forest.jpeg differ diff --git a/Demo/app/src/main/res/drawable/default_pager_dot.xml b/Demo/app/src/main/res/drawable/default_pager_dot.xml new file mode 100644 index 0000000..f65a9d1 --- /dev/null +++ b/Demo/app/src/main/res/drawable/default_pager_dot.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/Demo/app/src/main/res/drawable/google_io.png b/Demo/app/src/main/res/drawable/google_io.png new file mode 100644 index 0000000..cff63cf Binary files /dev/null and b/Demo/app/src/main/res/drawable/google_io.png differ diff --git a/Demo/app/src/main/res/drawable/gradient_button.xml b/Demo/app/src/main/res/drawable/gradient_button.xml new file mode 100644 index 0000000..1b2e930 --- /dev/null +++ b/Demo/app/src/main/res/drawable/gradient_button.xml @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/Demo/app/src/main/res/drawable/gradient_progress.xml b/Demo/app/src/main/res/drawable/gradient_progress.xml new file mode 100644 index 0000000..5f6dc5f --- /dev/null +++ b/Demo/app/src/main/res/drawable/gradient_progress.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Demo/app/src/main/res/drawable/ic_action_more.xml b/Demo/app/src/main/res/drawable/ic_action_more.xml new file mode 100644 index 0000000..6a7f274 --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_action_more.xml @@ -0,0 +1,5 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_add.xml b/Demo/app/src/main/res/drawable/ic_add.xml new file mode 100644 index 0000000..70046c4 --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_add.xml @@ -0,0 +1,5 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_android.xml b/Demo/app/src/main/res/drawable/ic_android.xml new file mode 100644 index 0000000..1168496 --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_android.xml @@ -0,0 +1,5 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_arrow_down.xml b/Demo/app/src/main/res/drawable/ic_arrow_down.xml new file mode 100644 index 0000000..1aeaa99 --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_arrow_down.xml @@ -0,0 +1,5 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_back.xml b/Demo/app/src/main/res/drawable/ic_back.xml new file mode 100644 index 0000000..6c3197a --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_back.xml @@ -0,0 +1,5 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_calculator.xml b/Demo/app/src/main/res/drawable/ic_calculator.xml new file mode 100644 index 0000000..aec74eb --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_calculator.xml @@ -0,0 +1,4 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_calendar.xml b/Demo/app/src/main/res/drawable/ic_calendar.xml new file mode 100644 index 0000000..b6caf72 --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_calendar.xml @@ -0,0 +1,5 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_call.xml b/Demo/app/src/main/res/drawable/ic_call.xml new file mode 100644 index 0000000..2aba8ad --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_call.xml @@ -0,0 +1,5 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_card.xml b/Demo/app/src/main/res/drawable/ic_card.xml new file mode 100644 index 0000000..838fb42 --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_card.xml @@ -0,0 +1,5 @@ + + + + diff --git a/Demo/app/src/main/res/drawable/ic_chat.xml b/Demo/app/src/main/res/drawable/ic_chat.xml new file mode 100644 index 0000000..9e7de07 --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_chat.xml @@ -0,0 +1,5 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_check.xml b/Demo/app/src/main/res/drawable/ic_check.xml new file mode 100644 index 0000000..fbc59ec --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_check.xml @@ -0,0 +1,10 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_circle.xml b/Demo/app/src/main/res/drawable/ic_circle.xml new file mode 100644 index 0000000..7c795ea --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_circle.xml @@ -0,0 +1,10 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_comment.xml b/Demo/app/src/main/res/drawable/ic_comment.xml new file mode 100644 index 0000000..99f9a4d --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_comment.xml @@ -0,0 +1,5 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_cross.xml b/Demo/app/src/main/res/drawable/ic_cross.xml new file mode 100644 index 0000000..70db409 --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_cross.xml @@ -0,0 +1,5 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_dislike_outlined.xml b/Demo/app/src/main/res/drawable/ic_dislike_outlined.xml new file mode 100644 index 0000000..94d5972 --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_dislike_outlined.xml @@ -0,0 +1,5 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_email.xml b/Demo/app/src/main/res/drawable/ic_email.xml new file mode 100644 index 0000000..80111c8 --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_email.xml @@ -0,0 +1,5 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_feed.xml b/Demo/app/src/main/res/drawable/ic_feed.xml new file mode 100644 index 0000000..4a4e158 --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_feed.xml @@ -0,0 +1,5 @@ + + + + diff --git a/Demo/app/src/main/res/drawable/ic_female.xml b/Demo/app/src/main/res/drawable/ic_female.xml new file mode 100644 index 0000000..ea62387 --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_female.xml @@ -0,0 +1,9 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_food.xml b/Demo/app/src/main/res/drawable/ic_food.xml new file mode 100644 index 0000000..45861e1 --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_food.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + diff --git a/Demo/app/src/main/res/drawable/ic_gasoline.xml b/Demo/app/src/main/res/drawable/ic_gasoline.xml new file mode 100644 index 0000000..bc05081 --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_gasoline.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/Demo/app/src/main/res/drawable/ic_grid_view.xml b/Demo/app/src/main/res/drawable/ic_grid_view.xml new file mode 100644 index 0000000..fdf8704 --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_grid_view.xml @@ -0,0 +1,6 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_heart_outlined.xml b/Demo/app/src/main/res/drawable/ic_heart_outlined.xml new file mode 100644 index 0000000..58c0403 --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_heart_outlined.xml @@ -0,0 +1,4 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_home.xml b/Demo/app/src/main/res/drawable/ic_home.xml new file mode 100644 index 0000000..4c5e854 --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_home.xml @@ -0,0 +1,5 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_image.xml b/Demo/app/src/main/res/drawable/ic_image.xml new file mode 100644 index 0000000..d35859d --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_image.xml @@ -0,0 +1,5 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_info.xml b/Demo/app/src/main/res/drawable/ic_info.xml new file mode 100644 index 0000000..e0ecb40 --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_info.xml @@ -0,0 +1,5 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_like_outlined.xml b/Demo/app/src/main/res/drawable/ic_like_outlined.xml new file mode 100644 index 0000000..6a44382 --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_like_outlined.xml @@ -0,0 +1,5 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_list.xml b/Demo/app/src/main/res/drawable/ic_list.xml new file mode 100644 index 0000000..2610659 --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_list.xml @@ -0,0 +1,5 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_male.xml b/Demo/app/src/main/res/drawable/ic_male.xml new file mode 100644 index 0000000..97af074 --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_male.xml @@ -0,0 +1,12 @@ + + + + diff --git a/Demo/app/src/main/res/drawable/ic_mastercard.xml b/Demo/app/src/main/res/drawable/ic_mastercard.xml new file mode 100644 index 0000000..3405fd8 --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_mastercard.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/Demo/app/src/main/res/drawable/ic_menu.xml b/Demo/app/src/main/res/drawable/ic_menu.xml new file mode 100644 index 0000000..470db52 --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_menu.xml @@ -0,0 +1,5 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_next.xml b/Demo/app/src/main/res/drawable/ic_next.xml new file mode 100644 index 0000000..4680420 --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_next.xml @@ -0,0 +1,5 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_other_gender.xml b/Demo/app/src/main/res/drawable/ic_other_gender.xml new file mode 100644 index 0000000..856139c --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_other_gender.xml @@ -0,0 +1,9 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_paperplane.xml b/Demo/app/src/main/res/drawable/ic_paperplane.xml new file mode 100644 index 0000000..bb0e194 --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_paperplane.xml @@ -0,0 +1,5 @@ + + + + diff --git a/Demo/app/src/main/res/drawable/ic_pause.xml b/Demo/app/src/main/res/drawable/ic_pause.xml new file mode 100644 index 0000000..f701d6f --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_pause.xml @@ -0,0 +1,5 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_pause_outlined.xml b/Demo/app/src/main/res/drawable/ic_pause_outlined.xml new file mode 100644 index 0000000..dbf5f42 --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_pause_outlined.xml @@ -0,0 +1,5 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_pen.xml b/Demo/app/src/main/res/drawable/ic_pen.xml new file mode 100644 index 0000000..faddfce --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_pen.xml @@ -0,0 +1,5 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_play.xml b/Demo/app/src/main/res/drawable/ic_play.xml new file mode 100644 index 0000000..0870be8 --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_play.xml @@ -0,0 +1,5 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_play_outlined.xml b/Demo/app/src/main/res/drawable/ic_play_outlined.xml new file mode 100644 index 0000000..c4f8875 --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_play_outlined.xml @@ -0,0 +1,5 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_previous.xml b/Demo/app/src/main/res/drawable/ic_previous.xml new file mode 100644 index 0000000..544bf5b --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_previous.xml @@ -0,0 +1,5 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_profile.xml b/Demo/app/src/main/res/drawable/ic_profile.xml new file mode 100644 index 0000000..ea4bd5c --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_profile.xml @@ -0,0 +1,4 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_receive.xml b/Demo/app/src/main/res/drawable/ic_receive.xml new file mode 100644 index 0000000..5b10648 --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_receive.xml @@ -0,0 +1,5 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_right_arrow.xml b/Demo/app/src/main/res/drawable/ic_right_arrow.xml new file mode 100644 index 0000000..a749bde --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_right_arrow.xml @@ -0,0 +1,5 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_search.xml b/Demo/app/src/main/res/drawable/ic_search.xml new file mode 100644 index 0000000..6031484 --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_search.xml @@ -0,0 +1,10 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_send.xml b/Demo/app/src/main/res/drawable/ic_send.xml new file mode 100644 index 0000000..5b10648 --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_send.xml @@ -0,0 +1,5 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_splash.xml b/Demo/app/src/main/res/drawable/ic_splash.xml new file mode 100644 index 0000000..ce0d04e --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_splash.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + diff --git a/Demo/app/src/main/res/drawable/ic_timezone.xml b/Demo/app/src/main/res/drawable/ic_timezone.xml new file mode 100644 index 0000000..3476385 --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_timezone.xml @@ -0,0 +1,5 @@ + + + + diff --git a/Demo/app/src/main/res/drawable/ic_two_circle.xml b/Demo/app/src/main/res/drawable/ic_two_circle.xml new file mode 100644 index 0000000..87fc77a --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_two_circle.xml @@ -0,0 +1,4 @@ + + + diff --git a/Demo/app/src/main/res/drawable/ic_video_library.xml b/Demo/app/src/main/res/drawable/ic_video_library.xml new file mode 100644 index 0000000..476e56f --- /dev/null +++ b/Demo/app/src/main/res/drawable/ic_video_library.xml @@ -0,0 +1,5 @@ + + + diff --git a/Demo/app/src/main/res/drawable/image_toggle.xml b/Demo/app/src/main/res/drawable/image_toggle.xml new file mode 100644 index 0000000..56adc24 --- /dev/null +++ b/Demo/app/src/main/res/drawable/image_toggle.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Demo/app/src/main/res/drawable/img.png b/Demo/app/src/main/res/drawable/img.png new file mode 100644 index 0000000..bc5121c Binary files /dev/null and b/Demo/app/src/main/res/drawable/img.png differ diff --git a/Demo/app/src/main/res/drawable/netflix_logo.png b/Demo/app/src/main/res/drawable/netflix_logo.png new file mode 100644 index 0000000..9b1e449 Binary files /dev/null and b/Demo/app/src/main/res/drawable/netflix_logo.png differ diff --git a/Demo/app/src/main/res/drawable/profile.jpeg b/Demo/app/src/main/res/drawable/profile.jpeg new file mode 100644 index 0000000..74d27a6 Binary files /dev/null and b/Demo/app/src/main/res/drawable/profile.jpeg differ diff --git a/Demo/app/src/main/res/drawable/progress_vertical.xml b/Demo/app/src/main/res/drawable/progress_vertical.xml new file mode 100644 index 0000000..c4350b1 --- /dev/null +++ b/Demo/app/src/main/res/drawable/progress_vertical.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Demo/app/src/main/res/drawable/progress_vertical_graph.xml b/Demo/app/src/main/res/drawable/progress_vertical_graph.xml new file mode 100644 index 0000000..915dae8 --- /dev/null +++ b/Demo/app/src/main/res/drawable/progress_vertical_graph.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Demo/app/src/main/res/drawable/rounded_bottom_bar.xml b/Demo/app/src/main/res/drawable/rounded_bottom_bar.xml new file mode 100644 index 0000000..b12f214 --- /dev/null +++ b/Demo/app/src/main/res/drawable/rounded_bottom_bar.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/Demo/app/src/main/res/drawable/rounded_bottom_sheet.xml b/Demo/app/src/main/res/drawable/rounded_bottom_sheet.xml new file mode 100644 index 0000000..98c2220 --- /dev/null +++ b/Demo/app/src/main/res/drawable/rounded_bottom_sheet.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/Demo/app/src/main/res/drawable/rounded_close_image_button.xml b/Demo/app/src/main/res/drawable/rounded_close_image_button.xml new file mode 100644 index 0000000..95af284 --- /dev/null +++ b/Demo/app/src/main/res/drawable/rounded_close_image_button.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Demo/app/src/main/res/drawable/rounded_image.xml b/Demo/app/src/main/res/drawable/rounded_image.xml new file mode 100644 index 0000000..ce56f8b --- /dev/null +++ b/Demo/app/src/main/res/drawable/rounded_image.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/Demo/app/src/main/res/drawable/rounded_toast.xml b/Demo/app/src/main/res/drawable/rounded_toast.xml new file mode 100644 index 0000000..db31de8 --- /dev/null +++ b/Demo/app/src/main/res/drawable/rounded_toast.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/Demo/app/src/main/res/drawable/running_up_that_hill.png b/Demo/app/src/main/res/drawable/running_up_that_hill.png new file mode 100644 index 0000000..82e0545 Binary files /dev/null and b/Demo/app/src/main/res/drawable/running_up_that_hill.png differ diff --git a/Demo/app/src/main/res/drawable/selected_pager_dot.xml b/Demo/app/src/main/res/drawable/selected_pager_dot.xml new file mode 100644 index 0000000..1fae4fb --- /dev/null +++ b/Demo/app/src/main/res/drawable/selected_pager_dot.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/Demo/app/src/main/res/drawable/selected_theme.xml b/Demo/app/src/main/res/drawable/selected_theme.xml new file mode 100644 index 0000000..0c30dbd --- /dev/null +++ b/Demo/app/src/main/res/drawable/selected_theme.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Demo/app/src/main/res/drawable/tab_pager_selector.xml b/Demo/app/src/main/res/drawable/tab_pager_selector.xml new file mode 100644 index 0000000..c5df60c --- /dev/null +++ b/Demo/app/src/main/res/drawable/tab_pager_selector.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/Demo/app/src/main/res/drawable/thumbnail1.jpeg b/Demo/app/src/main/res/drawable/thumbnail1.jpeg new file mode 100644 index 0000000..0464d01 Binary files /dev/null and b/Demo/app/src/main/res/drawable/thumbnail1.jpeg differ diff --git a/Demo/app/src/main/res/drawable/time_banner.jpeg b/Demo/app/src/main/res/drawable/time_banner.jpeg new file mode 100644 index 0000000..147a3f1 Binary files /dev/null and b/Demo/app/src/main/res/drawable/time_banner.jpeg differ diff --git a/Demo/app/src/main/res/layout/activity_main.xml b/Demo/app/src/main/res/layout/activity_main.xml index 6656eb1..6fc97e1 100644 --- a/Demo/app/src/main/res/layout/activity_main.xml +++ b/Demo/app/src/main/res/layout/activity_main.xml @@ -1,21 +1,31 @@ + - + - + + - \ No newline at end of file + + + + + + + \ No newline at end of file diff --git a/Demo/app/src/main/res/layout/activity_uicomponents.xml b/Demo/app/src/main/res/layout/activity_uicomponents.xml new file mode 100644 index 0000000..5851e1c --- /dev/null +++ b/Demo/app/src/main/res/layout/activity_uicomponents.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/Demo/app/src/main/res/layout/card_chip.xml b/Demo/app/src/main/res/layout/card_chip.xml new file mode 100644 index 0000000..8d95f42 --- /dev/null +++ b/Demo/app/src/main/res/layout/card_chip.xml @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/Demo/app/src/main/res/layout/card_layout.xml b/Demo/app/src/main/res/layout/card_layout.xml new file mode 100644 index 0000000..8448f1c --- /dev/null +++ b/Demo/app/src/main/res/layout/card_layout.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Demo/app/src/main/res/layout/chat_contact_details_layout.xml b/Demo/app/src/main/res/layout/chat_contact_details_layout.xml new file mode 100644 index 0000000..123ca9e --- /dev/null +++ b/Demo/app/src/main/res/layout/chat_contact_details_layout.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Demo/app/src/main/res/layout/community_post_layout.xml b/Demo/app/src/main/res/layout/community_post_layout.xml new file mode 100644 index 0000000..43d0853 --- /dev/null +++ b/Demo/app/src/main/res/layout/community_post_layout.xml @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Demo/app/src/main/res/layout/contact_layout.xml b/Demo/app/src/main/res/layout/contact_layout.xml new file mode 100644 index 0000000..34532cb --- /dev/null +++ b/Demo/app/src/main/res/layout/contact_layout.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Demo/app/src/main/res/layout/custom_toast.xml b/Demo/app/src/main/res/layout/custom_toast.xml new file mode 100644 index 0000000..44d0ada --- /dev/null +++ b/Demo/app/src/main/res/layout/custom_toast.xml @@ -0,0 +1,23 @@ + + + + + + + \ No newline at end of file diff --git a/Demo/app/src/main/res/layout/feed_video_layout.xml b/Demo/app/src/main/res/layout/feed_video_layout.xml new file mode 100644 index 0000000..b28ea52 --- /dev/null +++ b/Demo/app/src/main/res/layout/feed_video_layout.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Demo/app/src/main/res/layout/fragment_app_bar.xml b/Demo/app/src/main/res/layout/fragment_app_bar.xml new file mode 100644 index 0000000..12ac20d --- /dev/null +++ b/Demo/app/src/main/res/layout/fragment_app_bar.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Demo/app/src/main/res/layout/fragment_button.xml b/Demo/app/src/main/res/layout/fragment_button.xml new file mode 100644 index 0000000..d333081 --- /dev/null +++ b/Demo/app/src/main/res/layout/fragment_button.xml @@ -0,0 +1,149 @@ + + + +