diff --git a/app/build.gradle b/app/build.gradle index 0e043fc..aff1c05 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -14,6 +14,9 @@ apply from: 'jacoco.gradle' apply from: 'lint.gradle' apply from: 'build-scripts.gradle' +apply plugin: 'kotlin-kapt' +apply plugin: 'dagger.hilt.android.plugin' + repositories { mavenCentral() maven { url 'https://maven.google.com' } @@ -171,12 +174,15 @@ ext { lottie_version = "4.2.0" core_version = "1.6.0" desugar_jdk_version = "1.1.5" + hilt_version = "2.38.1" + glide_version = "4.13.0" + compose_ui_tool = "1.1.1" + // Compose - activity_compose_version = "1.3.1" + activity_compose_version = '1.4.0' constraintlayout_compose_version = "1.0.0-rc01" viewmodel_compose_version = "1.0.0-alpha07" } - jacoco { toolVersion = "0.8.7" } @@ -208,6 +214,8 @@ dependencies { implementation "com.squareup.retrofit2:retrofit:$retrofit_version" implementation "com.squareup.retrofit2:converter-moshi:$retrofit_version" + implementation "com.squareup.retrofit2:converter-gson:$retrofit_version" + // Http Logging Interceptor implementation "com.squareup.okhttp3:okhttp:$ok_http_version" implementation "com.squareup.okhttp3:logging-interceptor:$ok_http_version" @@ -291,6 +299,17 @@ dependencies { implementation "androidx.constraintlayout:constraintlayout-compose:$constraintlayout_compose_version" implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$viewmodel_compose_version" implementation "com.google.android.material:compose-theme-adapter:$compose_version" + debugImplementation "androidx.compose.ui:ui-tooling:$compose_ui_tool" + implementation "androidx.compose.ui:ui-tooling-preview:$compose_ui_tool" + + + // Hilt: Official Google Dependency Injection Library + implementation "com.google.dagger:hilt-android:$hilt_version" + kapt "com.google.dagger:hilt-android-compiler:$hilt_version" + + // Glide: Image loading framework + implementation "com.github.bumptech.glide:glide:$glide_version" + annotationProcessor "com.github.bumptech.glide:compiler:$glide_version" // Test // Unit testing framework diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 04b2829..9a52df3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -32,11 +32,14 @@ android:networkSecurityConfig="@xml/network_security_config" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" - android:theme="@style/Theme.AndroidBase.Main"> - + android:theme="@style/Theme.AndroidBase.Main" > + - + diff --git a/app/src/main/java/com/rocketinsights/android/RocketApplication.kt b/app/src/main/java/com/rocketinsights/android/RocketApplication.kt index 3de415c..adb280a 100644 --- a/app/src/main/java/com/rocketinsights/android/RocketApplication.kt +++ b/app/src/main/java/com/rocketinsights/android/RocketApplication.kt @@ -1,14 +1,17 @@ package com.rocketinsights.android import android.app.Application -import com.rocketinsights.android.di.initKoin +import androidx.viewbinding.BuildConfig +import com.rocketinsights.android.di.koin.initKoin import com.rocketinsights.android.work.messages.MessagesUpdateScheduler +import dagger.hilt.android.HiltAndroidApp import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.koin.android.ext.android.inject import timber.log.Timber +@HiltAndroidApp class RocketApplication : Application() { private val applicationScope = CoroutineScope(Dispatchers.Default) diff --git a/app/src/main/java/com/rocketinsights/android/di/hilt/AppModule.kt b/app/src/main/java/com/rocketinsights/android/di/hilt/AppModule.kt new file mode 100644 index 0000000..50eef66 --- /dev/null +++ b/app/src/main/java/com/rocketinsights/android/di/hilt/AppModule.kt @@ -0,0 +1,19 @@ +package com.rocketinsights.android.di.hilt + +import android.content.Context +import com.rocketinsights.android.RocketApplication +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object AppModule { + @Singleton + @Provides + fun provideApplication(@ApplicationContext app: Context): RocketApplication = + app as RocketApplication +} diff --git a/app/src/main/java/com/rocketinsights/android/di/hilt/HiltNetworkModule.kt b/app/src/main/java/com/rocketinsights/android/di/hilt/HiltNetworkModule.kt new file mode 100644 index 0000000..4e13cad --- /dev/null +++ b/app/src/main/java/com/rocketinsights/android/di/hilt/HiltNetworkModule.kt @@ -0,0 +1,57 @@ +package com.rocketinsights.android.di.hilt + +import com.google.gson.GsonBuilder +import com.rocketinsights.android.models.recipe_hilt.RecipeDtoMapper +import com.rocketinsights.android.network.hiltexample.RecipeService +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object HiltNetworkModule { + + private var okHttpClient: OkHttpClient? = null + + @Singleton + @Provides + fun providesRecipeMapper(): RecipeDtoMapper = RecipeDtoMapper() + + @Singleton + @Provides + fun provideRecipeService(): RecipeService { + return Retrofit.Builder() + .baseUrl("https://food2fork.ca/api/recipe/") + .addConverterFactory(GsonConverterFactory.create(GsonBuilder().create())) + .client(buildClientHttp()) + .build() + .create(RecipeService::class.java) + } + + private fun buildClientHttp(): OkHttpClient? { + okHttpClient = OkHttpClient.Builder() + .addInterceptor(buildInterceptor()) + .build() + + return okHttpClient + } + + private fun buildInterceptor() = Interceptor { chain -> + var request = chain.request() + + request = request.newBuilder() + .addHeader( + "Authorization", + "Token 9c8b06d329136da358c2d00e76946b0111ce2c48" + ) + .build() + + chain.proceed(request = request) + } +} diff --git a/app/src/main/java/com/rocketinsights/android/di/hilt/RepositoryModule.kt b/app/src/main/java/com/rocketinsights/android/di/hilt/RepositoryModule.kt new file mode 100644 index 0000000..e533985 --- /dev/null +++ b/app/src/main/java/com/rocketinsights/android/di/hilt/RepositoryModule.kt @@ -0,0 +1,28 @@ +package com.rocketinsights.android.di.hilt + +import com.rocketinsights.android.models.recipe_hilt.RecipeDtoMapper +import com.rocketinsights.android.network.hiltexample.RecipeService +import com.rocketinsights.android.repos.RecipeRepository +import com.rocketinsights.android.repos.RecipeRepositoryImplementation +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object RepositoryModule { + + @Singleton + @Provides + fun provideRecipeRepository( + recipeService: RecipeService, + recipeMapper: RecipeDtoMapper, + ): RecipeRepository { + return RecipeRepositoryImplementation( + recipeService = recipeService, + mapper = recipeMapper + ) + } +} diff --git a/app/src/main/java/com/rocketinsights/android/di/KoinUtils.kt b/app/src/main/java/com/rocketinsights/android/di/koin/KoinUtils.kt similarity index 99% rename from app/src/main/java/com/rocketinsights/android/di/KoinUtils.kt rename to app/src/main/java/com/rocketinsights/android/di/koin/KoinUtils.kt index fb0c482..bfe2917 100644 --- a/app/src/main/java/com/rocketinsights/android/di/KoinUtils.kt +++ b/app/src/main/java/com/rocketinsights/android/di/koin/KoinUtils.kt @@ -1,4 +1,4 @@ -package com.rocketinsights.android.di +package com.rocketinsights.android.di.koin import android.app.Application import android.app.NotificationManager diff --git a/app/src/main/java/com/rocketinsights/android/models/recipe_hilt/Recipe.kt b/app/src/main/java/com/rocketinsights/android/models/recipe_hilt/Recipe.kt new file mode 100644 index 0000000..5514eb6 --- /dev/null +++ b/app/src/main/java/com/rocketinsights/android/models/recipe_hilt/Recipe.kt @@ -0,0 +1,13 @@ +package com.rocketinsights.android.models.recipe_hilt + +data class Recipe( + val id: Int, + val title: String?, + val publisher: String, + val featuredImage: String?, + val rating: Int = 0, + val sourceUrl: String, + val ingredients: List, + val dateAdded: String, + val dateUpdated: String +) diff --git a/app/src/main/java/com/rocketinsights/android/models/recipe_hilt/RecipeDto.kt b/app/src/main/java/com/rocketinsights/android/models/recipe_hilt/RecipeDto.kt new file mode 100644 index 0000000..b4bfbcb --- /dev/null +++ b/app/src/main/java/com/rocketinsights/android/models/recipe_hilt/RecipeDto.kt @@ -0,0 +1,19 @@ +package com.rocketinsights.android.models.recipe_hilt + +import com.google.gson.annotations.SerializedName + +data class RecipeDto( + var pk: Int, + var title: String, + var publisher: String, + @SerializedName("featured_image") + var featuredImage: String, + var rating: Int = 0, + @SerializedName("source_url") + var sourceUrl: String, + var ingredients: List = emptyList(), + @SerializedName("date_added") + var dateAdded: String, + @SerializedName("date_updated") + var dateUpdated: String, +) diff --git a/app/src/main/java/com/rocketinsights/android/models/recipe_hilt/RecipeDtoMapper.kt b/app/src/main/java/com/rocketinsights/android/models/recipe_hilt/RecipeDtoMapper.kt new file mode 100644 index 0000000..fe59dc9 --- /dev/null +++ b/app/src/main/java/com/rocketinsights/android/models/recipe_hilt/RecipeDtoMapper.kt @@ -0,0 +1,19 @@ +package com.rocketinsights.android.models.recipe_hilt + +class RecipeDtoMapper { + fun mapToDomainModel(model: RecipeDto): Recipe { + return Recipe( + id = model.pk, + title = model.title, + featuredImage = model.featuredImage, + rating = model.rating, + publisher = model.publisher, + sourceUrl = model.sourceUrl, + ingredients = model.ingredients, + dateAdded = model.dateAdded, + dateUpdated = model.dateUpdated, + ) + } + + fun toDomainList(initial: List): List = initial.map { mapToDomainModel(it) } +} diff --git a/app/src/main/java/com/rocketinsights/android/network/hiltexample/RecipeSearchResponse.kt b/app/src/main/java/com/rocketinsights/android/network/hiltexample/RecipeSearchResponse.kt new file mode 100644 index 0000000..de99856 --- /dev/null +++ b/app/src/main/java/com/rocketinsights/android/network/hiltexample/RecipeSearchResponse.kt @@ -0,0 +1,8 @@ +package com.rocketinsights.android.network.hiltexample + +import com.rocketinsights.android.models.recipe_hilt.RecipeDto + +data class RecipeSearchResponse( + val count: Int, + val results: List, +) diff --git a/app/src/main/java/com/rocketinsights/android/network/hiltexample/RecipeService.kt b/app/src/main/java/com/rocketinsights/android/network/hiltexample/RecipeService.kt new file mode 100644 index 0000000..9660347 --- /dev/null +++ b/app/src/main/java/com/rocketinsights/android/network/hiltexample/RecipeService.kt @@ -0,0 +1,13 @@ +package com.rocketinsights.android.network.hiltexample + +import retrofit2.http.GET +import retrofit2.http.Query + +interface RecipeService { + + @GET("search") + suspend fun searchRecipe( + @Query("page") page: Int, + @Query("query") query: String + ): RecipeSearchResponse +} diff --git a/app/src/main/java/com/rocketinsights/android/repos/RecipeRepository.kt b/app/src/main/java/com/rocketinsights/android/repos/RecipeRepository.kt new file mode 100644 index 0000000..d1deecd --- /dev/null +++ b/app/src/main/java/com/rocketinsights/android/repos/RecipeRepository.kt @@ -0,0 +1,7 @@ +package com.rocketinsights.android.repos + +import com.rocketinsights.android.models.recipe_hilt.Recipe + +interface RecipeRepository { + suspend fun search(page: Int, query: String): List? +} diff --git a/app/src/main/java/com/rocketinsights/android/repos/RecipeRepositoryImplementation.kt b/app/src/main/java/com/rocketinsights/android/repos/RecipeRepositoryImplementation.kt new file mode 100644 index 0000000..df73112 --- /dev/null +++ b/app/src/main/java/com/rocketinsights/android/repos/RecipeRepositoryImplementation.kt @@ -0,0 +1,14 @@ +package com.rocketinsights.android.repos + +import com.rocketinsights.android.models.recipe_hilt.Recipe +import com.rocketinsights.android.models.recipe_hilt.RecipeDtoMapper +import com.rocketinsights.android.network.hiltexample.RecipeService + +class RecipeRepositoryImplementation( + private val recipeService: RecipeService, + private val mapper: RecipeDtoMapper, +) : RecipeRepository { + + override suspend fun search(page: Int, query: String): List = + mapper.toDomainList(recipeService.searchRecipe(page = page, query = query).results) +} diff --git a/app/src/main/java/com/rocketinsights/android/ui/HiltActivity.kt b/app/src/main/java/com/rocketinsights/android/ui/HiltActivity.kt new file mode 100644 index 0000000..8b0eb78 --- /dev/null +++ b/app/src/main/java/com/rocketinsights/android/ui/HiltActivity.kt @@ -0,0 +1,93 @@ +package com.rocketinsights.android.ui + +import android.os.Bundle +import androidx.activity.compose.setContent +import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.Icon +import androidx.compose.material.Text +import androidx.compose.material.TextField +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Search +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.unit.dp +import com.rocketinsights.android.ui.compose.CircularProgressBar +import com.rocketinsights.android.ui.compose.RecipeCard +import com.rocketinsights.android.viewmodels.HiltListViewModel +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class HiltActivity : AppCompatActivity() { + + private val viewModel: HiltListViewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + val recipes = viewModel.recipes.value + val searchQuery = viewModel.searchQuery.value + val loading = viewModel.loading.value + val focusManager = LocalFocusManager.current + + Column() { + TextField( + value = searchQuery, + onValueChange = { newValue -> + viewModel.onSearchQueryChanged(newValue) + }, + modifier = Modifier + .fillMaxWidth() + .padding(8.dp), + label = { + Text(text = "Search") + }, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Text, + imeAction = ImeAction.Search + ), + leadingIcon = { + Icon( + imageVector = Icons.Filled.Search, + contentDescription = "icon" + ) + }, + keyboardActions = KeyboardActions( + onSearch = { + viewModel.newSearch(searchQuery) + focusManager.clearFocus() + } + ) + ) + Box( + modifier = Modifier.fillMaxSize() + ) { + LazyColumn(content = { + itemsIndexed( + items = recipes + ) { index, recipe -> + RecipeCard( + recipe = recipe, + onCardClick = {} + ) + } + }) + + CircularProgressBar(isDisplayed = loading) + } + } + } + } +} diff --git a/app/src/main/java/com/rocketinsights/android/ui/MainFragment.kt b/app/src/main/java/com/rocketinsights/android/ui/MainFragment.kt index c1898ea..2eaf86c 100644 --- a/app/src/main/java/com/rocketinsights/android/ui/MainFragment.kt +++ b/app/src/main/java/com/rocketinsights/android/ui/MainFragment.kt @@ -1,5 +1,6 @@ package com.rocketinsights.android.ui +import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import android.os.Bundle @@ -126,6 +127,11 @@ class MainFragment : BaseFragment(R.layout.fragment_main) { setFadeThroughTransition() item.onNavDestinationSelected(findNavController()) } + R.id.hilt_activity -> { + setFadeThroughTransition() + Intent(this.context, HiltActivity::class.java).also { startActivity(it) } + true + } else -> super.onOptionsItemSelected(item) } } diff --git a/app/src/main/java/com/rocketinsights/android/ui/compose/CircularProgressBar.kt b/app/src/main/java/com/rocketinsights/android/ui/compose/CircularProgressBar.kt new file mode 100644 index 0000000..2f49b03 --- /dev/null +++ b/app/src/main/java/com/rocketinsights/android/ui/compose/CircularProgressBar.kt @@ -0,0 +1,29 @@ +package com.rocketinsights.android.ui.compose + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.CircularProgressIndicator +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +@Composable +fun CircularProgressBar( + isDisplayed: Boolean +) { + if (isDisplayed) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(30.dp), + horizontalArrangement = Arrangement.Center + ) { + CircularProgressIndicator( + color = MaterialTheme.colors.primary + ) + } + } +} diff --git a/app/src/main/java/com/rocketinsights/android/ui/compose/RecipeCard.kt b/app/src/main/java/com/rocketinsights/android/ui/compose/RecipeCard.kt new file mode 100644 index 0000000..a52500a --- /dev/null +++ b/app/src/main/java/com/rocketinsights/android/ui/compose/RecipeCard.kt @@ -0,0 +1,83 @@ +package com.rocketinsights.android.ui.compose + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.material.Card +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.unit.dp +import com.rocketinsights.android.R +import com.rocketinsights.android.models.recipe_hilt.Recipe +import com.rocketinsights.android.ui.compose.util.loadPicture + +@Composable +fun RecipeCard( + recipe: Recipe, + onCardClick: () -> Unit +) { + Card( + shape = MaterialTheme.shapes.small, + modifier = Modifier + .padding( + bottom = 6.dp, + top = 6.dp + ) + .fillMaxHeight() + .clickable(onClick = onCardClick), + elevation = 8.dp + ) { + Column { + recipe.featuredImage?.let { url -> + loadPicture( + url = url, + defaultImage = R.drawable.ic_rocketinsights_logo + ).value?.let { image -> + Image( + bitmap = image.asImageBitmap(), + contentDescription = "desc", + modifier = Modifier + .fillMaxWidth() + .height(225.dp), + contentScale = ContentScale.Crop + ) + } + } + + recipe.title?.let { title -> + Row( + modifier = Modifier + .fillMaxWidth() + .padding(12.dp) + ) { + Text( + text = title, + modifier = Modifier + .fillMaxWidth(0.85f) + .wrapContentWidth(Alignment.Start), + style = MaterialTheme.typography.h5 + ) + Text( + text = recipe.rating.toString(), + modifier = Modifier + .fillMaxWidth() + .wrapContentWidth(Alignment.End) + .align(Alignment.CenterVertically), + style = MaterialTheme.typography.h6 + ) + } + } + } + } +} diff --git a/app/src/main/java/com/rocketinsights/android/ui/compose/util/ImageComposableUtils.kt b/app/src/main/java/com/rocketinsights/android/ui/compose/util/ImageComposableUtils.kt new file mode 100644 index 0000000..f8d2cee --- /dev/null +++ b/app/src/main/java/com/rocketinsights/android/ui/compose/util/ImageComposableUtils.kt @@ -0,0 +1,36 @@ +package com.rocketinsights.android.ui.compose.util + +import android.annotation.SuppressLint +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import androidx.annotation.DrawableRes +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.platform.LocalContext +import com.bumptech.glide.Glide +import com.bumptech.glide.request.target.CustomTarget +import com.bumptech.glide.request.transition.Transition + +@SuppressLint("UnrememberedMutableState") +@Composable +fun loadPicture( + url: String, + @DrawableRes defaultImage: Int +): MutableState { + val bitmapState: MutableState = mutableStateOf(null) + + Glide.with(LocalContext.current) + .asBitmap() + .placeholder(defaultImage) + .load(url) + .into(object : CustomTarget() { + override fun onResourceReady(resource: Bitmap, transition: Transition?) { + bitmapState.value = resource + } + + override fun onLoadCleared(placeholder: Drawable?) {} + }) + + return bitmapState +} diff --git a/app/src/main/java/com/rocketinsights/android/viewmodels/HiltListViewModel.kt b/app/src/main/java/com/rocketinsights/android/viewmodels/HiltListViewModel.kt new file mode 100644 index 0000000..6c2b1e5 --- /dev/null +++ b/app/src/main/java/com/rocketinsights/android/viewmodels/HiltListViewModel.kt @@ -0,0 +1,52 @@ +package com.rocketinsights.android.viewmodels + +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.rocketinsights.android.models.recipe_hilt.Recipe +import com.rocketinsights.android.repos.RecipeRepository +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class HiltListViewModel +@Inject +constructor( + private val repository: RecipeRepository +) : ViewModel() { + + val recipes: MutableState> = mutableStateOf(emptyList()) + val searchQuery = mutableStateOf("") + val loading = mutableStateOf(false) + + init { + newSearch(searchQuery.value) + } + + fun newSearch(searchQuery: String) { + viewModelScope.launch { + loading.value = true + clearSearch() + + val result = repository.search( + page = 1, + query = searchQuery + ) + + result?.apply { + recipes.value = this + loading.value = false + } + } + } + + fun onSearchQueryChanged(query: String) { + this.searchQuery.value = query + } + + private fun clearSearch() { + recipes.value = listOf() + } +} diff --git a/app/src/main/res/menu/main_menu.xml b/app/src/main/res/menu/main_menu.xml index 764025a..8d3f4c2 100644 --- a/app/src/main/res/menu/main_menu.xml +++ b/app/src/main/res/menu/main_menu.xml @@ -29,6 +29,9 @@ + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b36d68a..9d62f0c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -109,4 +109,7 @@ Contacts Read contacts permission not granted + + + Hilt example diff --git a/build.gradle b/build.gradle index f23d4ed..c1eda9d 100644 --- a/build.gradle +++ b/build.gradle @@ -9,6 +9,7 @@ buildscript { navigation_version = "2.3.5" google_services_version = "4.3.10" compose_version = "1.0.3" + hilt_version = "2.38.1" } repositories { gradlePluginPortal() @@ -22,6 +23,7 @@ buildscript { classpath "com.google.gms:google-services:$google_services_version" classpath "org.owasp:dependency-check-gradle:$dependency_check_version" classpath "com.github.ben-manes:gradle-versions-plugin:$gradle_versions_version" + classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files }