Fragment Code Clean , and Unification

This commit is contained in:
Shabinder 2020-11-09 23:53:22 +05:30
parent 92e699075c
commit f869725953
9 changed files with 208 additions and 312 deletions

View File

@ -69,12 +69,12 @@ class MainActivity : AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
binding = DataBindingUtil.setContentView(this, R.layout.main_activity)
sharedViewModel = ViewModelProvider(this).get(SharedViewModel::class.java)
navController = findNavController(R.id.navHostFragment)
snackBarAnchor = binding.snackBarPosition
//Enabling Dark Mode
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
authenticateSpotify()

View File

@ -76,7 +76,7 @@ object DownloadHelper {
//Delay is Added ,if a request is in processing it may finish
Log.i("Spotify Helper","Download Request Sent")
sharedViewModel?.uiScope?.launch (Dispatchers.Main){
Toast.makeText(mainActivity,"Download Started, Now You can leave the App!", Toast.LENGTH_SHORT).show()
showMessage("Download Started, Now You can leave the App!")
}
startService(mainActivity,downloadList)
},5000)

View File

@ -17,55 +17,38 @@
package com.shabinder.spotiflyer.ui.gaana
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.SimpleItemAnimator
import com.shabinder.spotiflyer.R
import com.shabinder.spotiflyer.SharedViewModel
import com.shabinder.spotiflyer.databinding.TrackListFragmentBinding
import com.shabinder.spotiflyer.downloadHelper.DownloadHelper
import com.shabinder.spotiflyer.models.DownloadStatus
import com.shabinder.spotiflyer.models.TrackDetails
import com.shabinder.spotiflyer.models.spotify.Source
import com.shabinder.spotiflyer.networking.GaanaInterface
import com.shabinder.spotiflyer.networking.YoutubeMusicApi
import com.shabinder.spotiflyer.recyclerView.TrackListAdapter
import com.shabinder.spotiflyer.utils.*
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import javax.inject.Inject
@AndroidEntryPoint
class GaanaFragment : Fragment() {
class GaanaFragment : BaseFragment() {
override lateinit var baseViewModel: BaseViewModel
override lateinit var adapter: TrackListAdapter
override var source: Source = Source.Gaana
private val viewModel:GaanaViewModel
get() = baseViewModel as GaanaViewModel
private lateinit var binding: TrackListFragmentBinding
private lateinit var sharedViewModel: SharedViewModel
@Inject lateinit var youtubeMusicApi: YoutubeMusicApi
private lateinit var viewModel: GaanaViewModel
private lateinit var adapter: TrackListAdapter
@Inject lateinit var gaanaInterface: GaanaInterface
private var intentFilter: IntentFilter? = null
private var updateUIReceiver: BroadcastReceiver? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(inflater,R.layout.track_list_fragment, container, false)
super.onCreateView(inflater, container, savedInstanceState)
initializeAll()
initializeLiveDataObservers()
initializeBroadcast()
val gaanaLink = GaanaFragmentArgs.fromBundle(requireArguments()).link.substringAfter("gaana.com/")
//Link Schema: https://gaana.com/type/link
@ -92,16 +75,16 @@ class GaanaFragment : Fragment() {
binding.downloadingFab.visibility = View.VISIBLE
rotateAnim(binding.downloadingFab)
for (track in viewModel.trackList.value!!){
for (track in baseViewModel.trackList.value!!){
if(track.downloaded != DownloadStatus.Downloaded){
track.downloaded = DownloadStatus.Downloading
adapter.notifyItemChanged(viewModel.trackList.value!!.indexOf(track))
adapter.notifyItemChanged(baseViewModel.trackList.value!!.indexOf(track))
}
}
showMessage("Processing!")
sharedViewModel.uiScope.launch(Dispatchers.Default){
val urlList = arrayListOf<String>()
viewModel.trackList.value?.forEach { urlList.add(it.albumArtURL) }
baseViewModel.trackList.value?.forEach { urlList.add(it.albumArtURL) }
//Appending Source
urlList.add("gaana")
loadAllImages(
@ -109,19 +92,18 @@ class GaanaFragment : Fragment() {
urlList
)
}
viewModel.uiScope.launch {
val finalList = viewModel.trackList.value
baseViewModel.uiScope.launch {
val finalList = baseViewModel.trackList.value
if(finalList.isNullOrEmpty())showMessage("Not Downloading Any Song")
DownloadHelper.downloadAllTracks(
viewModel.folderType,
viewModel.subFolder,
baseViewModel.folderType,
baseViewModel.subFolder,
finalList ?: listOf(),
)
}
}
}
}
return binding.root
}
@ -130,7 +112,7 @@ class GaanaFragment : Fragment() {
**/
private fun initializeAll() {
sharedViewModel = ViewModelProvider(this.requireActivity()).get(SharedViewModel::class.java)
viewModel = ViewModelProvider(this).get(GaanaViewModel::class.java)
baseViewModel = ViewModelProvider(this).get(GaanaViewModel::class.java)
viewModel.gaanaInterface = gaanaInterface
adapter = TrackListAdapter(viewModel)
DownloadHelper.youtubeMusicApi = youtubeMusicApi
@ -140,73 +122,5 @@ class GaanaFragment : Fragment() {
(binding.trackList.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
}
/**
*Live Data Observers
**/
private fun initializeLiveDataObservers() {
viewModel.trackList.observe(viewLifecycleOwner, {
if (it.isNotEmpty()){
Log.i("GaanaFragment","TrackList Updated")
adapter.submitList(it,Source.Gaana)
checkIfAllDownloaded()
}
})
viewModel.coverUrl.observe(viewLifecycleOwner, {
if(it!="Loading") bindImage(binding.coverImage,it, Source.Gaana)
})
viewModel.title.observe(viewLifecycleOwner, {
binding.titleView.text = it
})
}
private fun checkIfAllDownloaded() {
if(!viewModel.trackList.value!!.any { it.downloaded != DownloadStatus.Downloaded }){
//All Tracks Downloaded
binding.btnDownloadAll.visibility = View.GONE
binding.downloadingFab.apply{
setImageResource(R.drawable.ic_tick)
visibility = View.VISIBLE
clearAnimation()
}
}
}
private fun initializeBroadcast() {
intentFilter = IntentFilter()
intentFilter?.addAction("track_download_completed")
updateUIReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
//UI update here
if (intent != null){
val trackDetails = intent.getParcelableExtra<TrackDetails?>("track")
trackDetails?.let {
val position: Int = viewModel.trackList.value?.map { it.title }?.indexOf(trackDetails.title) ?: -1
Log.i("Track","Download Completed Intent :$position")
if(position != -1) {
val track = viewModel.trackList.value?.get(position)
track?.let{
it.downloaded = DownloadStatus.Downloaded
viewModel.trackList.value?.set(position, it)
adapter.notifyItemChanged(position)
checkIfAllDownloaded()
}
}
}
}
}
}
requireActivity().registerReceiver(updateUIReceiver, intentFilter)
}
override fun onResume() {
super.onResume()
initializeBroadcast()
}
override fun onPause() {
super.onPause()
requireActivity().unregisterReceiver(updateUIReceiver)
}
}

View File

@ -18,47 +18,31 @@
package com.shabinder.spotiflyer.ui.spotify
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.SimpleItemAnimator
import com.shabinder.spotiflyer.MainActivity
import com.shabinder.spotiflyer.R
import com.shabinder.spotiflyer.SharedViewModel
import com.shabinder.spotiflyer.databinding.TrackListFragmentBinding
import com.shabinder.spotiflyer.downloadHelper.DownloadHelper
import com.shabinder.spotiflyer.models.DownloadStatus
import com.shabinder.spotiflyer.models.TrackDetails
import com.shabinder.spotiflyer.models.spotify.Source
import com.shabinder.spotiflyer.networking.YoutubeMusicApi
import com.shabinder.spotiflyer.recyclerView.TrackListAdapter
import com.shabinder.spotiflyer.utils.*
import com.shabinder.spotiflyer.utils.Provider.mainActivity
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import javax.inject.Inject
@Suppress("DEPRECATION")
class SpotifyFragment : BaseFragment() {
@AndroidEntryPoint
class SpotifyFragment : Fragment() {
private lateinit var binding:TrackListFragmentBinding
private lateinit var sharedViewModel: SharedViewModel
@Inject lateinit var youtubeMusicApi: YoutubeMusicApi
private lateinit var viewModel: SpotifyViewModel
private lateinit var adapter:TrackListAdapter
private var intentFilter:IntentFilter? = null
private var updateUIReceiver: BroadcastReceiver? = null
override lateinit var baseViewModel: BaseViewModel
override lateinit var adapter: TrackListAdapter
override var source: Source = Source.Spotify
private val viewModel: SpotifyViewModel
get() = baseViewModel as SpotifyViewModel
@SuppressLint("SetJavaScriptEnabled")
@ -66,10 +50,8 @@ class SpotifyFragment : Fragment() {
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = DataBindingUtil.inflate(inflater,R.layout.track_list_fragment,container,false)
super.onCreateView(inflater, container, savedInstanceState)
initializeAll()
initializeLiveDataObservers()
initializeBroadcast()
val spotifyLink = SpotifyFragmentArgs.fromBundle(requireArguments()).link.substringAfter("open.spotify.com/")
@ -142,87 +124,15 @@ class SpotifyFragment : Fragment() {
* Basic Initialization
**/
private fun initializeAll() {
sharedViewModel = ViewModelProvider(this.requireActivity()).get(SharedViewModel::class.java)
viewModel = ViewModelProvider(this).get(SpotifyViewModel::class.java)
baseViewModel = ViewModelProvider(this).get(SpotifyViewModel::class.java)
adapter = TrackListAdapter(viewModel)
sharedViewModel.spotifyService.observe(viewLifecycleOwner, {
viewModel.spotifyService = it
})
adapter = TrackListAdapter(viewModel)
DownloadHelper.youtubeMusicApi = youtubeMusicApi
DownloadHelper.sharedViewModel = sharedViewModel
DownloadHelper.statusBar = binding.statusBar
binding.trackList.adapter = adapter
(binding.trackList.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
}
/**
*Live Data Observers
**/
private fun initializeLiveDataObservers() {
viewModel.trackList.observe(viewLifecycleOwner, {
if (it.isNotEmpty()){
Log.i("SpotifyFragment","TrackList Updated")
adapter.submitList(it,Source.Spotify)
checkIfAllDownloaded()
}
})
viewModel.coverUrl.observe(viewLifecycleOwner, {
if(it!="Loading") bindImage(binding.coverImage,it, Source.Spotify)
})
viewModel.title.observe(viewLifecycleOwner, {
binding.titleView.text = it
})
}
private fun checkIfAllDownloaded() {
if(!viewModel.trackList.value!!.any { it.downloaded != DownloadStatus.Downloaded }){
//All Tracks Downloaded
binding.btnDownloadAll.visibility = View.GONE
binding.downloadingFab.apply{
setImageResource(R.drawable.ic_tick)
visibility = View.VISIBLE
clearAnimation()
}
}
}
private fun initializeBroadcast() {
intentFilter = IntentFilter()
intentFilter?.addAction("track_download_completed")
updateUIReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
//UI update here
if (intent != null){
val trackDetails = intent.getParcelableExtra<TrackDetails?>("track")
trackDetails?.let {
val position: Int = viewModel.trackList.value?.map { it.title }?.indexOf(trackDetails.title) ?: -1
Log.i("Track","Download Completed Intent :$position")
if(position != -1) {
val track = viewModel.trackList.value?.get(position)
track?.let{
it.downloaded = DownloadStatus.Downloaded
viewModel.trackList.value?.set(position, it)
adapter.notifyItemChanged(position)
checkIfAllDownloaded()
}
}
}
}
}
}
requireActivity().registerReceiver(updateUIReceiver, intentFilter)
}
override fun onResume() {
super.onResume()
initializeBroadcast()
}
override fun onPause() {
super.onPause()
requireActivity().unregisterReceiver(updateUIReceiver)
}
}

View File

@ -17,43 +17,26 @@
package com.shabinder.spotiflyer.ui.youtube
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import com.github.kiulian.downloader.YoutubeDownloader
import com.shabinder.spotiflyer.R
import com.shabinder.spotiflyer.SharedViewModel
import com.shabinder.spotiflyer.databinding.TrackListFragmentBinding
import com.shabinder.spotiflyer.downloadHelper.YTDownloadHelper
import com.shabinder.spotiflyer.models.DownloadStatus
import com.shabinder.spotiflyer.models.TrackDetails
import com.shabinder.spotiflyer.models.spotify.Source
import com.shabinder.spotiflyer.recyclerView.TrackListAdapter
import com.shabinder.spotiflyer.utils.*
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import javax.inject.Inject
@AndroidEntryPoint
class YoutubeFragment : Fragment() {
class YoutubeFragment : BaseFragment() {
private lateinit var binding: TrackListFragmentBinding
private lateinit var viewModel: YoutubeViewModel
private lateinit var sharedViewModel: SharedViewModel
@Inject lateinit var ytDownloader: YoutubeDownloader
private lateinit var adapter : TrackListAdapter
private var intentFilter: IntentFilter? = null
private var updateUIReceiver: BroadcastReceiver? = null
override lateinit var baseViewModel: BaseViewModel
override lateinit var adapter : TrackListAdapter
override var source: Source = Source.YouTube
private val viewModel: YoutubeViewModel
get() = baseViewModel as YoutubeViewModel
private val sampleDomain2 = "youtu.be"
private val sampleDomain1 = "youtube.com"
@ -61,15 +44,11 @@ class YoutubeFragment : Fragment() {
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(inflater,R.layout.track_list_fragment,container,false)
viewModel = ViewModelProvider(this).get(YoutubeViewModel::class.java)
sharedViewModel = ViewModelProvider(this.requireActivity()).get(SharedViewModel::class.java)
super.onCreateView(inflater, container, savedInstanceState)
baseViewModel = ViewModelProvider(this).get(YoutubeViewModel::class.java)
adapter = TrackListAdapter(viewModel)
binding.trackList.adapter = adapter
initializeLiveDataObservers()
initializeBroadcast()
val args = YoutubeFragmentArgs.fromBundle(requireArguments())
val link = args.link
youtubeSearch(link)
@ -135,76 +114,4 @@ class YoutubeFragment : Fragment() {
}
}
}
override fun onResume() {
super.onResume()
initializeBroadcast()
}
private fun initializeBroadcast() {
intentFilter = IntentFilter()
intentFilter?.addAction("track_download_completed")
updateUIReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
//UI update here
if (intent != null){
val trackDetails = intent.getParcelableExtra<TrackDetails?>("track")
trackDetails?.let {
val position: Int = viewModel.trackList.value?.map { it.title }?.indexOf(trackDetails.title) ?: -1
Log.i("Track","Download Completed Intent :$position")
if(position != -1) {
val track = viewModel.trackList.value?.get(position)
track?.let{
it.downloaded = DownloadStatus.Downloaded
viewModel.trackList.value?.set(position, it)
adapter.notifyItemChanged(position)
checkIfAllDownloaded()
}
}
}
}
}
}
requireActivity().registerReceiver(updateUIReceiver, intentFilter)
}
override fun onPause() {
super.onPause()
requireActivity().unregisterReceiver(updateUIReceiver)
}
private fun checkIfAllDownloaded() {
if(!viewModel.trackList.value!!.any { it.downloaded != DownloadStatus.Downloaded }){
//All Tracks Downloaded
binding.btnDownloadAll.visibility = View.GONE
binding.downloadingFab.apply{
setImageResource(R.drawable.ic_tick)
visibility = View.VISIBLE
clearAnimation()
}
}
}
private fun initializeLiveDataObservers() {
/**
* CoverUrl Binding Observer!
**/
viewModel.coverUrl.observe(viewLifecycleOwner, {
if(it!="Loading") bindImage(binding.coverImage,it, Source.YouTube)
})
/**
* TrackList Binding Observer!
**/
viewModel.trackList.observe(viewLifecycleOwner, {
adapter.submitList(it,Source.YouTube)
})
/**
* Title Binding Observer!
**/
viewModel.title.observe(viewLifecycleOwner, {
binding.titleView.text = it
})
}
}

View File

@ -0,0 +1,148 @@
/*
* Copyright (C) 2020 Shabinder Singh
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.shabinder.spotiflyer.utils
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import com.github.kiulian.downloader.YoutubeDownloader
import com.shabinder.spotiflyer.R
import com.shabinder.spotiflyer.SharedViewModel
import com.shabinder.spotiflyer.databinding.TrackListFragmentBinding
import com.shabinder.spotiflyer.models.DownloadStatus
import com.shabinder.spotiflyer.models.TrackDetails
import com.shabinder.spotiflyer.models.spotify.Source
import com.shabinder.spotiflyer.networking.GaanaInterface
import com.shabinder.spotiflyer.networking.YoutubeMusicApi
import com.shabinder.spotiflyer.recyclerView.TrackListAdapter
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
abstract class BaseFragment : Fragment() {
@Inject open lateinit var youtubeMusicApi: YoutubeMusicApi
@Inject open lateinit var ytDownloader: YoutubeDownloader
@Inject open lateinit var gaanaInterface: GaanaInterface
open lateinit var sharedViewModel: SharedViewModel
open lateinit var binding: TrackListFragmentBinding
abstract var baseViewModel: BaseViewModel
abstract var adapter: TrackListAdapter
abstract var source: Source
private var intentFilter: IntentFilter? = null
private var updateUIReceiver: BroadcastReceiver? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedViewModel = ViewModelProvider(this.requireActivity()).get(SharedViewModel::class.java)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(inflater,R.layout.track_list_fragment, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initializeLiveDataObservers()
}
/**
*Live Data Observers
**/
open fun initializeLiveDataObservers() {
baseViewModel.trackList.observe(viewLifecycleOwner, {
if (it.isNotEmpty()){
Log.i("GaanaFragment","TrackList Updated")
adapter.submitList(it, source)
checkIfAllDownloaded()
}
})
baseViewModel.coverUrl.observe(viewLifecycleOwner, {
if(it!="Loading") bindImage(binding.coverImage,it, source)
})
baseViewModel.title.observe(viewLifecycleOwner, {
binding.titleView.text = it
})
}
open fun initializeBroadcast() {
intentFilter = IntentFilter()
intentFilter?.addAction("track_download_completed")
updateUIReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
//UI update here
if (intent != null){
val trackDetails = intent.getParcelableExtra<TrackDetails?>("track")
trackDetails?.let {
val position: Int = baseViewModel.trackList.value?.map { it.title }?.indexOf(trackDetails.title) ?: -1
Log.i("Track","Download Completed Intent :$position")
if(position != -1) {
val track = baseViewModel.trackList.value?.get(position)
track?.let{
it.downloaded = DownloadStatus.Downloaded
baseViewModel.trackList.value?.set(position, it)
adapter.notifyItemChanged(position)
checkIfAllDownloaded()
}
}
}
}
}
}
requireActivity().registerReceiver(updateUIReceiver, intentFilter)
}
override fun onResume() {
super.onResume()
initializeBroadcast()
}
override fun onPause() {
super.onPause()
requireActivity().unregisterReceiver(updateUIReceiver)
}
open fun checkIfAllDownloaded() {
if(!baseViewModel.trackList.value!!.any { it.downloaded != DownloadStatus.Downloaded }){
//All Tracks Downloaded
binding.btnDownloadAll.visibility = View.GONE
binding.downloadingFab.apply{
setImageResource(R.drawable.ic_tick)
visibility = View.VISIBLE
clearAnimation()
}
}
}
}

View File

@ -29,6 +29,7 @@ abstract class BaseViewModel:ViewModel() {
abstract var folderType:String
abstract var subFolder:String
open val trackList = MutableLiveData<MutableList<TrackDetails>>()
private val viewModelJob:CompletableJob = Job()
open val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)

View File

@ -281,3 +281,19 @@ fun createDirectories() {
fun getEmojiByUnicode(unicode: Int): String? {
return String(Character.toChars(unicode))
}
/*
internal val nullOnEmptyConverterFactory = object : Converter.Factory() {
fun converterFactory() = this
override fun responseBodyConverter(
type: Type,
annotations: Array<out Annotation>,
retrofit: Retrofit
) = object : Converter<ResponseBody, Any?> {
val nextResponseBodyConverter =
retrofit.nextResponseBodyConverter<Any?>(converterFactory(), type, annotations)
override fun convert(value: ResponseBody) =
if (value.contentLength() != 0L) nextResponseBodyConverter.convert(value) else null
}
}*/

View File

@ -20,7 +20,7 @@ buildscript {
ext{
kotlin_version = "1.4.10"
navigationVersion = '2.3.0'
ext.hilt_version = '2.28-alpha'
ext.hilt_version = '2.29.1-alpha'
}
repositories {
google()