diff --git a/Demo/app/build.gradle b/Demo/app/build.gradle
index 8382565..39c8592 100644
--- a/Demo/app/build.gradle
+++ b/Demo/app/build.gradle
@@ -2,6 +2,7 @@ plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
+ id 'androidx.navigation.safeargs.kotlin'
}
android {
@@ -40,6 +41,8 @@ android {
dependencies {
def lifecycle_version = "2.6.1"
+ def nav_version = "2.5.3"
+
implementation 'androidx.core:core-ktx:1.10.1'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
@@ -49,6 +52,12 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "androidx.core:core-splashscreen:1.0.1"
implementation "androidx.preference:preference-ktx:1.2.0"
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1"
+ implementation 'androidx.fragment:fragment-ktx:1.5.7'
+
+ // Navigation
+ implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
+ implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
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 4858a5c..211832d 100644
--- a/Demo/app/src/main/AndroidManifest.xml
+++ b/Demo/app/src/main/AndroidManifest.xml
@@ -2,6 +2,16 @@
+
+
+
+
+
+
+
+
+
+ android:theme="@style/Theme.StackExchange" />
+
+
-
+
+ android:name=".appcomponents.ui.activities.ImagePickerActivity"
+ android:exported="true"
+ android:label="DemoImagePicker"
+ android:taskAffinity="com.krunal.demo.imagePicker">
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/DemoApplication.kt b/Demo/app/src/main/java/com/krunal/demo/DemoApplication.kt
index 45e7964..212788b 100644
--- a/Demo/app/src/main/java/com/krunal/demo/DemoApplication.kt
+++ b/Demo/app/src/main/java/com/krunal/demo/DemoApplication.kt
@@ -1,7 +1,8 @@
package com.krunal.demo
import android.app.Application
-import com.krunal.demo.uicomponents.helpers.PreferenceHelper
+import com.krunal.demo.helpers.PreferenceHelper
+import com.krunal.demo.searchwebview.helpers.PackageHelper
class DemoApplication: Application() {
@@ -14,6 +15,11 @@ class DemoApplication: Application() {
* Initialize [PreferenceHelper]
*/
PreferenceHelper.initialize(applicationContext)
+
+ /**
+ * Initialize [PackageHelper]
+ */
+ PackageHelper.initialize(applicationContext)
}
companion object {
diff --git a/Demo/app/src/main/java/com/krunal/demo/UIComponentsActivity.kt b/Demo/app/src/main/java/com/krunal/demo/UIComponentsActivity.kt
index 6713d53..e09a3d6 100644
--- a/Demo/app/src/main/java/com/krunal/demo/UIComponentsActivity.kt
+++ b/Demo/app/src/main/java/com/krunal/demo/UIComponentsActivity.kt
@@ -11,7 +11,7 @@ import com.krunal.demo.uicomponents.CoordinatorLayoutFragment
import com.krunal.demo.uicomponents.ListViewFragment
import com.krunal.demo.uicomponents.ThemeFragment
import com.krunal.demo.uicomponents.cardscreen.CardFragment
-import com.krunal.demo.uicomponents.helpers.ThemeHelper
+import com.krunal.demo.helpers.ThemeHelper
class UIComponentsActivity : AppCompatActivity() {
diff --git a/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/activities/FirstActivity.kt b/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/activities/FirstActivity.kt
new file mode 100644
index 0000000..9783b9b
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/activities/FirstActivity.kt
@@ -0,0 +1,85 @@
+package com.krunal.demo.appcomponents.ui.activities
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.util.Log
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContract
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
+import androidx.fragment.app.commitNow
+import com.krunal.demo.appcomponents.ui.fragments.FirstFragment
+import com.krunal.demo.appcomponents.ui.viewmodels.FirstActivityViewModel
+import com.krunal.demo.appcomponents.utils.IntentData
+import com.krunal.demo.appcomponents.utils.LifecycleLogger
+import com.krunal.demo.databinding.ActivityFirstBinding
+
+class FirstActivity : AppCompatActivity() {
+
+ private lateinit var binding: ActivityFirstBinding
+ private val viewModel: FirstActivityViewModel by viewModels()
+ private val activityResultLauncher: ActivityResultLauncher = registerActivityForResult()
+
+ init {
+ LifecycleLogger(this)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ installSplashScreen()
+
+ binding = ActivityFirstBinding.inflate(layoutInflater)
+ binding.lifecycleOwner = this
+ binding.viewModel = viewModel
+ setContentView(binding.root)
+
+ setupUI()
+ }
+
+ private fun setupUI() {
+ binding.btnSecondActivity.setOnClickListener {
+ val intent = Intent(this, SecondActivity::class.java).apply {
+ putExtra(IntentData.MESSAGE, viewModel.message.value)
+ }
+ startActivityForResult(intent, MESSAGE_REQUEST_CODE)
+ }
+
+ binding.btnFragment.setOnClickListener {
+ activityResultLauncher.launch(Unit)
+ }
+ }
+
+ private fun registerActivityForResult(): ActivityResultLauncher {
+ return registerForActivityResult(object : ActivityResultContract() {
+ override fun createIntent(context: Context, input: Unit): Intent = Intent(this@FirstActivity, FragmentDemoActivity::class.java).apply {
+ putExtra(IntentData.MESSAGE, viewModel.message.value)
+ }
+
+ override fun parseResult(resultCode: Int, intent: Intent?): String? {
+ if (resultCode == Activity.RESULT_OK) {
+ return intent?.getStringExtra(IntentData.MESSAGE)
+ }
+ return null
+ }
+
+ }) {
+ it?.let(viewModel::setMessage)
+ }
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+
+ if (requestCode == MESSAGE_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
+ data?.getStringExtra(IntentData.MESSAGE)?.let(viewModel::setMessage)
+ }
+ }
+
+ companion object {
+ private const val MESSAGE_REQUEST_CODE = 101
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/activities/FragmentDemoActivity.kt b/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/activities/FragmentDemoActivity.kt
new file mode 100644
index 0000000..e8e3514
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/activities/FragmentDemoActivity.kt
@@ -0,0 +1,58 @@
+package com.krunal.demo.appcomponents.ui.activities
+
+import android.app.Activity
+import android.app.PendingIntent
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.commit
+import com.krunal.demo.R
+import com.krunal.demo.appcomponents.ui.fragments.FirstFragment
+import com.krunal.demo.appcomponents.ui.viewmodels.FragmentDemoViewModel
+import com.krunal.demo.appcomponents.utils.IntentData
+import com.krunal.demo.databinding.ActivityFragmentDemoBinding
+
+class FragmentDemoActivity : AppCompatActivity() {
+
+ private lateinit var binding: ActivityFragmentDemoBinding
+ private val viewModel: FragmentDemoViewModel by viewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ binding = ActivityFragmentDemoBinding.inflate(layoutInflater)
+ binding.lifecycleOwner = this
+ binding.viewModel = viewModel
+ setContentView(binding.root)
+ setupInitialValue(intent)
+ setupUI()
+ }
+
+ private fun setupInitialValue(intent: Intent) {
+ intent.getStringExtra(Intent.EXTRA_TEXT)?.let(viewModel::setMessage)
+ intent.getStringExtra(IntentData.MESSAGE)?.let(viewModel::setMessage)
+ }
+
+ private fun setupUI() {
+ val data = Bundle().apply {
+ putString(IntentData.MESSAGE, viewModel.message.value)
+ }
+
+ supportFragmentManager.commit {
+ add(R.id.fragmentContainer, FirstFragment().apply {
+ arguments = data
+ })
+ }
+ }
+
+ override fun finish() {
+ val message = viewModel.message.value
+ val intent = Intent().apply {
+ putExtra(IntentData.MESSAGE, message)
+ }
+ setResult(Activity.RESULT_OK, intent)
+ super.finish()
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/activities/ImagePickerActivity.kt b/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/activities/ImagePickerActivity.kt
new file mode 100644
index 0000000..d0109d5
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/activities/ImagePickerActivity.kt
@@ -0,0 +1,87 @@
+package com.krunal.demo.appcomponents.ui.activities
+
+import android.Manifest
+import android.content.pm.PackageManager
+import android.net.Uri
+import android.os.Bundle
+import android.os.Environment
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.FileProvider
+import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
+import androidx.lifecycle.lifecycleScope
+import com.krunal.demo.appcomponents.ui.viewmodels.ImagePickerViewModel
+import com.krunal.demo.databinding.ActivityImagePickerBinding
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+import java.io.File
+
+class ImagePickerActivity : AppCompatActivity() {
+
+ private lateinit var binding: ActivityImagePickerBinding
+ private val viewModel: ImagePickerViewModel by viewModels()
+ private val activityResultLauncher: ActivityResultLauncher =
+ registerActivityResultLauncher()
+ private val uri: Uri by lazy {
+ val file = File(Environment.getExternalStorageDirectory().listFiles()[8], "cameraPick")
+ FileProvider.getUriForFile(this, "${applicationContext.packageName}.provider", file)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ installSplashScreen()
+
+ binding = ActivityImagePickerBinding.inflate(layoutInflater)
+ binding.lifecycleOwner = this
+ binding.viewModel = viewModel
+ setContentView(binding.root)
+ setupUI()
+ }
+
+ override fun onRequestPermissionsResult(
+ requestCode: Int, permissions: Array, grantResults: IntArray
+ ) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ if (requestCode == MY_CAMERA_REQUEST_CODE && grantResults.first() == PackageManager.PERMISSION_GRANTED) {
+ takePicture()
+ }
+ }
+
+ private fun registerActivityResultLauncher(): ActivityResultLauncher {
+ return registerForActivityResult(ActivityResultContracts.TakePicture()) {
+ if (it) viewModel.setImage(uri)
+ }
+ }
+
+ private fun setupUI() {
+ binding.btnPickImage.setOnClickListener {
+ if (checkCameraPermission()) {
+ takePicture()
+ }
+ }
+
+ lifecycleScope.launch {
+ viewModel.image.collectLatest { uri ->
+ binding.imgView.setImageURI(uri)
+ }
+ }
+ }
+
+ private fun takePicture() {
+ activityResultLauncher.launch(uri)
+ }
+
+ private fun checkCameraPermission(): Boolean {
+ if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
+ requestPermissions(arrayOf(Manifest.permission.CAMERA), MY_CAMERA_REQUEST_CODE)
+ return false
+ }
+ return true
+ }
+
+ companion object {
+ private const val MY_CAMERA_REQUEST_CODE = 100
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/activities/SecondActivity.kt b/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/activities/SecondActivity.kt
new file mode 100644
index 0000000..1f7ecd5
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/activities/SecondActivity.kt
@@ -0,0 +1,53 @@
+package com.krunal.demo.appcomponents.ui.activities
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import com.krunal.demo.appcomponents.ui.viewmodels.SecondActivityViewModel
+import com.krunal.demo.appcomponents.utils.IntentData
+import com.krunal.demo.databinding.ActivitySecondBinding
+
+class SecondActivity : AppCompatActivity() {
+
+ private lateinit var binding: ActivitySecondBinding
+ private val viewModel: SecondActivityViewModel by viewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ binding = ActivitySecondBinding.inflate(layoutInflater)
+ binding.viewModel = viewModel
+ setContentView(binding.root)
+
+ setupUI()
+ setupInitialValue(intent)
+ }
+
+ private fun setupInitialValue(intent: Intent) {
+ intent.getStringExtra(IntentData.MESSAGE)?.let(viewModel::setMessage)
+ }
+
+ private fun setupUI() {
+ binding.btnGoBackActivity.setOnClickListener {
+ finish()
+ }
+
+ binding.btnFragment.setOnClickListener {
+ val intent = Intent(IntentData.ACTION_MESSAGE).apply {
+ putExtra(IntentData.MESSAGE, viewModel.message.value)
+ type = "text/plain"
+ }
+ startActivity(intent)
+ }
+ }
+
+ override fun finish() {
+ val intent = Intent().apply {
+ putExtra(IntentData.MESSAGE, viewModel.message.value)
+ }
+ setResult(Activity.RESULT_OK, intent)
+ super.finish()
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/fragments/FirstFragment.kt b/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/fragments/FirstFragment.kt
new file mode 100644
index 0000000..1f90fcf
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/fragments/FirstFragment.kt
@@ -0,0 +1,72 @@
+package com.krunal.demo.appcomponents.ui.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.activityViewModels
+import androidx.fragment.app.commit
+import androidx.fragment.app.viewModels
+import com.krunal.demo.R
+import com.krunal.demo.appcomponents.ui.viewmodels.FirstFragmentViewModel
+import com.krunal.demo.appcomponents.ui.viewmodels.FragmentDemoViewModel
+import com.krunal.demo.appcomponents.utils.IntentData
+import com.krunal.demo.appcomponents.utils.LifecycleLogger
+import com.krunal.demo.databinding.FragmentFirstBinding
+
+class FirstFragment : Fragment() {
+
+ private lateinit var binding: FragmentFirstBinding
+ private val viewModel: FirstFragmentViewModel by viewModels()
+ private val activityViewModel: FragmentDemoViewModel by activityViewModels()
+
+ init {
+ LifecycleLogger(this)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ binding = FragmentFirstBinding.inflate(inflater, container, false)
+ binding.lifecycleOwner = viewLifecycleOwner
+ binding.viewModel = viewModel
+ binding.activityViewModel = activityViewModel
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ setupUI()
+ }
+
+ private fun setupUI() {
+ setupInitialValue()
+
+ val data = Bundle().apply {
+ putString(IntentData.MESSAGE, viewModel.message.value)
+ }
+
+ binding.btnSecondFragment.setOnClickListener {
+ parentFragmentManager.commit {
+ replace(R.id.fragmentContainer, SecondFragment().apply {
+ arguments = data
+ })
+ }
+ }
+
+ binding.btnSecondActivity.setOnClickListener {
+ activity?.finish()
+ }
+ }
+
+ private fun setupInitialValue() {
+ arguments?.getString(IntentData.MESSAGE)?.let(viewModel::setMessage)
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/fragments/SecondFragment.kt b/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/fragments/SecondFragment.kt
new file mode 100644
index 0000000..e144a0a
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/fragments/SecondFragment.kt
@@ -0,0 +1,51 @@
+package com.krunal.demo.appcomponents.ui.fragments
+
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.activityViewModels
+import androidx.fragment.app.viewModels
+import com.krunal.demo.R
+import com.krunal.demo.appcomponents.ui.viewmodels.FirstFragmentViewModel
+import com.krunal.demo.appcomponents.ui.viewmodels.FragmentDemoViewModel
+import com.krunal.demo.appcomponents.ui.viewmodels.SecondFragmentViewModel
+import com.krunal.demo.appcomponents.utils.IntentData
+import com.krunal.demo.databinding.FragmentFirstBinding
+import com.krunal.demo.databinding.FragmentSecondBinding
+
+class SecondFragment : Fragment() {
+
+
+ private lateinit var binding: FragmentSecondBinding
+ private val viewModel: SecondFragmentViewModel by viewModels()
+ private val activityViewModel: FragmentDemoViewModel by activityViewModels()
+
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ binding = FragmentSecondBinding.inflate(inflater, container, false)
+ binding.viewModel = viewModel
+ binding.lifecycleOwner = viewLifecycleOwner
+ binding.activityViewModel = activityViewModel
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ setupUI()
+ }
+
+ private fun setupUI() {
+ setupInitialValue()
+ }
+
+ private fun setupInitialValue() {
+ arguments?.getString(IntentData.MESSAGE)?.let(viewModel::setMessage)
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/viewmodels/FirstActivityViewModel.kt b/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/viewmodels/FirstActivityViewModel.kt
new file mode 100644
index 0000000..02a719f
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/viewmodels/FirstActivityViewModel.kt
@@ -0,0 +1,19 @@
+package com.krunal.demo.appcomponents.ui.viewmodels
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import androidx.recyclerview.widget.RecyclerView
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+
+class FirstActivityViewModel: ViewModel() {
+
+ val message: MutableStateFlow = MutableStateFlow(null)
+
+ fun setMessage(message: String) {
+ viewModelScope.launch {
+ this@FirstActivityViewModel.message.emit(message)
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/viewmodels/FirstFragmentViewModel.kt b/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/viewmodels/FirstFragmentViewModel.kt
new file mode 100644
index 0000000..09a5ddf
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/viewmodels/FirstFragmentViewModel.kt
@@ -0,0 +1,18 @@
+package com.krunal.demo.appcomponents.ui.viewmodels
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+
+class FirstFragmentViewModel: ViewModel() {
+
+ val message: MutableStateFlow = MutableStateFlow(null)
+
+ fun setMessage(message: String) {
+ viewModelScope.launch {
+ this@FirstFragmentViewModel.message.emit(message)
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/viewmodels/FragmentDemoViewModel.kt b/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/viewmodels/FragmentDemoViewModel.kt
new file mode 100644
index 0000000..6612650
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/viewmodels/FragmentDemoViewModel.kt
@@ -0,0 +1,18 @@
+package com.krunal.demo.appcomponents.ui.viewmodels
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+
+class FragmentDemoViewModel: ViewModel() {
+
+ val message: MutableStateFlow = MutableStateFlow(null)
+
+ fun setMessage(message: String) {
+ viewModelScope.launch {
+ this@FragmentDemoViewModel.message.emit(message)
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/viewmodels/ImagePickerViewModel.kt b/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/viewmodels/ImagePickerViewModel.kt
new file mode 100644
index 0000000..3cd4ad9
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/viewmodels/ImagePickerViewModel.kt
@@ -0,0 +1,18 @@
+package com.krunal.demo.appcomponents.ui.viewmodels
+
+import android.net.Uri
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.launch
+
+class ImagePickerViewModel: ViewModel() {
+
+ val image: MutableStateFlow = MutableStateFlow(null)
+
+ fun setImage(image: Uri) {
+ viewModelScope.launch {
+ this@ImagePickerViewModel.image.emit(image)
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/viewmodels/SecondActivityViewModel.kt b/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/viewmodels/SecondActivityViewModel.kt
new file mode 100644
index 0000000..98fee28
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/viewmodels/SecondActivityViewModel.kt
@@ -0,0 +1,19 @@
+package com.krunal.demo.appcomponents.ui.viewmodels
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import androidx.recyclerview.widget.RecyclerView
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+
+class SecondActivityViewModel: ViewModel() {
+
+ val message: MutableStateFlow = MutableStateFlow(null)
+
+ fun setMessage(message: String) {
+ viewModelScope.launch {
+ this@SecondActivityViewModel.message.emit(message)
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/viewmodels/SecondFragmentViewModel.kt b/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/viewmodels/SecondFragmentViewModel.kt
new file mode 100644
index 0000000..00c7b70
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/appcomponents/ui/viewmodels/SecondFragmentViewModel.kt
@@ -0,0 +1,18 @@
+package com.krunal.demo.appcomponents.ui.viewmodels
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+
+class SecondFragmentViewModel: ViewModel() {
+
+ val message: MutableStateFlow = MutableStateFlow(null)
+
+ fun setMessage(message: String) {
+ viewModelScope.launch {
+ this@SecondFragmentViewModel.message.emit(message)
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/appcomponents/utils/IntentData.kt b/Demo/app/src/main/java/com/krunal/demo/appcomponents/utils/IntentData.kt
new file mode 100644
index 0000000..faaa8df
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/appcomponents/utils/IntentData.kt
@@ -0,0 +1,16 @@
+package com.krunal.demo.appcomponents.utils
+
+object IntentData {
+
+ /**
+ * Intent data keys
+ */
+ const val MESSAGE = "message"
+ const val AUTH_KEY = "auth_key"
+
+ /**
+ * Actions
+ */
+ const val ACTION_MESSAGE = "com.krunal.demo.action.MESSAGE"
+ const val ACTION_TRIVIA = "com.krunal.demo.action.TRIVIA"
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/appcomponents/utils/LifecycleLogger.kt b/Demo/app/src/main/java/com/krunal/demo/appcomponents/utils/LifecycleLogger.kt
new file mode 100644
index 0000000..c3c10b7
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/appcomponents/utils/LifecycleLogger.kt
@@ -0,0 +1,28 @@
+package com.krunal.demo.appcomponents.utils
+
+import android.util.Log
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.LifecycleOwner
+
+/**
+ * Observe and log [Lifecycle.Event] for [LifecycleOwner]
+ */
+class LifecycleLogger(lifecycleOwner: LifecycleOwner) {
+
+ init {
+ lifecycleOwner.lifecycle.addObserver(object : LifecycleEventObserver {
+ override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
+ log(source::class.simpleName, event.name)
+ }
+ })
+ }
+
+ private fun log(name: String?, state: String) {
+ Log.i(TAG, "[$name state]: $state")
+ }
+
+ companion object {
+ private const val TAG = "LifecycleLogger"
+ }
+}
\ 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/helpers/PreferenceHelper.kt
similarity index 97%
rename from Demo/app/src/main/java/com/krunal/demo/uicomponents/helpers/PreferenceHelper.kt
rename to Demo/app/src/main/java/com/krunal/demo/helpers/PreferenceHelper.kt
index 4c7cca8..a1d36c3 100644
--- a/Demo/app/src/main/java/com/krunal/demo/uicomponents/helpers/PreferenceHelper.kt
+++ b/Demo/app/src/main/java/com/krunal/demo/helpers/PreferenceHelper.kt
@@ -1,4 +1,4 @@
-package com.krunal.demo.uicomponents.helpers
+package com.krunal.demo.helpers
import android.content.Context
import android.content.SharedPreferences
diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/helpers/ThemeHelper.kt b/Demo/app/src/main/java/com/krunal/demo/helpers/ThemeHelper.kt
similarity index 96%
rename from Demo/app/src/main/java/com/krunal/demo/uicomponents/helpers/ThemeHelper.kt
rename to Demo/app/src/main/java/com/krunal/demo/helpers/ThemeHelper.kt
index 2bedaae..edb4552 100644
--- a/Demo/app/src/main/java/com/krunal/demo/uicomponents/helpers/ThemeHelper.kt
+++ b/Demo/app/src/main/java/com/krunal/demo/helpers/ThemeHelper.kt
@@ -1,11 +1,11 @@
-package com.krunal.demo.uicomponents.helpers
+package com.krunal.demo.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
+import com.krunal.demo.utils.PreferenceKeys
object ThemeHelper {
fun getThemes(context: Context, isDark: Boolean = false): List {
diff --git a/Demo/app/src/main/java/com/krunal/demo/navigation/data/models/Credential.kt b/Demo/app/src/main/java/com/krunal/demo/navigation/data/models/Credential.kt
new file mode 100644
index 0000000..acc0a9d
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/navigation/data/models/Credential.kt
@@ -0,0 +1,6 @@
+package com.krunal.demo.navigation.data.models
+
+data class Credential(
+ val email: String,
+ val password: String
+)
diff --git a/Demo/app/src/main/java/com/krunal/demo/navigation/data/models/Match.kt b/Demo/app/src/main/java/com/krunal/demo/navigation/data/models/Match.kt
new file mode 100644
index 0000000..d219a8e
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/navigation/data/models/Match.kt
@@ -0,0 +1,8 @@
+package com.krunal.demo.navigation.data.models
+
+data class Match(
+ val matchId: Int,
+ val user1: UserProfile,
+ val user2: UserProfile,
+ val winner: UserProfile? = null
+)
diff --git a/Demo/app/src/main/java/com/krunal/demo/navigation/data/models/RegisterDetail.kt b/Demo/app/src/main/java/com/krunal/demo/navigation/data/models/RegisterDetail.kt
new file mode 100644
index 0000000..f4fae24
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/navigation/data/models/RegisterDetail.kt
@@ -0,0 +1,36 @@
+package com.krunal.demo.navigation.data.models
+
+import androidx.databinding.BaseObservable
+import androidx.databinding.Bindable
+import com.krunal.demo.BR
+
+data class RegisterDetail(
+ private var _username: String = "",
+ private var _email: String = "",
+ private var _password: String = ""
+): BaseObservable() {
+
+ @get:Bindable
+ var username: String = _username
+ set(value) {
+ _username = value
+ field = value
+ notifyPropertyChanged(BR.username)
+ }
+
+ @get:Bindable
+ var email: String = _email
+ set(value) {
+ _email = value
+ field = value
+ notifyPropertyChanged(BR.email)
+ }
+
+ @get:Bindable
+ var password: String = _password
+ set(value) {
+ _password = value
+ field = value
+ notifyPropertyChanged(BR.password)
+ }
+}
diff --git a/Demo/app/src/main/java/com/krunal/demo/navigation/data/models/UserProfile.kt b/Demo/app/src/main/java/com/krunal/demo/navigation/data/models/UserProfile.kt
new file mode 100644
index 0000000..80e1162
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/navigation/data/models/UserProfile.kt
@@ -0,0 +1,14 @@
+package com.krunal.demo.navigation.data.models
+
+import androidx.annotation.DrawableRes
+
+data class UserProfile(
+ val id: Int,
+ val name: String,
+ val credential: Credential,
+ @DrawableRes val profileImage: Int,
+ val rank: Int = -1,
+ val wins: Int = 0,
+ val losses: Int = 0,
+ val pts: Int = 0
+)
diff --git a/Demo/app/src/main/java/com/krunal/demo/navigation/data/repositories/MatchRepository.kt b/Demo/app/src/main/java/com/krunal/demo/navigation/data/repositories/MatchRepository.kt
new file mode 100644
index 0000000..b7166d5
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/navigation/data/repositories/MatchRepository.kt
@@ -0,0 +1,27 @@
+package com.krunal.demo.navigation.data.repositories
+
+import com.krunal.demo.navigation.data.models.Match
+
+object MatchRepository {
+
+ private val matches = mutableMapOf(
+ 1 to Match(
+ 0,
+ UserProfileRepository.getAllUserProfiles()[0],
+ UserProfileRepository.getAllUserProfiles()[1]
+ ),
+ 2 to Match(
+ 0,
+ UserProfileRepository.getAllUserProfiles()[2],
+ UserProfileRepository.getAllUserProfiles()[3]
+ )
+ )
+
+ fun getMatch(matchId: Int): Match? = matches[matchId]
+
+ fun getAllMatches(): List = matches.toSortedMap().values.toList()
+
+ fun createMatch(match: Match) {
+ matches[match.matchId] = match
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/navigation/data/repositories/UserProfileRepository.kt b/Demo/app/src/main/java/com/krunal/demo/navigation/data/repositories/UserProfileRepository.kt
new file mode 100644
index 0000000..1ad1594
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/navigation/data/repositories/UserProfileRepository.kt
@@ -0,0 +1,68 @@
+package com.krunal.demo.navigation.data.repositories
+
+import com.krunal.demo.R
+import com.krunal.demo.helpers.PreferenceHelper
+import com.krunal.demo.navigation.data.models.Credential
+import com.krunal.demo.navigation.data.models.UserProfile
+import com.krunal.demo.utils.PreferenceKeys
+
+object UserProfileRepository {
+
+ private val userProfiles = mutableMapOf(
+ 1 to UserProfile(
+ id = 1,
+ name = "Krunal Patel",
+ credential = Credential("krunal@gmail.com", "Krunal#321"),
+ profileImage = R.drawable.avatar_1_raster,
+ rank = 1,
+ wins = 10,
+ losses = 5
+ ), 2 to UserProfile(
+ id = 2,
+ name = "Harsh Mehta",
+ credential = Credential("harsh@gmail.com", "Harsh#321"),
+ profileImage = R.drawable.avatar_3_raster,
+ rank = 2,
+ wins = 12,
+ losses = 8
+ ), 3 to UserProfile(
+ id = 3,
+ name = "Ankur Gamit",
+ credential = Credential("ankur@gmail.com", "Ankur@777"),
+ profileImage = R.drawable.avatar_2_raster,
+ rank = 3,
+ wins = 4,
+ losses = 2
+ ), 4 to UserProfile(
+ id = 4,
+ name = "Harshit Patel",
+ credential = Credential("harshit@gmail.com", "Harshit@777"),
+ profileImage = R.drawable.avatar_4_raster,
+ rank = 4,
+ wins = 0,
+ losses = 2
+ )
+ )
+
+ fun getAllUserProfiles(): List = userProfiles.toSortedMap().values.toList()
+
+ fun getCurrentUser(): UserProfile? {
+ val userId = PreferenceHelper.getInt(PreferenceKeys.TRIVIA_USER_ID, -1)
+ if (userId == -1) return null
+
+ return userProfiles[userId]
+ }
+
+ fun getUserProfile(userId: Int): UserProfile? = userProfiles[userId]
+
+ fun setCurrentUser(userProfile: UserProfile) {
+ userProfiles[userProfile.id] = userProfile
+ PreferenceHelper.putInt(PreferenceKeys.TRIVIA_USER_ID, userProfile.id)
+ }
+
+ fun getRandomUser(): UserProfile? = userProfiles.values.randomOrNull()
+
+ fun updateUser(userProfile: UserProfile) {
+ userProfiles[userProfile.id] = userProfile
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/navigation/ui/activities/TriviaGameActivity.kt b/Demo/app/src/main/java/com/krunal/demo/navigation/ui/activities/TriviaGameActivity.kt
new file mode 100644
index 0000000..e63098d
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/navigation/ui/activities/TriviaGameActivity.kt
@@ -0,0 +1,67 @@
+package com.krunal.demo.navigation.ui.activities
+
+import android.os.Bundle
+import android.util.Log
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.navigation.NavController
+import androidx.navigation.findNavController
+import androidx.navigation.fragment.NavHostFragment
+import androidx.navigation.ui.setupWithNavController
+import com.krunal.demo.R
+import com.krunal.demo.databinding.ActivityTriviaGameBinding
+import com.krunal.demo.navigation.ui.viewmodels.TriviaGameViewModel
+import com.krunal.demo.helpers.ThemeHelper
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+class TriviaGameActivity : AppCompatActivity() {
+
+ private lateinit var navController: NavController
+ private lateinit var binding: ActivityTriviaGameBinding
+ private val viewModel: TriviaGameViewModel by viewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityTriviaGameBinding.inflate(layoutInflater)
+ binding.lifecycleOwner = this
+ binding.viewModel = viewModel
+ installSplashScreen()
+ setupTheme()
+ setContentView(binding.root)
+ setupUI()
+ }
+
+ private fun setupTheme() {
+ setTheme(ThemeHelper.getThemeResource(ThemeHelper.getThemeAccent()))
+ }
+
+ private fun setupUI() {
+ setSupportActionBar(binding.toolbar)
+ (supportFragmentManager.findFragmentById(R.id.navHost) as? NavHostFragment)?.let {
+ navController = it.navController
+ }
+ setupNavigation()
+ }
+
+ private fun setupNavigation() {
+ binding.toolbar.setupWithNavController(navController)
+ binding.bottomNav.setupWithNavController(navController)
+
+ binding.toolbar.setNavigationOnClickListener {
+ onBackPressedDispatcher.onBackPressed()
+ }
+
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ navController.currentBackStackEntryFlow.collectLatest {
+ Log.d("Navigation", "current: ${navController.currentBackStackEntry?.destination?.displayName}")
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/navigation/ui/adapters/LeaderboardAdapter.kt b/Demo/app/src/main/java/com/krunal/demo/navigation/ui/adapters/LeaderboardAdapter.kt
new file mode 100644
index 0000000..7f627ad
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/navigation/ui/adapters/LeaderboardAdapter.kt
@@ -0,0 +1,44 @@
+package com.krunal.demo.navigation.ui.adapters
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import com.krunal.demo.databinding.ItemLeaderboardUserBinding
+import com.krunal.demo.navigation.data.models.UserProfile
+
+class LeaderboardAdapter(private val onClick: (userId: Int) -> Unit) :
+ RecyclerView.Adapter() {
+
+ private val userProfiles: MutableList = mutableListOf()
+
+ class UserProfileViewHolder(
+ private val binding: ItemLeaderboardUserBinding,
+ private val onClick: (userId: Int) -> Unit
+ ) : RecyclerView.ViewHolder(binding.root) {
+
+ fun bind(userProfile: UserProfile) {
+ binding.userProfile = userProfile
+ binding.root.setOnClickListener {
+ onClick(userProfile.id)
+ }
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserProfileViewHolder {
+ val layoutInflater = LayoutInflater.from(parent.context)
+ val binding = ItemLeaderboardUserBinding.inflate(layoutInflater, parent, false)
+ return UserProfileViewHolder(binding, onClick)
+ }
+
+ override fun getItemCount(): Int = userProfiles.count()
+
+ override fun onBindViewHolder(holder: UserProfileViewHolder, position: Int) {
+ holder.bind(userProfiles[position])
+ }
+
+ fun submitList(list: List) {
+ userProfiles.clear()
+ userProfiles.addAll(list)
+ notifyDataSetChanged()
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/navigation/ui/fragments/GameOverFragment.kt b/Demo/app/src/main/java/com/krunal/demo/navigation/ui/fragments/GameOverFragment.kt
new file mode 100644
index 0000000..8507d63
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/navigation/ui/fragments/GameOverFragment.kt
@@ -0,0 +1,35 @@
+package com.krunal.demo.navigation.ui.fragments
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.navigation.fragment.findNavController
+import com.krunal.demo.databinding.FragmentGameOverBinding
+
+class GameOverFragment : Fragment() {
+
+ private lateinit var binding: FragmentGameOverBinding
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
+ ): View {
+ binding = FragmentGameOverBinding.inflate(layoutInflater, container, false)
+
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ setupUI()
+ }
+
+ private fun setupUI() {
+ binding.apply {
+ btnPlayAgain.setOnClickListener {
+ findNavController().navigate(GameOverFragmentDirections.actionGameOverFragmentToMatchFragment())
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/navigation/ui/fragments/InGameFragment.kt b/Demo/app/src/main/java/com/krunal/demo/navigation/ui/fragments/InGameFragment.kt
new file mode 100644
index 0000000..d437297
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/navigation/ui/fragments/InGameFragment.kt
@@ -0,0 +1,60 @@
+package com.krunal.demo.navigation.ui.fragments
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.activity.addCallback
+import androidx.appcompat.widget.Toolbar
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.fragment.findNavController
+import androidx.navigation.fragment.navArgs
+import com.google.android.material.appbar.MaterialToolbar
+import com.krunal.demo.R
+import com.krunal.demo.databinding.FragmentInGameBinding
+import com.krunal.demo.navigation.ui.viewmodels.InGameViewModel
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+class InGameFragment : Fragment() {
+
+ private lateinit var binding: FragmentInGameBinding
+ private val viewModel: InGameViewModel by viewModels()
+ private val args: InGameFragmentArgs by navArgs()
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
+ ): View {
+ binding = FragmentInGameBinding.inflate(layoutInflater, container, false)
+ binding.lifecycleOwner = viewLifecycleOwner
+ binding.viewModel = viewModel
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ setupUI()
+ }
+
+ private fun setupUI() {
+ viewModel.loadMatch(args.matchId)
+
+ binding.apply {
+ btnWin.setOnClickListener {
+ findNavController().navigate(InGameFragmentDirections.actionInGameFragmentToResultWinnerFragment())
+ viewModel?.win()
+ }
+
+ btnLoss.setOnClickListener {
+ findNavController().navigate(InGameFragmentDirections.actionInGameFragmentToGameOverFragment())
+ viewModel?.loss()
+ }
+ }
+
+ requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
+ findNavController().popBackStack(R.id.titleScreenFragment, false)
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/navigation/ui/fragments/LeaderboardFragment.kt b/Demo/app/src/main/java/com/krunal/demo/navigation/ui/fragments/LeaderboardFragment.kt
new file mode 100644
index 0000000..8b248b8
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/navigation/ui/fragments/LeaderboardFragment.kt
@@ -0,0 +1,58 @@
+package com.krunal.demo.navigation.ui.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.lifecycleScope
+import androidx.navigation.fragment.findNavController
+import androidx.recyclerview.widget.DividerItemDecoration
+import com.krunal.demo.databinding.FragmentLeaderboardBinding
+import com.krunal.demo.navigation.ui.adapters.LeaderboardAdapter
+import com.krunal.demo.navigation.ui.viewmodels.LeaderboardViewModel
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+class LeaderboardFragment : Fragment() {
+
+ private lateinit var binding: FragmentLeaderboardBinding
+ private val viewModel: LeaderboardViewModel by viewModels()
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
+ ): View {
+ binding = FragmentLeaderboardBinding.inflate(layoutInflater, container, false)
+ binding.lifecycleOwner = viewLifecycleOwner
+ binding.viewModel = viewModel
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ setupUI()
+ }
+
+ private fun setupUI() {
+ val adapter = LeaderboardAdapter { id ->
+ findNavController().navigate(LeaderboardFragmentDirections.actionLeaderboardFragmentToUserProfileFragment(id))
+ }
+
+ binding.rvLeaderboard.apply {
+ this.adapter = adapter
+ addItemDecoration(
+ DividerItemDecoration(
+ requireContext(),
+ DividerItemDecoration.VERTICAL
+ )
+ )
+ }
+
+ lifecycleScope.launch {
+ viewModel.userProfiles.collectLatest { list ->
+ adapter.submitList(list)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/navigation/ui/fragments/MatchFragment.kt b/Demo/app/src/main/java/com/krunal/demo/navigation/ui/fragments/MatchFragment.kt
new file mode 100644
index 0000000..6dc7480
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/navigation/ui/fragments/MatchFragment.kt
@@ -0,0 +1,52 @@
+package com.krunal.demo.navigation.ui.fragments
+
+import android.graphics.ColorFilter
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.activity.addCallback
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import androidx.navigation.fragment.findNavController
+import androidx.navigation.fragment.navArgs
+import androidx.navigation.navOptions
+import com.krunal.demo.R
+import com.krunal.demo.databinding.FragmentMatchBinding
+import com.krunal.demo.navigation.ui.viewmodels.MatchViewModel
+
+class MatchFragment : Fragment() {
+
+ private lateinit var binding: FragmentMatchBinding
+ private val viewModel: MatchViewModel by viewModels()
+ private val args: MatchFragmentArgs by navArgs()
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
+ ): View {
+ binding = FragmentMatchBinding.inflate(layoutInflater, container, false)
+ binding.lifecycleOwner = viewLifecycleOwner
+ binding.viewModel = viewModel
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ setupUI()
+ }
+
+ private fun setupUI() {
+ viewModel.setUser(args.userId)
+ viewModel.createMatch()
+
+ binding.btnStart.setOnClickListener {
+ viewModel.match.value?.let { match ->
+ findNavController().navigate(MatchFragmentDirections.actionMatchFragmentToInGameFragment(match.matchId))
+ }
+ }
+
+ requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
+ findNavController().popBackStack(R.id.titleScreenFragment, false)
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/navigation/ui/fragments/RegisterFragment.kt b/Demo/app/src/main/java/com/krunal/demo/navigation/ui/fragments/RegisterFragment.kt
new file mode 100644
index 0000000..bfac004
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/navigation/ui/fragments/RegisterFragment.kt
@@ -0,0 +1,47 @@
+package com.krunal.demo.navigation.ui.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.navigation.fragment.findNavController
+import com.krunal.demo.databinding.FragmentRegisterBinding
+import com.krunal.demo.navigation.ui.viewmodels.RegisterViewModel
+
+class RegisterFragment : Fragment() {
+
+ private lateinit var binding: FragmentRegisterBinding
+ private val viewModel: RegisterViewModel by viewModels()
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
+ ): View {
+ binding = FragmentRegisterBinding.inflate(layoutInflater, container, false)
+ binding.lifecycleOwner = viewLifecycleOwner
+ binding.viewModel = viewModel
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ setupUI()
+ }
+
+ private fun setupUI() {
+ binding.btnSave.setOnClickListener {
+ viewModel.saveUser()
+ }
+
+ binding.btnStart.setOnClickListener {
+ viewModel.userProfile.value?.id?.let { userId ->
+ findNavController().navigate(
+ RegisterFragmentDirections.actionRegisterFragmentToMatchFragment(
+ userId
+ )
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/navigation/ui/fragments/ResultWinnerFragment.kt b/Demo/app/src/main/java/com/krunal/demo/navigation/ui/fragments/ResultWinnerFragment.kt
new file mode 100644
index 0000000..76b31c4
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/navigation/ui/fragments/ResultWinnerFragment.kt
@@ -0,0 +1,38 @@
+package com.krunal.demo.navigation.ui.fragments
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.navigation.fragment.findNavController
+import com.krunal.demo.databinding.FragmentResultWinnerBinding
+
+class ResultWinnerFragment : Fragment() {
+
+ private lateinit var binding: FragmentResultWinnerBinding
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
+ ): View {
+ binding = FragmentResultWinnerBinding.inflate(layoutInflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ setupUI()
+ }
+
+ private fun setupUI() {
+ binding.apply {
+ btnLeaderboard.setOnClickListener {
+ findNavController().navigate(ResultWinnerFragmentDirections.actionResultWinnerFragmentToLeaderboardFragment())
+ }
+
+ btnPlay.setOnClickListener {
+ findNavController().navigate(ResultWinnerFragmentDirections.actionResultWinnerFragmentToMatchFragment())
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/navigation/ui/fragments/TitleScreenFragment.kt b/Demo/app/src/main/java/com/krunal/demo/navigation/ui/fragments/TitleScreenFragment.kt
new file mode 100644
index 0000000..510d223
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/navigation/ui/fragments/TitleScreenFragment.kt
@@ -0,0 +1,55 @@
+package com.krunal.demo.navigation.ui.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.navigation.fragment.findNavController
+import com.krunal.demo.R
+import com.krunal.demo.databinding.FragmentTitleScreenBinding
+import com.krunal.demo.navigation.ui.viewmodels.TitleScreenViewModel
+
+class TitleScreenFragment : Fragment(R.layout.fragment_title_screen) {
+
+ private lateinit var binding: FragmentTitleScreenBinding
+ private val viewModel: TitleScreenViewModel by viewModels()
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
+ ): View {
+ binding = FragmentTitleScreenBinding.inflate(layoutInflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ setupUI()
+ }
+
+ private fun setupUI() {
+ binding.apply {
+ btnLeaderboard.setOnClickListener {
+ findNavController().navigate(TitleScreenFragmentDirections.actionTitleScreenFragmentToLeaderboardFragment())
+ }
+
+ btnPlay.setOnClickListener {
+ val userProfile = viewModel.userProfile.value
+ findNavController().navigate(
+ if (userProfile == null) {
+ TitleScreenFragmentDirections.actionTitleScreenFragmentToRegisterFragment()
+ } else {
+ TitleScreenFragmentDirections.actionTitleScreenFragmentToMatchFragment(
+ userProfile.id
+ )
+ }
+ )
+ }
+
+ btnAbout.setOnClickListener {
+ // TODO: Go to about
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/navigation/ui/fragments/UserProfileFragment.kt b/Demo/app/src/main/java/com/krunal/demo/navigation/ui/fragments/UserProfileFragment.kt
new file mode 100644
index 0000000..6d36107
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/navigation/ui/fragments/UserProfileFragment.kt
@@ -0,0 +1,36 @@
+package com.krunal.demo.navigation.ui.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.navigation.fragment.navArgs
+import com.krunal.demo.databinding.FragmentUserProfileBinding
+import com.krunal.demo.navigation.ui.viewmodels.UserProfileViewModel
+
+class UserProfileFragment : Fragment() {
+
+ private lateinit var binding: FragmentUserProfileBinding
+ private val viewModel: UserProfileViewModel by viewModels()
+ private val args: UserProfileFragmentArgs by navArgs()
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
+ ): View {
+ binding = FragmentUserProfileBinding.inflate(layoutInflater, container, false)
+ binding.lifecycleOwner = viewLifecycleOwner
+ binding.viewModel = viewModel
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ setupUI()
+ }
+
+ private fun setupUI() {
+ viewModel.setUser(args.userId)
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/navigation/ui/viewmodels/InGameViewModel.kt b/Demo/app/src/main/java/com/krunal/demo/navigation/ui/viewmodels/InGameViewModel.kt
new file mode 100644
index 0000000..e3e2a4d
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/navigation/ui/viewmodels/InGameViewModel.kt
@@ -0,0 +1,50 @@
+package com.krunal.demo.navigation.ui.viewmodels
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.krunal.demo.navigation.data.models.Match
+import com.krunal.demo.navigation.data.repositories.MatchRepository
+import com.krunal.demo.navigation.data.repositories.UserProfileRepository
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+
+class InGameViewModel : ViewModel() {
+
+ private val _match: MutableStateFlow = MutableStateFlow(null)
+ val match: StateFlow = _match
+
+ fun loadMatch(matchId: Int) {
+ viewModelScope.launch {
+ _match.emit(MatchRepository.getMatch(matchId))
+ }
+ }
+
+ fun win() {
+ viewModelScope.launch {
+ match.value?.let { match ->
+ val userProfile = match.user1.copy(
+ wins = match.user1.wins + 1
+ )
+ UserProfileRepository.updateUser(userProfile)
+ _match.emit(
+ match.copy(user1 = userProfile, winner = match.user1)
+ )
+ }
+ }
+ }
+
+ fun loss() {
+ viewModelScope.launch {
+ match.value?.let { match ->
+ val userProfile = match.user2.copy(
+ wins = match.user2.wins + 1
+ )
+ UserProfileRepository.updateUser(userProfile)
+ _match.emit(
+ match.copy(user2 = userProfile, winner = match.user2)
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/navigation/ui/viewmodels/LeaderboardViewModel.kt b/Demo/app/src/main/java/com/krunal/demo/navigation/ui/viewmodels/LeaderboardViewModel.kt
new file mode 100644
index 0000000..59dc9bd
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/navigation/ui/viewmodels/LeaderboardViewModel.kt
@@ -0,0 +1,25 @@
+package com.krunal.demo.navigation.ui.viewmodels
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.krunal.demo.navigation.data.models.UserProfile
+import com.krunal.demo.navigation.data.repositories.UserProfileRepository
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+
+class LeaderboardViewModel : ViewModel() {
+
+ private val _userProfiles: MutableStateFlow> = MutableStateFlow(emptyList())
+ val userProfiles: StateFlow> = _userProfiles
+
+ init {
+ setupInitialData()
+ }
+
+ private fun setupInitialData() {
+ viewModelScope.launch {
+ _userProfiles.emit(UserProfileRepository.getAllUserProfiles().sortedBy { it.rank })
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/navigation/ui/viewmodels/MatchViewModel.kt b/Demo/app/src/main/java/com/krunal/demo/navigation/ui/viewmodels/MatchViewModel.kt
new file mode 100644
index 0000000..7d4c870
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/navigation/ui/viewmodels/MatchViewModel.kt
@@ -0,0 +1,59 @@
+package com.krunal.demo.navigation.ui.viewmodels
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.krunal.demo.navigation.data.models.Match
+import com.krunal.demo.navigation.data.models.UserProfile
+import com.krunal.demo.navigation.data.repositories.MatchRepository
+import com.krunal.demo.navigation.data.repositories.UserProfileRepository
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+
+class MatchViewModel : ViewModel() {
+
+ private val _userProfile1: MutableStateFlow = MutableStateFlow(null)
+ val userProfile1: StateFlow = _userProfile1
+
+ private val _userProfile2: MutableStateFlow = MutableStateFlow(null)
+ val userProfile2: StateFlow = _userProfile2
+
+ private val _match: MutableStateFlow = MutableStateFlow(null)
+ val match: StateFlow = _match
+
+ fun setUser(userId: Int) {
+ viewModelScope.launch {
+ val user = if (userId == -1) {
+ UserProfileRepository.getCurrentUser()
+ } else {
+ UserProfileRepository.getUserProfile(userId)
+ }
+ _userProfile1.emit(user)
+ }
+ }
+
+ fun createMatch() {
+
+ viewModelScope.launch {
+ delay(1200)
+
+ // Insufficient players
+ if (UserProfileRepository.getAllUserProfiles().count() <= 1) return@launch
+
+ val user1 = userProfile1.value ?: return@launch
+ var user2: UserProfile
+ do {
+ user2 = UserProfileRepository.getRandomUser() ?: return@launch
+ } while (user2.id == user1.id)
+
+ _userProfile2.emit(user2)
+
+ val id = MatchRepository.getAllMatches().count()
+ val match = Match(id, user1, user2)
+
+ MatchRepository.createMatch(match)
+ _match.emit(match)
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/navigation/ui/viewmodels/RegisterViewModel.kt b/Demo/app/src/main/java/com/krunal/demo/navigation/ui/viewmodels/RegisterViewModel.kt
new file mode 100644
index 0000000..3f2f384
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/navigation/ui/viewmodels/RegisterViewModel.kt
@@ -0,0 +1,50 @@
+package com.krunal.demo.navigation.ui.viewmodels
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.krunal.demo.R
+import com.krunal.demo.navigation.data.models.Credential
+import com.krunal.demo.navigation.data.models.RegisterDetail
+import com.krunal.demo.navigation.data.models.UserProfile
+import com.krunal.demo.navigation.data.repositories.UserProfileRepository
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+
+class RegisterViewModel : ViewModel() {
+
+ val registerDetail: MutableStateFlow =
+ MutableStateFlow(RegisterDetail())
+
+ private val _userProfile: MutableStateFlow = MutableStateFlow(null)
+ val userProfile: StateFlow = _userProfile
+
+ init {
+ setupInitialData()
+ }
+
+ private fun setupInitialData() {
+ viewModelScope.launch {
+ val userDetail = UserProfileRepository.getCurrentUser() ?: return@launch
+
+ registerDetail.emit(RegisterDetail(userDetail.name, userDetail.credential.email, userDetail.credential.password))
+ _userProfile.emit(userDetail)
+ }
+ }
+
+ fun saveUser() {
+ viewModelScope.launch {
+ registerDetail.value.let { details ->
+ val userProfile = UserProfile(
+ 1,
+ details.username,
+ Credential(details.email, details.password),
+ R.drawable.avatar_5_raster
+ )
+
+ UserProfileRepository.setCurrentUser(userProfile)
+ _userProfile.emit(userProfile)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/navigation/ui/viewmodels/TitleScreenViewModel.kt b/Demo/app/src/main/java/com/krunal/demo/navigation/ui/viewmodels/TitleScreenViewModel.kt
new file mode 100644
index 0000000..84b4790
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/navigation/ui/viewmodels/TitleScreenViewModel.kt
@@ -0,0 +1,25 @@
+package com.krunal.demo.navigation.ui.viewmodels
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.krunal.demo.navigation.data.models.UserProfile
+import com.krunal.demo.navigation.data.repositories.UserProfileRepository
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+
+class TitleScreenViewModel : ViewModel() {
+
+ private val _userProfile: MutableStateFlow = MutableStateFlow(null)
+ val userProfile: StateFlow = _userProfile
+
+ init {
+ setupInitialData()
+ }
+
+ private fun setupInitialData() {
+ viewModelScope.launch {
+ _userProfile.emit(UserProfileRepository.getCurrentUser())
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/navigation/ui/viewmodels/TriviaGameViewModel.kt b/Demo/app/src/main/java/com/krunal/demo/navigation/ui/viewmodels/TriviaGameViewModel.kt
new file mode 100644
index 0000000..a81d945
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/navigation/ui/viewmodels/TriviaGameViewModel.kt
@@ -0,0 +1,25 @@
+package com.krunal.demo.navigation.ui.viewmodels
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.krunal.demo.navigation.data.models.UserProfile
+import com.krunal.demo.navigation.data.repositories.UserProfileRepository
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+
+class TriviaGameViewModel : ViewModel() {
+
+ private val _userProfiles: MutableStateFlow> = MutableStateFlow(emptyList())
+ val userProfile: StateFlow> = _userProfiles
+
+ init {
+ setupInitialData()
+ }
+
+ private fun setupInitialData() {
+ viewModelScope.launch {
+ _userProfiles.emit(UserProfileRepository.getAllUserProfiles())
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/navigation/ui/viewmodels/UserProfileViewModel.kt b/Demo/app/src/main/java/com/krunal/demo/navigation/ui/viewmodels/UserProfileViewModel.kt
new file mode 100644
index 0000000..ce9af0b
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/navigation/ui/viewmodels/UserProfileViewModel.kt
@@ -0,0 +1,22 @@
+package com.krunal.demo.navigation.ui.viewmodels
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.krunal.demo.navigation.data.models.UserProfile
+import com.krunal.demo.navigation.data.repositories.UserProfileRepository
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+
+class UserProfileViewModel : ViewModel() {
+
+ private val _userProfile: MutableStateFlow = MutableStateFlow(null)
+ val userProfile: StateFlow = _userProfile
+
+ fun setUser(userId: Int) {
+ viewModelScope.launch {
+ _userProfile.emit(UserProfileRepository.getAllUserProfiles()
+ .firstOrNull { it.id == userId })
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/searchwebview/data/models/PackageDetail.kt b/Demo/app/src/main/java/com/krunal/demo/searchwebview/data/models/PackageDetail.kt
new file mode 100644
index 0000000..c5278ef
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/searchwebview/data/models/PackageDetail.kt
@@ -0,0 +1,9 @@
+package com.krunal.demo.searchwebview.data.models
+
+import android.graphics.drawable.Drawable
+
+data class PackageDetail(
+ val name: String,
+ val packageName: String,
+ val icon: Drawable
+)
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/searchwebview/data/repositories/PackagesRepository.kt b/Demo/app/src/main/java/com/krunal/demo/searchwebview/data/repositories/PackagesRepository.kt
new file mode 100644
index 0000000..553b8ac
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/searchwebview/data/repositories/PackagesRepository.kt
@@ -0,0 +1,11 @@
+package com.krunal.demo.searchwebview.data.repositories
+
+import com.krunal.demo.searchwebview.data.models.PackageDetail
+import com.krunal.demo.searchwebview.helpers.PackageHelper
+
+object PackagesRepository {
+
+ fun getPackageDetails(): List = PackageHelper.getPackageDetails()
+
+ fun getPackageNames(): List = PackageHelper.getPackageDetails().map { it.name }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/searchwebview/data/repositories/WebViewRepository.kt b/Demo/app/src/main/java/com/krunal/demo/searchwebview/data/repositories/WebViewRepository.kt
new file mode 100644
index 0000000..01ade6b
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/searchwebview/data/repositories/WebViewRepository.kt
@@ -0,0 +1,23 @@
+package com.krunal.demo.searchwebview.data.repositories
+
+object WebViewRepository {
+
+ private val urls = listOf(
+ "https://krunalpatel.me",
+ "https://blog.krunalpatel.me",
+ "https://instagram.com",
+ "https://facebook.com",
+ "https://m.facebook.com",
+ "https://youtube.com",
+ "https://shodan.io"
+ )
+
+ fun getBlockedUrls(): List = listOf(
+ "facebook.com",
+ "m.facebook.com",
+ "youtube.com",
+ "m.youtube.com"
+ )
+
+ fun getRandomUrl(): String = urls.random()
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/searchwebview/helpers/PackageHelper.kt b/Demo/app/src/main/java/com/krunal/demo/searchwebview/helpers/PackageHelper.kt
new file mode 100644
index 0000000..73e894c
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/searchwebview/helpers/PackageHelper.kt
@@ -0,0 +1,38 @@
+package com.krunal.demo.searchwebview.helpers
+
+import android.content.Context
+import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
+import android.os.Build
+import com.krunal.demo.searchwebview.data.models.PackageDetail
+
+object PackageHelper {
+
+ private lateinit var packageManager: PackageManager
+
+ private val packages: List by lazy {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ packageManager.getInstalledPackages(PackageManager.PackageInfoFlags.of(PackageManager.GET_META_DATA.toLong()))
+ } else {
+ @Suppress("DEPRECATION") packageManager.getInstalledPackages(PackageManager.GET_META_DATA)
+ }
+ }
+
+ /**
+ * set the context that is being used to access the shared preferences
+ */
+ fun initialize(context: Context) {
+
+ packageManager = context.packageManager
+ }
+
+ fun getPackageDetails(): List = packages.map {
+ PackageDetail(
+ name = it.applicationInfo.loadLabel(
+ packageManager
+ ).toString(),
+ packageName = it.packageName,
+ icon = it.applicationInfo.loadIcon(packageManager)
+ )
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/searchwebview/helpers/SearchCountDownTimer.kt b/Demo/app/src/main/java/com/krunal/demo/searchwebview/helpers/SearchCountDownTimer.kt
new file mode 100644
index 0000000..443bcd1
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/searchwebview/helpers/SearchCountDownTimer.kt
@@ -0,0 +1,12 @@
+package com.krunal.demo.searchwebview.helpers
+
+import android.os.CountDownTimer
+
+class SearchCountDownTimer(delay: Long, val onComplete: () -> Unit): CountDownTimer(delay, delay) {
+ override fun onTick(millisUntilFinished: Long) {
+ }
+
+ override fun onFinish() {
+ onComplete()
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/searchwebview/helpers/WebClient.kt b/Demo/app/src/main/java/com/krunal/demo/searchwebview/helpers/WebClient.kt
new file mode 100644
index 0000000..65555a0
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/searchwebview/helpers/WebClient.kt
@@ -0,0 +1,37 @@
+package com.krunal.demo.searchwebview.helpers
+
+import android.content.Intent
+import android.net.Uri
+import android.webkit.WebResourceRequest
+import android.webkit.WebView
+import android.webkit.WebViewClient
+import androidx.core.net.toUri
+import com.krunal.demo.R
+import com.krunal.demo.searchwebview.data.repositories.WebViewRepository
+
+class WebClient : WebViewClient() {
+
+ override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
+ if (view == null || request == null) return true
+
+ if ("${request.url.scheme}:" in listOf(WebView.SCHEME_MAILTO, WebView.SCHEME_TEL)) {
+ Intent(Intent.ACTION_VIEW, request.url.normalizeScheme()).also { intent ->
+ view.context.startActivity(intent)
+ }
+ return true
+ }
+
+ val blocked = request.url?.host in WebViewRepository.getBlockedUrls()
+ if (blocked) {
+ val unauthorised =
+ view.resources.openRawResource(R.raw.unauthorised).readBytes()?.decodeToString()
+ ?: ""
+
+ // https://issuetracker.google.com/issues/36915138
+ // view?.loadData(unauthorised, "text/html", "charset=UTF-8")
+
+ view.loadDataWithBaseURL(null, unauthorised, "text/html", "charset=UTF-8", null)
+ }
+ return blocked
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/searchwebview/ui/activities/SearchWebActivity.kt b/Demo/app/src/main/java/com/krunal/demo/searchwebview/ui/activities/SearchWebActivity.kt
new file mode 100644
index 0000000..889fa00
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/searchwebview/ui/activities/SearchWebActivity.kt
@@ -0,0 +1,81 @@
+package com.krunal.demo.searchwebview.ui.activities
+
+import android.os.Bundle
+import android.util.Log
+import android.view.KeyEvent
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.navigation.NavController
+import androidx.navigation.fragment.NavHostFragment
+import androidx.navigation.ui.setupWithNavController
+import com.krunal.demo.R
+import com.krunal.demo.databinding.ActivitySearchWebBinding
+import com.krunal.demo.helpers.ThemeHelper
+import com.krunal.demo.searchwebview.ui.fragments.WebViewFragment
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+class SearchWebActivity : AppCompatActivity() {
+
+ private lateinit var navController: NavController
+ private lateinit var binding: ActivitySearchWebBinding
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivitySearchWebBinding.inflate(layoutInflater)
+ installSplashScreen()
+ setupTheme()
+ setContentView(binding.root)
+ setupUI()
+ }
+
+ private fun setupTheme() {
+ setTheme(ThemeHelper.getThemeResource(ThemeHelper.getThemeAccent()))
+ }
+
+ private fun setupUI() {
+ setSupportActionBar(binding.toolbar)
+ (supportFragmentManager.findFragmentById(R.id.navHost) as? NavHostFragment)?.let {
+ navController = it.navController
+ }
+ setupNavigation()
+ }
+
+ private fun setupNavigation() {
+ binding.toolbar.setupWithNavController(navController)
+ binding.bottomNav.setupWithNavController(navController)
+
+ binding.toolbar.setNavigationOnClickListener {
+ onBackPressedDispatcher.onBackPressed()
+ }
+
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ navController.currentBackStackEntryFlow.collectLatest {
+ Log.d(
+ "Navigation",
+ "current: ${navController.currentBackStackEntry?.destination?.displayName}"
+ )
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Forward [KeyEvent.KEYCODE_BACK] event to [WebViewFragment]
+ */
+ override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ supportFragmentManager.fragments.firstOrNull()?.childFragmentManager?.let { navHostFragment ->
+ (navHostFragment.fragments.firstOrNull() as? WebViewFragment)?.let { webViewFragment ->
+ if (webViewFragment.onBackPressed()) return true
+ }
+ }
+ }
+ return super.onKeyDown(keyCode, event)
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/searchwebview/ui/adapters/PackageDetailAdapter.kt b/Demo/app/src/main/java/com/krunal/demo/searchwebview/ui/adapters/PackageDetailAdapter.kt
new file mode 100644
index 0000000..b608c42
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/searchwebview/ui/adapters/PackageDetailAdapter.kt
@@ -0,0 +1,73 @@
+package com.krunal.demo.searchwebview.ui.adapters
+
+import android.annotation.SuppressLint
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import android.widget.Filter
+import android.widget.Filterable
+import androidx.recyclerview.widget.RecyclerView
+import com.krunal.demo.databinding.ItemPackageDetailBinding
+import com.krunal.demo.searchwebview.data.models.PackageDetail
+
+class PackageDetailAdapter(private val onClick: (packageDetail: PackageDetail) -> Unit) :
+ RecyclerView.Adapter(), Filterable {
+
+ private val filteredPackageDetails: MutableList = mutableListOf()
+ private val originalPackageDetails: MutableList = mutableListOf()
+
+ class PackageDetailsViewHolder(
+ private val binding: ItemPackageDetailBinding, private val onClick: (packageDetail: PackageDetail) -> Unit
+ ) : RecyclerView.ViewHolder(binding.root) {
+
+ fun bind(packageDetail: PackageDetail) {
+ binding.packageDetail = packageDetail
+ binding.root.setOnClickListener {
+ onClick(packageDetail)
+ }
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PackageDetailsViewHolder {
+ val layoutInflater = LayoutInflater.from(parent.context)
+ val binding = ItemPackageDetailBinding.inflate(layoutInflater, parent, false)
+ return PackageDetailsViewHolder(binding, onClick)
+ }
+
+ override fun getItemCount(): Int = filteredPackageDetails.count()
+
+ override fun onBindViewHolder(holder: PackageDetailsViewHolder, position: Int) {
+ holder.bind(filteredPackageDetails[position])
+ }
+
+ @SuppressLint("NotifyDataSetChanged")
+ fun submitList(list: List) {
+ originalPackageDetails.clear()
+ originalPackageDetails.addAll(list)
+ filteredPackageDetails.clear()
+ filteredPackageDetails.addAll(list)
+ notifyDataSetChanged()
+ }
+
+ override fun getFilter(): Filter = object : Filter() {
+ override fun performFiltering(constraint: CharSequence?): FilterResults {
+ val filterString = constraint.toString().lowercase()
+ val filteredList =
+ originalPackageDetails.filter { it.name.contains(filterString, true) }
+
+ return FilterResults().apply {
+ values = filteredList
+ count = filteredList.count()
+ }
+ }
+
+ @SuppressLint("NotifyDataSetChanged")
+ @Suppress("UNCHECKED_CAST")
+ override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
+ (results?.values as? List)?.let { filteredList ->
+ filteredPackageDetails.clear()
+ filteredPackageDetails.addAll(filteredList)
+ }
+ notifyDataSetChanged()
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/searchwebview/ui/fragments/SearchViewFragment.kt b/Demo/app/src/main/java/com/krunal/demo/searchwebview/ui/fragments/SearchViewFragment.kt
new file mode 100644
index 0000000..ff0445f
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/searchwebview/ui/fragments/SearchViewFragment.kt
@@ -0,0 +1,154 @@
+package com.krunal.demo.searchwebview.ui.fragments
+
+import android.app.SearchManager
+import android.database.MatrixCursor
+import android.os.Bundle
+import android.provider.BaseColumns
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Toast
+import androidx.appcompat.widget.SearchView
+import androidx.cursoradapter.widget.SimpleCursorAdapter
+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.R
+import com.krunal.demo.databinding.FragmentSearchViewBinding
+import com.krunal.demo.searchwebview.ui.adapters.PackageDetailAdapter
+import com.krunal.demo.searchwebview.ui.viewmodels.SearchViewModel
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+class SearchViewFragment : Fragment() {
+
+ private lateinit var binding: FragmentSearchViewBinding
+ private val viewModel: SearchViewModel by viewModels()
+ private lateinit var packageDetailAdapter: PackageDetailAdapter
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
+ ): View {
+ binding = FragmentSearchViewBinding.inflate(layoutInflater, container, false)
+ binding.lifecycleOwner = viewLifecycleOwner
+ binding.viewModel = viewModel
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ setupUI()
+ }
+
+ private fun setupUI() {
+ setupSearchView()
+ setupPackageDetails()
+
+ lifecycleScope.launch {
+ viewModel.query.collectLatest { query ->
+ filterPackageDetails(query)
+ }
+ }
+ }
+
+ private fun setupSearchView() {
+ val from = arrayOf(SearchManager.SUGGEST_COLUMN_TEXT_1)
+ val to = intArrayOf(R.id.searchItemID)
+ val cursorAdapter = SimpleCursorAdapter(
+ requireContext(),
+ R.layout.item_search_suggestion,
+ null,
+ from,
+ to,
+ SimpleCursorAdapter.NO_SELECTION
+ )
+
+ binding.searchView.apply {
+ suggestionsAdapter = cursorAdapter
+
+ setOnQueryTextListener(object : SearchView.OnQueryTextListener {
+ override fun onQueryTextSubmit(query: String?): Boolean {
+ query?.let(viewModel::searchQuery)
+ return false
+ }
+
+ override fun onQueryTextChange(newText: String?): Boolean {
+ val suggestions = viewModel.suggestions.value
+ val cursor =
+ MatrixCursor(arrayOf(BaseColumns._ID, SearchManager.SUGGEST_COLUMN_TEXT_1))
+ newText?.let { text ->
+ suggestions.filter { it.contains(text, true) }
+ .forEachIndexed { index, suggestion ->
+ cursor.addRow(arrayOf(index, suggestion))
+ }
+ }
+ suggestionsAdapter.changeCursor(cursor)
+ newText?.let(viewModel::searchQuery)
+ return true
+ }
+ })
+
+ setOnSuggestionListener(object : SearchView.OnSuggestionListener {
+ override fun onSuggestionSelect(position: Int): Boolean {
+ return false
+ }
+
+ override fun onSuggestionClick(position: Int): Boolean {
+ val query = suggestionsAdapter.cursor.getString(1)
+ setQuery(query, false)
+ viewModel.searchQuery(query)
+ return true
+ }
+ })
+
+ setOnCloseListener {
+ viewModel.searchQuery(query.toString())
+ true
+ }
+ }
+ }
+
+ private fun filterPackageDetails(query: String) {
+ packageDetailAdapter.filter.filter(query)
+ }
+
+ private fun setupPackageDetails() {
+ packageDetailAdapter = PackageDetailAdapter { packageDetails ->
+ val intent =
+ activity?.packageManager?.getLaunchIntentForPackage(packageDetails.packageName)
+
+ if (intent == null) {
+ Toast.makeText(
+ requireContext(),
+ "can't start ${packageDetails.name} \nno intent for launch",
+ Toast.LENGTH_SHORT
+ ).show()
+ return@PackageDetailAdapter
+ }
+
+ runCatching {
+ startActivity(intent)
+ }
+ }
+
+ binding.rvPackageDetail.apply {
+ this.adapter = packageDetailAdapter
+ addItemDecoration(
+ DividerItemDecoration(
+ requireContext(), DividerItemDecoration.VERTICAL
+ )
+ )
+ }
+
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ viewModel.packageDetails.collectLatest { list ->
+ packageDetailAdapter.submitList(list)
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/searchwebview/ui/fragments/WebViewFragment.kt b/Demo/app/src/main/java/com/krunal/demo/searchwebview/ui/fragments/WebViewFragment.kt
new file mode 100644
index 0000000..3f01b84
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/searchwebview/ui/fragments/WebViewFragment.kt
@@ -0,0 +1,68 @@
+package com.krunal.demo.searchwebview.ui.fragments
+
+import android.annotation.SuppressLint
+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.FragmentWebViewBinding
+import com.krunal.demo.searchwebview.helpers.WebClient
+import com.krunal.demo.searchwebview.ui.viewmodels.WebViewViewModel
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+class WebViewFragment : Fragment() {
+
+ private lateinit var binding: FragmentWebViewBinding
+ private val viewModel: WebViewViewModel by viewModels()
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
+ ): View {
+ binding = FragmentWebViewBinding.inflate(layoutInflater, container, false)
+ binding.lifecycleOwner = viewLifecycleOwner
+ binding.viewModel = viewModel
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ setupUI()
+ }
+
+ private fun setupUI() {
+ setupWebView()
+ }
+
+ @SuppressLint("SetJavaScriptEnabled")
+ private fun setupWebView() {
+ binding.webView.apply {
+ webViewClient = WebClient()
+ settings.javaScriptEnabled = true
+ }
+
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ viewModel.url.collectLatest { url ->
+ binding.webView.loadUrl(url)
+ }
+ }
+ }
+ }
+
+ fun onBackPressed(): Boolean {
+ binding.webView.apply {
+ return if (canGoBack()) {
+ goBack()
+ true
+ } else {
+ false
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/searchwebview/ui/viewmodels/SearchViewModel.kt b/Demo/app/src/main/java/com/krunal/demo/searchwebview/ui/viewmodels/SearchViewModel.kt
new file mode 100644
index 0000000..9293e23
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/searchwebview/ui/viewmodels/SearchViewModel.kt
@@ -0,0 +1,60 @@
+package com.krunal.demo.searchwebview.ui.viewmodels
+
+import android.os.CountDownTimer
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.krunal.demo.searchwebview.data.models.PackageDetail
+import com.krunal.demo.searchwebview.data.repositories.PackagesRepository
+import com.krunal.demo.searchwebview.helpers.SearchCountDownTimer
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+
+class SearchViewModel : ViewModel() {
+
+ private val _suggestions: MutableStateFlow> = MutableStateFlow(emptyList())
+ val suggestions: StateFlow> = _suggestions
+
+ private val _packageDetails: MutableStateFlow> = MutableStateFlow(emptyList())
+ val packageDetails: StateFlow> = _packageDetails
+
+ private val _query: MutableStateFlow = MutableStateFlow("")
+ val query: StateFlow = _query
+
+ private var job: Job? = null
+ private var countDownTimer: CountDownTimer? = null
+
+ init {
+ setupInitialData()
+ }
+
+ private fun setupInitialData() {
+ viewModelScope.launch {
+ _packageDetails.emit(PackagesRepository.getPackageDetails())
+ _suggestions.emit(PackagesRepository.getPackageNames())
+ }
+ }
+
+ fun searchQuery(query: String) {
+ job?.cancel()
+ job = viewModelScope.launch {
+ delay(DELAY)
+ _query.emit(query)
+ }
+ }
+
+ fun searchQueryWithCountDown(query: String) {
+ countDownTimer?.cancel()
+ countDownTimer = SearchCountDownTimer(DELAY) {
+ viewModelScope.launch {
+ _query.emit(query)
+ }
+ }.start()
+ }
+
+ companion object {
+ private const val DELAY = 1500L
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/searchwebview/ui/viewmodels/WebViewViewModel.kt b/Demo/app/src/main/java/com/krunal/demo/searchwebview/ui/viewmodels/WebViewViewModel.kt
new file mode 100644
index 0000000..cf4f0b8
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/searchwebview/ui/viewmodels/WebViewViewModel.kt
@@ -0,0 +1,24 @@
+package com.krunal.demo.searchwebview.ui.viewmodels
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.krunal.demo.searchwebview.data.repositories.WebViewRepository
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+
+class WebViewViewModel : ViewModel() {
+
+ private val _url: MutableStateFlow = MutableStateFlow("")
+ val url: StateFlow = _url
+
+ init {
+ loadUrl(WebViewRepository.getRandomUrl())
+ }
+
+ fun loadUrl(url: String) {
+ viewModelScope.launch {
+ _url.emit(url)
+ }
+ }
+}
\ 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
index 7fc27ed..7f21876 100644
--- a/Demo/app/src/main/java/com/krunal/demo/uicomponents/ThemeFragment.kt
+++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/ThemeFragment.kt
@@ -10,7 +10,7 @@ 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 com.krunal.demo.helpers.ThemeHelper
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/utils/PreferenceKeys.kt b/Demo/app/src/main/java/com/krunal/demo/utils/PreferenceKeys.kt
similarity index 56%
rename from Demo/app/src/main/java/com/krunal/demo/uicomponents/utils/PreferenceKeys.kt
rename to Demo/app/src/main/java/com/krunal/demo/utils/PreferenceKeys.kt
index 70f4061..0ba9724 100644
--- a/Demo/app/src/main/java/com/krunal/demo/uicomponents/utils/PreferenceKeys.kt
+++ b/Demo/app/src/main/java/com/krunal/demo/utils/PreferenceKeys.kt
@@ -1,4 +1,4 @@
-package com.krunal.demo.uicomponents.utils
+package com.krunal.demo.utils
object PreferenceKeys {
@@ -8,4 +8,8 @@ object PreferenceKeys {
const val THEME_MODE = "theme_mode"
const val ACCENT_COLOR = "accent_color"
+ /**
+ * Trivia game
+ */
+ const val TRIVIA_USER_ID = "trivia_user_id"
}
diff --git a/Demo/app/src/main/res/anim/nav_slide_in_bottom.xml b/Demo/app/src/main/res/anim/nav_slide_in_bottom.xml
new file mode 100644
index 0000000..80f1088
--- /dev/null
+++ b/Demo/app/src/main/res/anim/nav_slide_in_bottom.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/anim/nav_slide_in_left.xml b/Demo/app/src/main/res/anim/nav_slide_in_left.xml
new file mode 100644
index 0000000..00a1df4
--- /dev/null
+++ b/Demo/app/src/main/res/anim/nav_slide_in_left.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/anim/nav_slide_in_right.xml b/Demo/app/src/main/res/anim/nav_slide_in_right.xml
new file mode 100644
index 0000000..7a41221
--- /dev/null
+++ b/Demo/app/src/main/res/anim/nav_slide_in_right.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/anim/nav_slide_in_up.xml b/Demo/app/src/main/res/anim/nav_slide_in_up.xml
new file mode 100644
index 0000000..fbc0fb7
--- /dev/null
+++ b/Demo/app/src/main/res/anim/nav_slide_in_up.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/anim/nav_slide_out_bottom.xml b/Demo/app/src/main/res/anim/nav_slide_out_bottom.xml
new file mode 100644
index 0000000..fbc0fb7
--- /dev/null
+++ b/Demo/app/src/main/res/anim/nav_slide_out_bottom.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/anim/nav_slide_out_left.xml b/Demo/app/src/main/res/anim/nav_slide_out_left.xml
new file mode 100644
index 0000000..821243c
--- /dev/null
+++ b/Demo/app/src/main/res/anim/nav_slide_out_left.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/anim/nav_slide_out_right.xml b/Demo/app/src/main/res/anim/nav_slide_out_right.xml
new file mode 100644
index 0000000..6c9825a
--- /dev/null
+++ b/Demo/app/src/main/res/anim/nav_slide_out_right.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/anim/nav_slide_out_up.xml b/Demo/app/src/main/res/anim/nav_slide_out_up.xml
new file mode 100644
index 0000000..0f61a9e
--- /dev/null
+++ b/Demo/app/src/main/res/anim/nav_slide_out_up.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/drawable/avatar_10_raster.png b/Demo/app/src/main/res/drawable/avatar_10_raster.png
new file mode 100644
index 0000000..f65c0b2
Binary files /dev/null and b/Demo/app/src/main/res/drawable/avatar_10_raster.png differ
diff --git a/Demo/app/src/main/res/drawable/avatar_11_raster.png b/Demo/app/src/main/res/drawable/avatar_11_raster.png
new file mode 100644
index 0000000..18b842b
Binary files /dev/null and b/Demo/app/src/main/res/drawable/avatar_11_raster.png differ
diff --git a/Demo/app/src/main/res/drawable/avatar_12_raster.png b/Demo/app/src/main/res/drawable/avatar_12_raster.png
new file mode 100644
index 0000000..fd71f8c
Binary files /dev/null and b/Demo/app/src/main/res/drawable/avatar_12_raster.png differ
diff --git a/Demo/app/src/main/res/drawable/avatar_13_raster.png b/Demo/app/src/main/res/drawable/avatar_13_raster.png
new file mode 100644
index 0000000..b97517f
Binary files /dev/null and b/Demo/app/src/main/res/drawable/avatar_13_raster.png differ
diff --git a/Demo/app/src/main/res/drawable/avatar_14_raster.png b/Demo/app/src/main/res/drawable/avatar_14_raster.png
new file mode 100644
index 0000000..fb7a871
Binary files /dev/null and b/Demo/app/src/main/res/drawable/avatar_14_raster.png differ
diff --git a/Demo/app/src/main/res/drawable/avatar_15_raster.png b/Demo/app/src/main/res/drawable/avatar_15_raster.png
new file mode 100644
index 0000000..2860447
Binary files /dev/null and b/Demo/app/src/main/res/drawable/avatar_15_raster.png differ
diff --git a/Demo/app/src/main/res/drawable/avatar_16_raster.png b/Demo/app/src/main/res/drawable/avatar_16_raster.png
new file mode 100644
index 0000000..52ee27a
Binary files /dev/null and b/Demo/app/src/main/res/drawable/avatar_16_raster.png differ
diff --git a/Demo/app/src/main/res/drawable/avatar_1_raster.png b/Demo/app/src/main/res/drawable/avatar_1_raster.png
new file mode 100644
index 0000000..22ff80a
Binary files /dev/null and b/Demo/app/src/main/res/drawable/avatar_1_raster.png differ
diff --git a/Demo/app/src/main/res/drawable/avatar_2_raster.png b/Demo/app/src/main/res/drawable/avatar_2_raster.png
new file mode 100644
index 0000000..0126588
Binary files /dev/null and b/Demo/app/src/main/res/drawable/avatar_2_raster.png differ
diff --git a/Demo/app/src/main/res/drawable/avatar_3_raster.png b/Demo/app/src/main/res/drawable/avatar_3_raster.png
new file mode 100644
index 0000000..8ecc7fb
Binary files /dev/null and b/Demo/app/src/main/res/drawable/avatar_3_raster.png differ
diff --git a/Demo/app/src/main/res/drawable/avatar_4_raster.png b/Demo/app/src/main/res/drawable/avatar_4_raster.png
new file mode 100644
index 0000000..a04de0a
Binary files /dev/null and b/Demo/app/src/main/res/drawable/avatar_4_raster.png differ
diff --git a/Demo/app/src/main/res/drawable/avatar_5_raster.png b/Demo/app/src/main/res/drawable/avatar_5_raster.png
new file mode 100644
index 0000000..b0ebf8c
Binary files /dev/null and b/Demo/app/src/main/res/drawable/avatar_5_raster.png differ
diff --git a/Demo/app/src/main/res/drawable/avatar_6_raster.png b/Demo/app/src/main/res/drawable/avatar_6_raster.png
new file mode 100644
index 0000000..410f7ee
Binary files /dev/null and b/Demo/app/src/main/res/drawable/avatar_6_raster.png differ
diff --git a/Demo/app/src/main/res/drawable/avatar_7_raster.png b/Demo/app/src/main/res/drawable/avatar_7_raster.png
new file mode 100644
index 0000000..42df954
Binary files /dev/null and b/Demo/app/src/main/res/drawable/avatar_7_raster.png differ
diff --git a/Demo/app/src/main/res/drawable/avatar_8_raster.png b/Demo/app/src/main/res/drawable/avatar_8_raster.png
new file mode 100644
index 0000000..ea62706
Binary files /dev/null and b/Demo/app/src/main/res/drawable/avatar_8_raster.png differ
diff --git a/Demo/app/src/main/res/drawable/avatar_9_raster.png b/Demo/app/src/main/res/drawable/avatar_9_raster.png
new file mode 100644
index 0000000..e612b7a
Binary files /dev/null and b/Demo/app/src/main/res/drawable/avatar_9_raster.png differ
diff --git a/Demo/app/src/main/res/drawable/button_press.xml b/Demo/app/src/main/res/drawable/button_press.xml
new file mode 100644
index 0000000..86b4589
--- /dev/null
+++ b/Demo/app/src/main/res/drawable/button_press.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
diff --git a/Demo/app/src/main/res/drawable/button_press_dark.xml b/Demo/app/src/main/res/drawable/button_press_dark.xml
new file mode 100644
index 0000000..c874f22
--- /dev/null
+++ b/Demo/app/src/main/res/drawable/button_press_dark.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
diff --git a/Demo/app/src/main/res/drawable/circle.xml b/Demo/app/src/main/res/drawable/circle.xml
new file mode 100644
index 0000000..24ee8e0
--- /dev/null
+++ b/Demo/app/src/main/res/drawable/circle.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/Demo/app/src/main/res/drawable/dark_frame.xml b/Demo/app/src/main/res/drawable/dark_frame.xml
new file mode 100644
index 0000000..118891e
--- /dev/null
+++ b/Demo/app/src/main/res/drawable/dark_frame.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/drawable/ic_arrow_outward.xml b/Demo/app/src/main/res/drawable/ic_arrow_outward.xml
new file mode 100644
index 0000000..d0a2177
--- /dev/null
+++ b/Demo/app/src/main/res/drawable/ic_arrow_outward.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/Demo/app/src/main/res/drawable/ic_feedback.xml b/Demo/app/src/main/res/drawable/ic_feedback.xml
new file mode 100644
index 0000000..a14523a
--- /dev/null
+++ b/Demo/app/src/main/res/drawable/ic_feedback.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/Demo/app/src/main/res/drawable/ic_home.xml b/Demo/app/src/main/res/drawable/ic_home.xml
index 4c5e854..f8d24e9 100644
--- a/Demo/app/src/main/res/drawable/ic_home.xml
+++ b/Demo/app/src/main/res/drawable/ic_home.xml
@@ -1,5 +1,11 @@
-
-
+
+
+
+
diff --git a/Demo/app/src/main/res/drawable/ic_list.xml b/Demo/app/src/main/res/drawable/ic_list.xml
index 2610659..05526e8 100644
--- a/Demo/app/src/main/res/drawable/ic_list.xml
+++ b/Demo/app/src/main/res/drawable/ic_list.xml
@@ -1,5 +1,11 @@
-
-
+
+
+
+
diff --git a/Demo/app/src/main/res/drawable/ic_termux.xml b/Demo/app/src/main/res/drawable/ic_termux.xml
new file mode 100644
index 0000000..472e1f8
--- /dev/null
+++ b/Demo/app/src/main/res/drawable/ic_termux.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/drawable/ic_web.xml b/Demo/app/src/main/res/drawable/ic_web.xml
new file mode 100644
index 0000000..fa17c74
--- /dev/null
+++ b/Demo/app/src/main/res/drawable/ic_web.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/Demo/app/src/main/res/drawable/icon_category_leaderboard_raster.png b/Demo/app/src/main/res/drawable/icon_category_leaderboard_raster.png
new file mode 100644
index 0000000..47db979
Binary files /dev/null and b/Demo/app/src/main/res/drawable/icon_category_leaderboard_raster.png differ
diff --git a/Demo/app/src/main/res/drawable/image_category_entertainment_raster.png b/Demo/app/src/main/res/drawable/image_category_entertainment_raster.png
new file mode 100644
index 0000000..ae6cc52
Binary files /dev/null and b/Demo/app/src/main/res/drawable/image_category_entertainment_raster.png differ
diff --git a/Demo/app/src/main/res/drawable/image_category_tvmovies_raster.png b/Demo/app/src/main/res/drawable/image_category_tvmovies_raster.png
new file mode 100644
index 0000000..3b4fa16
Binary files /dev/null and b/Demo/app/src/main/res/drawable/image_category_tvmovies_raster.png differ
diff --git a/Demo/app/src/main/res/drawable/lose_button.xml b/Demo/app/src/main/res/drawable/lose_button.xml
new file mode 100644
index 0000000..85b3e3f
--- /dev/null
+++ b/Demo/app/src/main/res/drawable/lose_button.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/drawable/mask.xml b/Demo/app/src/main/res/drawable/mask.xml
new file mode 100644
index 0000000..9e51e2f
--- /dev/null
+++ b/Demo/app/src/main/res/drawable/mask.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/drawable/purple_frame.xml b/Demo/app/src/main/res/drawable/purple_frame.xml
new file mode 100644
index 0000000..d44965f
--- /dev/null
+++ b/Demo/app/src/main/res/drawable/purple_frame.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/Demo/app/src/main/res/drawable/rounded_button.xml b/Demo/app/src/main/res/drawable/rounded_button.xml
new file mode 100644
index 0000000..126b4c7
--- /dev/null
+++ b/Demo/app/src/main/res/drawable/rounded_button.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/Demo/app/src/main/res/drawable/rounded_button_dark.xml b/Demo/app/src/main/res/drawable/rounded_button_dark.xml
new file mode 100644
index 0000000..a872fba
--- /dev/null
+++ b/Demo/app/src/main/res/drawable/rounded_button_dark.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/drawable/rounded_button_white.xml b/Demo/app/src/main/res/drawable/rounded_button_white.xml
new file mode 100644
index 0000000..6d7dc73
--- /dev/null
+++ b/Demo/app/src/main/res/drawable/rounded_button_white.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/drawable/rounded_rect.xml b/Demo/app/src/main/res/drawable/rounded_rect.xml
new file mode 100644
index 0000000..46ce684
--- /dev/null
+++ b/Demo/app/src/main/res/drawable/rounded_rect.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/Demo/app/src/main/res/drawable/white_outline.xml b/Demo/app/src/main/res/drawable/white_outline.xml
new file mode 100644
index 0000000..9b55f87
--- /dev/null
+++ b/Demo/app/src/main/res/drawable/white_outline.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/drawable/win_button.xml b/Demo/app/src/main/res/drawable/win_button.xml
new file mode 100644
index 0000000..fd4904e
--- /dev/null
+++ b/Demo/app/src/main/res/drawable/win_button.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/layout/activity_first.xml b/Demo/app/src/main/res/layout/activity_first.xml
new file mode 100644
index 0000000..6bc23db
--- /dev/null
+++ b/Demo/app/src/main/res/layout/activity_first.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/layout/activity_fragment_demo.xml b/Demo/app/src/main/res/layout/activity_fragment_demo.xml
new file mode 100644
index 0000000..ce0f534
--- /dev/null
+++ b/Demo/app/src/main/res/layout/activity_fragment_demo.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/layout/activity_image_picker.xml b/Demo/app/src/main/res/layout/activity_image_picker.xml
new file mode 100644
index 0000000..5f488f8
--- /dev/null
+++ b/Demo/app/src/main/res/layout/activity_image_picker.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/layout/activity_search_web.xml b/Demo/app/src/main/res/layout/activity_search_web.xml
new file mode 100644
index 0000000..26e9fc8
--- /dev/null
+++ b/Demo/app/src/main/res/layout/activity_search_web.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/layout/activity_second.xml b/Demo/app/src/main/res/layout/activity_second.xml
new file mode 100644
index 0000000..f60127c
--- /dev/null
+++ b/Demo/app/src/main/res/layout/activity_second.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/layout/activity_trivia_game.xml b/Demo/app/src/main/res/layout/activity_trivia_game.xml
new file mode 100644
index 0000000..a428936
--- /dev/null
+++ b/Demo/app/src/main/res/layout/activity_trivia_game.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/layout/fragment_chatting.xml b/Demo/app/src/main/res/layout/fragment_chatting.xml
index 78fdbe8..e599bb8 100644
--- a/Demo/app/src/main/res/layout/fragment_chatting.xml
+++ b/Demo/app/src/main/res/layout/fragment_chatting.xml
@@ -23,7 +23,6 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/layout/fragment_game_over.xml b/Demo/app/src/main/res/layout/fragment_game_over.xml
new file mode 100644
index 0000000..e24bbd6
--- /dev/null
+++ b/Demo/app/src/main/res/layout/fragment_game_over.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/layout/fragment_in_game.xml b/Demo/app/src/main/res/layout/fragment_in_game.xml
new file mode 100644
index 0000000..30509c4
--- /dev/null
+++ b/Demo/app/src/main/res/layout/fragment_in_game.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/layout/fragment_leaderboard.xml b/Demo/app/src/main/res/layout/fragment_leaderboard.xml
new file mode 100644
index 0000000..132337b
--- /dev/null
+++ b/Demo/app/src/main/res/layout/fragment_leaderboard.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/layout/fragment_match.xml b/Demo/app/src/main/res/layout/fragment_match.xml
new file mode 100644
index 0000000..e815378
--- /dev/null
+++ b/Demo/app/src/main/res/layout/fragment_match.xml
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/layout/fragment_register.xml b/Demo/app/src/main/res/layout/fragment_register.xml
new file mode 100644
index 0000000..16b3708
--- /dev/null
+++ b/Demo/app/src/main/res/layout/fragment_register.xml
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/layout/fragment_result_winner.xml b/Demo/app/src/main/res/layout/fragment_result_winner.xml
new file mode 100644
index 0000000..80a8bf1
--- /dev/null
+++ b/Demo/app/src/main/res/layout/fragment_result_winner.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/layout/fragment_search_view.xml b/Demo/app/src/main/res/layout/fragment_search_view.xml
new file mode 100644
index 0000000..369e10c
--- /dev/null
+++ b/Demo/app/src/main/res/layout/fragment_search_view.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/layout/fragment_second.xml b/Demo/app/src/main/res/layout/fragment_second.xml
new file mode 100644
index 0000000..2e542a5
--- /dev/null
+++ b/Demo/app/src/main/res/layout/fragment_second.xml
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/layout/fragment_title_screen.xml b/Demo/app/src/main/res/layout/fragment_title_screen.xml
new file mode 100644
index 0000000..d06c1bd
--- /dev/null
+++ b/Demo/app/src/main/res/layout/fragment_title_screen.xml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/layout/fragment_user_profile.xml b/Demo/app/src/main/res/layout/fragment_user_profile.xml
new file mode 100644
index 0000000..d38c8dd
--- /dev/null
+++ b/Demo/app/src/main/res/layout/fragment_user_profile.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/layout/fragment_web_view.xml b/Demo/app/src/main/res/layout/fragment_web_view.xml
new file mode 100644
index 0000000..72d8228
--- /dev/null
+++ b/Demo/app/src/main/res/layout/fragment_web_view.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/layout/item_leaderboard_user.xml b/Demo/app/src/main/res/layout/item_leaderboard_user.xml
new file mode 100644
index 0000000..9b601af
--- /dev/null
+++ b/Demo/app/src/main/res/layout/item_leaderboard_user.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/layout/item_package_detail.xml b/Demo/app/src/main/res/layout/item_package_detail.xml
new file mode 100644
index 0000000..31b9e1b
--- /dev/null
+++ b/Demo/app/src/main/res/layout/item_package_detail.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/layout/item_search_suggestion.xml b/Demo/app/src/main/res/layout/item_search_suggestion.xml
new file mode 100644
index 0000000..d72f220
--- /dev/null
+++ b/Demo/app/src/main/res/layout/item_search_suggestion.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/layout/user_card.xml b/Demo/app/src/main/res/layout/user_card.xml
new file mode 100644
index 0000000..881727a
--- /dev/null
+++ b/Demo/app/src/main/res/layout/user_card.xml
@@ -0,0 +1,148 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/menu/search_web_bottom_nav.xml b/Demo/app/src/main/res/menu/search_web_bottom_nav.xml
new file mode 100644
index 0000000..45a232f
--- /dev/null
+++ b/Demo/app/src/main/res/menu/search_web_bottom_nav.xml
@@ -0,0 +1,19 @@
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/menu/trivia_game_bottom_nav.xml b/Demo/app/src/main/res/menu/trivia_game_bottom_nav.xml
new file mode 100644
index 0000000..258eb2a
--- /dev/null
+++ b/Demo/app/src/main/res/menu/trivia_game_bottom_nav.xml
@@ -0,0 +1,19 @@
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/navigation/search_web_nav_graph.xml b/Demo/app/src/main/res/navigation/search_web_nav_graph.xml
new file mode 100644
index 0000000..e6a623f
--- /dev/null
+++ b/Demo/app/src/main/res/navigation/search_web_nav_graph.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/navigation/trivia_nav_graph.xml b/Demo/app/src/main/res/navigation/trivia_nav_graph.xml
new file mode 100644
index 0000000..ce8e5be
--- /dev/null
+++ b/Demo/app/src/main/res/navigation/trivia_nav_graph.xml
@@ -0,0 +1,151 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/raw/unauthorised.html b/Demo/app/src/main/res/raw/unauthorised.html
new file mode 100644
index 0000000..5b728b3
--- /dev/null
+++ b/Demo/app/src/main/res/raw/unauthorised.html
@@ -0,0 +1,39 @@
+
+
+
+ Access Denied
+
+
+
+
+
+
+
+ Access Denied
+
+
+
You don't have permission to view this site.
+
🚫🚫🚫🚫
+
error: website is blocked
+
+
+
\ No newline at end of file
diff --git a/Demo/app/src/main/res/values/colors.xml b/Demo/app/src/main/res/values/colors.xml
index 44ee837..40a2d0a 100644
--- a/Demo/app/src/main/res/values/colors.xml
+++ b/Demo/app/src/main/res/values/colors.xml
@@ -444,4 +444,9 @@
#7E8084#729975#d26e6d
+
+ #35A571
+ #B7F8C9
+ #FF897D
+ #FFE2DF
\ No newline at end of file
diff --git a/Demo/app/src/main/res/values/dimens.xml b/Demo/app/src/main/res/values/dimens.xml
index b89e01b..aae23bd 100644
--- a/Demo/app/src/main/res/values/dimens.xml
+++ b/Demo/app/src/main/res/values/dimens.xml
@@ -15,4 +15,31 @@
4dp16dp30dp
+ 1dp
+ 6dp
+ 0dp
+ 13sp
+ 18sp
+ 2dp
+ 8dp
+ 40dp
+ 30sp
+ 20dp
+ 200dp
+ 36sp
+ 362dp
+ 279dp
+ 42dp
+ 160dp
+ 24dp
+ 120dp
+ 225dp
+ 62sp
+ 34dp
+ 70dp
+ 16sp
+ 22sp
+ 52dp
+ 15sp
+ 4dp
\ No newline at end of file
diff --git a/Demo/app/src/main/res/values/strings.xml b/Demo/app/src/main/res/values/strings.xml
index 00b0f15..5991873 100644
--- a/Demo/app/src/main/res/values/strings.xml
+++ b/Demo/app/src/main/res/values/strings.xml
@@ -183,4 +183,44 @@
🎪 Coming to you live from Shoreline, it\'s #GoogleIO! \nJoin developers around the world for thoughtful discussions, interactive learning, and a first look at our latest product updates. LibraryType something...
+ Enter message to forward
+ Second activity
+ Send back to first
+ Send to Fragment
+ Second Fragment
+ Message with bundle
+ Message with shared viewModel
+ Send to implicit Fragment
+ Bottom navigation home
+ Home
+ Bottom navigation list
+ List
+ Bottom navigation registration form
+ Register
+ Rank
+ # %1$d
+ Losses
+ Wins
+ Congratulations
+ Next match
+ Leaderboards
+ Game over
+ Try again
+ VS
+ Welcome
+ About
+ Name
+ Email
+ Sign Up
+ Profile image
+ %1$s pts
+ Play
+ win
+ Loss
+ Save details
+ Web
+ Bottom navigation web
+ Bottom navigation packages
+ Application icon
+ Search app or package name
\ No newline at end of file
diff --git a/Demo/app/src/main/res/xml/provider_paths.xml b/Demo/app/src/main/res/xml/provider_paths.xml
new file mode 100644
index 0000000..262d6d6
--- /dev/null
+++ b/Demo/app/src/main/res/xml/provider_paths.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/build.gradle b/Demo/build.gradle
index c84cccf..0dc935f 100644
--- a/Demo/build.gradle
+++ b/Demo/build.gradle
@@ -1,4 +1,11 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ dependencies {
+ def nav_version = "2.5.3"
+ classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
+ }
+}
+
plugins {
id 'com.android.application' version '8.0.0' apply false
id 'com.android.library' version '8.0.0' apply false