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 @@ 4dp 16dp 30dp + 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. Library Type 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