diff --git a/app/build.gradle b/app/build.gradle
index c6d2c82..067a5d5 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,6 +1,9 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
+ id 'com.google.devtools.ksp'
+ id 'dagger.hilt.android.plugin'
}
android {
@@ -15,6 +18,7 @@ android {
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ buildConfigField "String", "BASE_URL", "\"https://api.npoint.io/\""
}
buildTypes {
@@ -24,21 +28,58 @@ android {
}
}
compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
+ sourceCompatibility JavaVersion.VERSION_17
+ targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
- jvmTarget = '1.8'
+ jvmTarget = '17'
+ }
+ kapt {
+ generateStubs = true
+ }
+ dataBinding {
+ enabled = true
}
}
dependencies {
- implementation 'androidx.core:core-ktx:1.9.0'
+ implementation 'androidx.core:core-ktx:1.10.1'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
+ // sdp & ssp library for support all sizes on the screen
+ implementation "com.intuit.sdp:sdp-android:$sdp_version"
+ implementation "com.intuit.ssp:ssp-android:$ssp_version"
+ // media3-exoplayer library for playing videos
+ implementation "androidx.media3:media3-exoplayer:$media3_version"
+ implementation "androidx.media3:media3-ui:$media3_version"
+ // dagger hilt dependencies for dependency injection
+ implementation "com.google.dagger:hilt-android:$hilt_version"
+ kapt "com.google.dagger:hilt-compiler:$hilt_version"
+ // datastore for saving data in the local storage
+ implementation "androidx.datastore:datastore-preferences:$datastore_version"
+ // mockito for testing purposes
+ testImplementation "org.mockito:mockito-core:$mockito_version"
+ // lifecycle scope for observing data
+ implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'
+ implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1'
+ // ktx for kotlin extensions
+ implementation 'androidx.activity:activity-ktx:1.7.2'
+ implementation 'androidx.fragment:fragment-ktx:1.6.1'
+ // navigation component for navigating between fragments
+ implementation 'androidx.navigation:navigation-fragment-ktx:2.6.0'
+ implementation 'androidx.navigation:navigation-ui-ktx:2.6.0'
+ // retrofit for networking calls
+ implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
+ implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
+ // coil library for loading images
+ implementation "io.coil-kt:coil:$coil_version"
+ // room database
+ implementation "androidx.room:room-runtime:$room_version"
+ implementation "androidx.room:room-ktx:$room_version"
+ ksp "androidx.room:room-compiler:$room_version"
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a05c8b9..5618e00 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,7 +2,12 @@
+
+
+
+
+
@@ -21,6 +27,10 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png
new file mode 100644
index 0000000..c684997
Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ
diff --git a/app/src/main/java/com/moataz/phood/MainActivity.kt b/app/src/main/java/com/moataz/phood/MainActivity.kt
deleted file mode 100644
index 0dd7a94..0000000
--- a/app/src/main/java/com/moataz/phood/MainActivity.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.moataz.phood
-
-import android.os.Bundle
-import androidx.appcompat.app.AppCompatActivity
-
-class MainActivity : AppCompatActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_main)
- }
-}
diff --git a/app/src/main/java/com/moataz/phood/app/PhoodApplication.kt b/app/src/main/java/com/moataz/phood/app/PhoodApplication.kt
new file mode 100644
index 0000000..78fdd76
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/app/PhoodApplication.kt
@@ -0,0 +1,7 @@
+package com.moataz.phood.app
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class PhoodApplication : Application()
diff --git a/app/src/main/java/com/moataz/phood/app/di/DataSourceModule.kt b/app/src/main/java/com/moataz/phood/app/di/DataSourceModule.kt
new file mode 100644
index 0000000..84d963f
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/app/di/DataSourceModule.kt
@@ -0,0 +1,20 @@
+package com.moataz.phood.app.di
+
+import com.moataz.phood.identity.data.local.IdentityLocalDataSourceImpl
+import com.moataz.phood.identity.data.repositories.datasources.IdentityLocalDataSource
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+abstract class DataSourceModule {
+
+ @Singleton
+ @Binds
+ abstract fun bindIdentityDataSource(
+ identityLocalDataSourceImpl: IdentityLocalDataSourceImpl,
+ ): IdentityLocalDataSource
+}
diff --git a/app/src/main/java/com/moataz/phood/app/di/DatabaseModule.kt b/app/src/main/java/com/moataz/phood/app/di/DatabaseModule.kt
new file mode 100644
index 0000000..9b2c270
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/app/di/DatabaseModule.kt
@@ -0,0 +1,49 @@
+package com.moataz.phood.app.di
+
+import android.content.Context
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.PreferenceDataStoreFactory
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.preferencesDataStoreFile
+import androidx.room.Room
+import com.moataz.phood.app.di.utils.Constant.DATABASE_NAME
+import com.moataz.phood.app.di.utils.Constant.PREFERENCE_NAME
+import com.moataz.phood.recipes.data.local.RecipesDao
+import com.moataz.phood.recipes.data.local.RecipesDatabase
+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 DatabaseModule {
+
+ @Singleton
+ @Provides
+ fun provideDatabase(@ApplicationContext appContext: Context): RecipesDatabase {
+ return Room.databaseBuilder(
+ appContext,
+ RecipesDatabase::class.java,
+ DATABASE_NAME,
+ ).build()
+ }
+
+ @Singleton
+ @Provides
+ fun provideRecipesDao(database: RecipesDatabase): RecipesDao {
+ return database.recipesDao()
+ }
+
+ @Provides
+ @Singleton
+ fun provideIdentityDataStorePreferences(
+ @ApplicationContext applicationContext: Context,
+ ): DataStore {
+ return PreferenceDataStoreFactory.create {
+ applicationContext.preferencesDataStoreFile(PREFERENCE_NAME)
+ }
+ }
+}
diff --git a/app/src/main/java/com/moataz/phood/app/di/NetworkModule.kt b/app/src/main/java/com/moataz/phood/app/di/NetworkModule.kt
new file mode 100644
index 0000000..3ed0d5a
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/app/di/NetworkModule.kt
@@ -0,0 +1,63 @@
+package com.moataz.phood.app.di
+
+import com.moataz.phood.BuildConfig
+import com.moataz.phood.identity.data.remote.IdentityService
+import com.moataz.phood.identity.data.remote.IdentityServiceImpl
+import com.moataz.phood.recipes.data.remote.RecipesService
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import okhttp3.OkHttpClient
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+import java.util.concurrent.TimeUnit
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object NetworkModule {
+
+ @Provides
+ @Singleton
+ fun provideIdentityService(): IdentityService {
+ return IdentityServiceImpl()
+ }
+
+ @Singleton
+ @Provides
+ fun provideRecipesNetworkService(
+ retrofit: Retrofit,
+ ): RecipesService {
+ return retrofit.create(RecipesService::class.java)
+ }
+
+ @Singleton
+ @Provides
+ fun provideOkHttpClient(): OkHttpClient {
+ val builder = OkHttpClient()
+ .newBuilder()
+ .callTimeout(25, TimeUnit.SECONDS)
+ .connectTimeout(25, TimeUnit.SECONDS)
+ return builder.build()
+ }
+
+ @Singleton
+ @Provides
+ fun provideGsonConverterFactory(): GsonConverterFactory {
+ return GsonConverterFactory.create()
+ }
+
+ @Singleton
+ @Provides
+ fun provideRetrofit(
+ okHttpClient: OkHttpClient,
+ gsonConverterFactory: GsonConverterFactory,
+ ): Retrofit {
+ return Retrofit.Builder()
+ .baseUrl(BuildConfig.BASE_URL)
+ .client(okHttpClient)
+ .addConverterFactory(gsonConverterFactory)
+ .build()
+ }
+}
diff --git a/app/src/main/java/com/moataz/phood/app/di/RepositoryModule.kt b/app/src/main/java/com/moataz/phood/app/di/RepositoryModule.kt
new file mode 100644
index 0000000..786046e
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/app/di/RepositoryModule.kt
@@ -0,0 +1,28 @@
+package com.moataz.phood.app.di
+
+import com.moataz.phood.identity.data.repositories.IdentityRepositoryImpl
+import com.moataz.phood.identity.domain.repository.IdentityRepository
+import com.moataz.phood.recipes.data.repositories.RecipesRepositoryImpl
+import com.moataz.phood.recipes.domain.repository.RecipesRepository
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.android.components.ViewModelComponent
+import dagger.hilt.android.scopes.ViewModelScoped
+
+@Module
+@InstallIn(ViewModelComponent::class)
+abstract class RepositoryModule {
+
+ @ViewModelScoped
+ @Binds
+ abstract fun bindIdentityRepository(
+ identityRepositoryImpl: IdentityRepositoryImpl,
+ ): IdentityRepository
+
+ @ViewModelScoped
+ @Binds
+ abstract fun bindRecipesRepository(
+ recipesRepositoryImpl: RecipesRepositoryImpl,
+ ): RecipesRepository
+}
diff --git a/app/src/main/java/com/moataz/phood/app/di/utils/Constant.kt b/app/src/main/java/com/moataz/phood/app/di/utils/Constant.kt
new file mode 100644
index 0000000..c6e51e9
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/app/di/utils/Constant.kt
@@ -0,0 +1,6 @@
+package com.moataz.phood.app.di.utils
+
+object Constant {
+ const val DATABASE_NAME = "phood_recipes.db"
+ const val PREFERENCE_NAME = "com.moataz.phood.app_preferences"
+}
diff --git a/app/src/main/java/com/moataz/phood/app/main/MainActivity.kt b/app/src/main/java/com/moataz/phood/app/main/MainActivity.kt
new file mode 100644
index 0000000..490c5f0
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/app/main/MainActivity.kt
@@ -0,0 +1,29 @@
+package com.moataz.phood.app.main
+
+import android.os.Build
+import android.os.Bundle
+import android.view.View
+import androidx.appcompat.app.AppCompatActivity
+import androidx.databinding.DataBindingUtil
+import com.moataz.phood.R
+import com.moataz.phood.databinding.ActivityMainBinding
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class MainActivity : AppCompatActivity() {
+ private lateinit var binding: ActivityMainBinding
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
+ initView()
+ }
+
+ private fun initView() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
+ } else {
+ window.statusBarColor = resources.getColor(R.color.main_color)
+ }
+ }
+}
diff --git a/app/src/main/java/com/moataz/phood/identity/data/local/IdentityLocalDataSourceImpl.kt b/app/src/main/java/com/moataz/phood/identity/data/local/IdentityLocalDataSourceImpl.kt
new file mode 100644
index 0000000..5364e36
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/identity/data/local/IdentityLocalDataSourceImpl.kt
@@ -0,0 +1,33 @@
+package com.moataz.phood.identity.data.local
+
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.booleanPreferencesKey
+import androidx.datastore.preferences.core.edit
+import com.moataz.phood.identity.data.local.utils.DataStorePreferencesKeys.LOGIN_STATE
+import com.moataz.phood.identity.data.repositories.datasources.IdentityLocalDataSource
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.withContext
+import javax.inject.Inject
+
+class IdentityLocalDataSourceImpl @Inject constructor(
+ private val userDataStore: DataStore,
+) : IdentityLocalDataSource {
+ override suspend fun saveLoggedInStatus(status: Boolean) {
+ withContext(Dispatchers.IO) {
+ userDataStore.edit { preferences ->
+ preferences[booleanPreferencesKey(LOGIN_STATE)] = status
+ }
+ }
+ }
+
+ override suspend fun getLoggedInStatus(): Boolean? {
+ return withContext(Dispatchers.IO) {
+ userDataStore.data.map { preferences ->
+ preferences[booleanPreferencesKey(LOGIN_STATE)]
+ }.first()
+ }
+ }
+}
diff --git a/app/src/main/java/com/moataz/phood/identity/data/local/utils/DataStorePreferencesKeys.kt b/app/src/main/java/com/moataz/phood/identity/data/local/utils/DataStorePreferencesKeys.kt
new file mode 100644
index 0000000..1487d45
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/identity/data/local/utils/DataStorePreferencesKeys.kt
@@ -0,0 +1,5 @@
+package com.moataz.phood.identity.data.local.utils
+
+object DataStorePreferencesKeys {
+ const val LOGIN_STATE = "login_state"
+}
diff --git a/app/src/main/java/com/moataz/phood/identity/data/remote/IdentityService.kt b/app/src/main/java/com/moataz/phood/identity/data/remote/IdentityService.kt
new file mode 100644
index 0000000..30a2f95
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/identity/data/remote/IdentityService.kt
@@ -0,0 +1,7 @@
+package com.moataz.phood.identity.data.remote
+
+import com.moataz.phood.identity.data.remote.dto.UserDTO
+
+interface IdentityService {
+ suspend fun signIn(email: String, password: String): UserDTO
+}
diff --git a/app/src/main/java/com/moataz/phood/identity/data/remote/IdentityServiceImpl.kt b/app/src/main/java/com/moataz/phood/identity/data/remote/IdentityServiceImpl.kt
new file mode 100644
index 0000000..dc17860
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/identity/data/remote/IdentityServiceImpl.kt
@@ -0,0 +1,9 @@
+package com.moataz.phood.identity.data.remote
+
+import com.moataz.phood.identity.data.remote.dto.UserDTO
+
+class IdentityServiceImpl : IdentityService {
+ override suspend fun signIn(email: String, password: String): UserDTO {
+ return UserDTO()
+ }
+}
diff --git a/app/src/main/java/com/moataz/phood/identity/data/remote/dto/UserDTO.kt b/app/src/main/java/com/moataz/phood/identity/data/remote/dto/UserDTO.kt
new file mode 100644
index 0000000..db2efe2
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/identity/data/remote/dto/UserDTO.kt
@@ -0,0 +1,7 @@
+package com.moataz.phood.identity.data.remote.dto
+
+data class UserDTO(
+ val id: Long = 525118,
+ val email: String = "mohamed.salah@phood.com",
+ val password: String = "Brightskies",
+)
diff --git a/app/src/main/java/com/moataz/phood/identity/data/repositories/IdentityRepositoryImpl.kt b/app/src/main/java/com/moataz/phood/identity/data/repositories/IdentityRepositoryImpl.kt
new file mode 100644
index 0000000..79bcfaf
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/identity/data/repositories/IdentityRepositoryImpl.kt
@@ -0,0 +1,25 @@
+package com.moataz.phood.identity.data.repositories
+
+import com.moataz.phood.identity.data.remote.IdentityService
+import com.moataz.phood.identity.data.repositories.datasources.IdentityLocalDataSource
+import com.moataz.phood.identity.data.repositories.mapper.toUser
+import com.moataz.phood.identity.domain.entities.User
+import com.moataz.phood.identity.domain.repository.IdentityRepository
+import javax.inject.Inject
+
+class IdentityRepositoryImpl @Inject constructor(
+ private val remoteService: IdentityService,
+ private val localDataSource: IdentityLocalDataSource,
+) : IdentityRepository {
+ override suspend fun signIn(email: String, password: String): User {
+ return remoteService.signIn(email, password).toUser()
+ }
+
+ override suspend fun saveLoggedInStatus(status: Boolean) {
+ localDataSource.saveLoggedInStatus(status)
+ }
+
+ override suspend fun getLoggedInStatus(): Boolean? {
+ return localDataSource.getLoggedInStatus()
+ }
+}
diff --git a/app/src/main/java/com/moataz/phood/identity/data/repositories/datasources/IdentityLocalDataSource.kt b/app/src/main/java/com/moataz/phood/identity/data/repositories/datasources/IdentityLocalDataSource.kt
new file mode 100644
index 0000000..ac90e01
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/identity/data/repositories/datasources/IdentityLocalDataSource.kt
@@ -0,0 +1,6 @@
+package com.moataz.phood.identity.data.repositories.datasources
+
+interface IdentityLocalDataSource {
+ suspend fun saveLoggedInStatus(status: Boolean)
+ suspend fun getLoggedInStatus(): Boolean?
+}
diff --git a/app/src/main/java/com/moataz/phood/identity/data/repositories/mapper/IdentityMapper.kt b/app/src/main/java/com/moataz/phood/identity/data/repositories/mapper/IdentityMapper.kt
new file mode 100644
index 0000000..3b97d1f
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/identity/data/repositories/mapper/IdentityMapper.kt
@@ -0,0 +1,12 @@
+package com.moataz.phood.identity.data.repositories.mapper
+
+import com.moataz.phood.identity.data.remote.dto.UserDTO
+import com.moataz.phood.identity.domain.entities.User
+
+internal fun UserDTO.toUser(): User {
+ return User(
+ id = id,
+ email = email,
+ password = password,
+ )
+}
diff --git a/app/src/main/java/com/moataz/phood/identity/domain/entities/User.kt b/app/src/main/java/com/moataz/phood/identity/domain/entities/User.kt
new file mode 100644
index 0000000..957b8df
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/identity/domain/entities/User.kt
@@ -0,0 +1,7 @@
+package com.moataz.phood.identity.domain.entities
+
+data class User(
+ val id: Long,
+ val email: String,
+ val password: String,
+)
diff --git a/app/src/main/java/com/moataz/phood/identity/domain/repository/IdentityRepository.kt b/app/src/main/java/com/moataz/phood/identity/domain/repository/IdentityRepository.kt
new file mode 100644
index 0000000..12ddf27
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/identity/domain/repository/IdentityRepository.kt
@@ -0,0 +1,9 @@
+package com.moataz.phood.identity.domain.repository
+
+import com.moataz.phood.identity.domain.entities.User
+
+interface IdentityRepository {
+ suspend fun signIn(email: String, password: String): User
+ suspend fun saveLoggedInStatus(status: Boolean)
+ suspend fun getLoggedInStatus(): Boolean?
+}
diff --git a/app/src/main/java/com/moataz/phood/identity/domain/usecases/LoggedInStatus.kt b/app/src/main/java/com/moataz/phood/identity/domain/usecases/LoggedInStatus.kt
new file mode 100644
index 0000000..dc4936c
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/identity/domain/usecases/LoggedInStatus.kt
@@ -0,0 +1,12 @@
+package com.moataz.phood.identity.domain.usecases
+
+import com.moataz.phood.identity.domain.repository.IdentityRepository
+import javax.inject.Inject
+
+class LoggedInStatus @Inject constructor(
+ private val identityRepository: IdentityRepository,
+) {
+ suspend operator fun invoke(): Boolean {
+ return identityRepository.getLoggedInStatus() ?: false
+ }
+}
diff --git a/app/src/main/java/com/moataz/phood/identity/domain/usecases/SignInUseCase.kt b/app/src/main/java/com/moataz/phood/identity/domain/usecases/SignInUseCase.kt
new file mode 100644
index 0000000..fdc4a94
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/identity/domain/usecases/SignInUseCase.kt
@@ -0,0 +1,18 @@
+package com.moataz.phood.identity.domain.usecases
+
+import com.moataz.phood.identity.domain.repository.IdentityRepository
+import javax.inject.Inject
+
+class SignInUseCase @Inject constructor(
+ private val identityRepository: IdentityRepository,
+) {
+ suspend operator fun invoke(email: String, password: String): Boolean {
+ val user = identityRepository.signIn(email, password)
+ return if (user.email == email && user.password == password) {
+ identityRepository.saveLoggedInStatus(true)
+ true
+ } else {
+ false
+ }
+ }
+}
diff --git a/app/src/main/java/com/moataz/phood/identity/ui/view/LoginActivity.kt b/app/src/main/java/com/moataz/phood/identity/ui/view/LoginActivity.kt
new file mode 100644
index 0000000..941d426
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/identity/ui/view/LoginActivity.kt
@@ -0,0 +1,87 @@
+package com.moataz.phood.identity.ui.view
+
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import android.view.View
+import android.widget.Toast
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import androidx.databinding.DataBindingUtil
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.moataz.phood.R
+import com.moataz.phood.app.main.MainActivity
+import com.moataz.phood.databinding.ActivityLoginBinding
+import com.moataz.phood.identity.ui.view.utils.Constants.INTRO_VIDEO_URL
+import com.moataz.phood.identity.ui.view.utils.CustomPlayerManager
+import com.moataz.phood.identity.ui.viewmodel.LoginViewModel
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.launch
+
+@AndroidEntryPoint
+class LoginActivity : AppCompatActivity() {
+ private lateinit var binding: ActivityLoginBinding
+ private lateinit var playerManager: CustomPlayerManager
+ private val viewModel: LoginViewModel by viewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = DataBindingUtil.setContentView(this, R.layout.activity_login)
+ binding.lifecycleOwner = this
+ binding.viewModel = viewModel
+ initStateBar()
+ initMediaPlayer()
+ observeEvents()
+ }
+
+ private fun initStateBar() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
+ } else {
+ window.statusBarColor = resources.getColor(R.color.main_color)
+ }
+ }
+
+ private fun initMediaPlayer() {
+ playerManager = CustomPlayerManager(this, binding.playerView)
+ playerManager.setMediaItem(INTRO_VIDEO_URL)
+ playerManager.displayCustomViewBeforePlayVideo(binding.loginImageBackground)
+ playerManager.displayVideoPlayerAfterVideoPlay(binding.loginImageBackground)
+ }
+
+ private fun observeEvents() {
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.isUserLoggedIn.collect { isUserLoggedIn ->
+ if (isUserLoggedIn) {
+ startActivity(
+ Intent(this@LoginActivity, MainActivity::class.java),
+ )
+ finish()
+ }
+ }
+ }
+ }
+
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.isLoginFailed.collect {isLoginFailed ->
+ if (isLoginFailed) {
+ Toast.makeText(
+ this@LoginActivity,
+ "Login Failed",
+ Toast.LENGTH_SHORT,
+ ).show()
+ }
+ }
+ }
+ }
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ playerManager.release()
+ }
+}
diff --git a/app/src/main/java/com/moataz/phood/identity/ui/view/databinding/ValidationDataBinding.kt b/app/src/main/java/com/moataz/phood/identity/ui/view/databinding/ValidationDataBinding.kt
new file mode 100644
index 0000000..d5c874e
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/identity/ui/view/databinding/ValidationDataBinding.kt
@@ -0,0 +1,9 @@
+package com.moataz.phood.identity.ui.view.databinding
+
+import androidx.databinding.BindingAdapter
+import com.google.android.material.textfield.TextInputLayout
+
+@BindingAdapter("app:isFieldValid", "app:errorMessage")
+fun setErrorText(textInputLayout: TextInputLayout, isValid: Boolean, errorMessage: String) {
+ textInputLayout.error = if (isValid) null else errorMessage
+}
diff --git a/app/src/main/java/com/moataz/phood/identity/ui/view/utils/Constants.kt b/app/src/main/java/com/moataz/phood/identity/ui/view/utils/Constants.kt
new file mode 100644
index 0000000..89aae4c
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/identity/ui/view/utils/Constants.kt
@@ -0,0 +1,5 @@
+package com.moataz.phood.identity.ui.view.utils
+
+object Constants {
+ const val INTRO_VIDEO_URL = "https://github-production-user-asset-6210df.s3.amazonaws.com/63272288/259910193-32a4a5c7-4844-4a49-837e-375965048de8.mp4"
+}
diff --git a/app/src/main/java/com/moataz/phood/identity/ui/view/utils/CustomPlayerManager.kt b/app/src/main/java/com/moataz/phood/identity/ui/view/utils/CustomPlayerManager.kt
new file mode 100644
index 0000000..b5a66fd
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/identity/ui/view/utils/CustomPlayerManager.kt
@@ -0,0 +1,56 @@
+package com.moataz.phood.identity.ui.view.utils
+
+import android.content.Context
+import android.view.View
+import android.view.View.GONE
+import android.view.View.INVISIBLE
+import android.view.View.VISIBLE
+import androidx.media3.common.MediaItem
+import androidx.media3.common.Player
+import androidx.media3.exoplayer.ExoPlayer
+import androidx.media3.ui.PlayerView
+
+class CustomPlayerManager(
+ private val context: Context,
+ private val playerView: PlayerView,
+) {
+ private lateinit var videoPlayer: ExoPlayer
+
+ init {
+ initPlayer()
+ }
+
+ private fun initPlayer() {
+ videoPlayer = ExoPlayer.Builder(context).build()
+ playerView.player = videoPlayer
+ videoPlayer.prepare()
+ videoPlayer.playWhenReady = true
+ videoPlayer.repeatMode = Player.REPEAT_MODE_ALL
+ }
+
+ fun setMediaItem(uri: String) {
+ val mediaItem = MediaItem.Builder().setUri(uri).build()
+ videoPlayer.setMediaItem(mediaItem)
+ videoPlayer.prepare()
+ }
+
+ fun displayCustomViewBeforePlayVideo(view: View) {
+ playerView.visibility = GONE
+ view.visibility = VISIBLE
+ }
+
+ fun displayVideoPlayerAfterVideoPlay(view: View) {
+ videoPlayer.addListener(object : Player.Listener {
+ override fun onPlaybackStateChanged(playbackState: Int) {
+ if (playbackState == ExoPlayer.STATE_READY) {
+ playerView.visibility = VISIBLE
+ view.visibility = INVISIBLE
+ }
+ }
+ })
+ }
+
+ fun release() {
+ videoPlayer.release()
+ }
+}
diff --git a/app/src/main/java/com/moataz/phood/identity/ui/viewmodel/LoginViewModel.kt b/app/src/main/java/com/moataz/phood/identity/ui/viewmodel/LoginViewModel.kt
new file mode 100644
index 0000000..fa57092
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/identity/ui/viewmodel/LoginViewModel.kt
@@ -0,0 +1,67 @@
+package com.moataz.phood.identity.ui.viewmodel
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.moataz.phood.identity.domain.usecases.LoggedInStatus
+import com.moataz.phood.identity.domain.usecases.SignInUseCase
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.receiveAsFlow
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class LoginViewModel @Inject constructor(
+ private val signInUseCase: SignInUseCase,
+ private val loggedInStatusUseCase: LoggedInStatus,
+) : ViewModel() {
+
+ val email = MutableStateFlow("mohamed.salah@phood.com")
+ val password = MutableStateFlow("Brightskies")
+
+ private val _isEmailValid = MutableStateFlow(true)
+ val isEmailValid = _isEmailValid
+
+ private val _isPasswordValid = MutableStateFlow(true)
+ val isPasswordValid = _isPasswordValid
+
+ private val _isUserLoggedIn = Channel()
+ val isUserLoggedIn get() = _isUserLoggedIn.receiveAsFlow()
+
+ private val _isLoginFailed = Channel()
+ val isLoginFailed get() = _isLoginFailed.receiveAsFlow()
+
+ init {
+ checkIfUserLoggedIn()
+ }
+
+ private fun checkIfUserLoggedIn() {
+ viewModelScope.launch {
+ val isLoggedIn = loggedInStatusUseCase()
+ if (isLoggedIn) {
+ _isUserLoggedIn.send(true)
+ }
+ }
+ }
+
+ fun onLoginClicked() {
+ if (email.value.isNotEmpty() && password.value.isNotEmpty()) {
+ viewModelScope.launch {
+ val isUserLoggedIn = signInUseCase(email.value, password.value)
+ if (isUserLoggedIn) {
+ checkIfUserLoggedIn()
+ } else {
+ _isLoginFailed.send(true)
+ }
+ }
+ } else {
+ if (email.value.isEmpty()) {
+ _isEmailValid.value = false
+ }
+ if (password.value.isEmpty()) {
+ _isPasswordValid.value = false
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/moataz/phood/recipes/data/local/RecipesDao.kt b/app/src/main/java/com/moataz/phood/recipes/data/local/RecipesDao.kt
new file mode 100644
index 0000000..297c175
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/recipes/data/local/RecipesDao.kt
@@ -0,0 +1,26 @@
+package com.moataz.phood.recipes.data.local
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import com.moataz.phood.recipes.data.local.entity.RecipeEntity
+import kotlinx.coroutines.flow.Flow
+
+@Dao
+interface RecipesDao {
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun insertRecipe(vararg recipeEntity: RecipeEntity)
+
+ @Query("DELETE FROM RECIPE_TABLE")
+ suspend fun deleteRecipe()
+
+ @Query("SELECT * FROM RECIPE_TABLE WHERE recipeType = :recipeType")
+ fun getRecipesByCategory(recipeType: String): Flow>
+
+ @Query("SELECT * FROM RECIPE_TABLE")
+ fun getAllRecipes(): Flow>
+
+ @Query("SELECT * FROM RECIPE_TABLE")
+ suspend fun getRecipes(): List
+}
diff --git a/app/src/main/java/com/moataz/phood/recipes/data/local/RecipesDatabase.kt b/app/src/main/java/com/moataz/phood/recipes/data/local/RecipesDatabase.kt
new file mode 100644
index 0000000..3d12264
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/recipes/data/local/RecipesDatabase.kt
@@ -0,0 +1,16 @@
+package com.moataz.phood.recipes.data.local
+
+import androidx.room.Database
+import androidx.room.RoomDatabase
+import androidx.room.TypeConverters
+import com.moataz.phood.recipes.data.local.entity.RecipeEntity
+import com.moataz.phood.recipes.data.local.utils.Converters
+
+@Database(
+ entities = [RecipeEntity::class],
+ version = 1,
+)
+@TypeConverters(Converters::class)
+abstract class RecipesDatabase : RoomDatabase() {
+ abstract fun recipesDao(): RecipesDao
+}
diff --git a/app/src/main/java/com/moataz/phood/recipes/data/local/entity/RecipeEntity.kt b/app/src/main/java/com/moataz/phood/recipes/data/local/entity/RecipeEntity.kt
new file mode 100644
index 0000000..ebd10a9
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/recipes/data/local/entity/RecipeEntity.kt
@@ -0,0 +1,23 @@
+package com.moataz.phood.recipes.data.local.entity
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import com.moataz.phood.recipes.data.local.utils.EntityConstant.RECIPE_TABLE
+
+@Entity(tableName = RECIPE_TABLE)
+data class RecipeEntity(
+ @PrimaryKey
+ val id: String,
+ val name: String,
+ val image: String,
+ val difficulty: Int,
+ val headline: String,
+ val fats: String,
+ val carbos: String,
+ val calories: String,
+ val proteins: String,
+ val description: String,
+ val time: String,
+ val ingredients: List,
+ val recipeType: String,
+)
diff --git a/app/src/main/java/com/moataz/phood/recipes/data/local/utils/Converters.kt b/app/src/main/java/com/moataz/phood/recipes/data/local/utils/Converters.kt
new file mode 100644
index 0000000..a590cf5
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/recipes/data/local/utils/Converters.kt
@@ -0,0 +1,15 @@
+package com.moataz.phood.recipes.data.local.utils
+
+import androidx.room.TypeConverter
+
+class Converters {
+ @TypeConverter
+ fun fromList(list: List): String {
+ return list.joinToString(",")
+ }
+
+ @TypeConverter
+ fun toList(data: String): List {
+ return data.split(",")
+ }
+}
diff --git a/app/src/main/java/com/moataz/phood/recipes/data/local/utils/EntityConstant.kt b/app/src/main/java/com/moataz/phood/recipes/data/local/utils/EntityConstant.kt
new file mode 100644
index 0000000..01643da
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/recipes/data/local/utils/EntityConstant.kt
@@ -0,0 +1,5 @@
+package com.moataz.phood.recipes.data.local.utils
+
+object EntityConstant {
+ const val RECIPE_TABLE = "RECIPE_TABLE"
+}
diff --git a/app/src/main/java/com/moataz/phood/recipes/data/remote/RecipesService.kt b/app/src/main/java/com/moataz/phood/recipes/data/remote/RecipesService.kt
new file mode 100644
index 0000000..b8fe981
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/recipes/data/remote/RecipesService.kt
@@ -0,0 +1,9 @@
+package com.moataz.phood.recipes.data.remote
+
+import com.moataz.phood.recipes.data.remote.dto.RecipeDTO
+import retrofit2.http.GET
+
+interface RecipesService {
+ @GET("43427003d33f1f6b51cc")
+ suspend fun getRecipes(): List
+}
diff --git a/app/src/main/java/com/moataz/phood/recipes/data/remote/dto/RecipeDTO.kt b/app/src/main/java/com/moataz/phood/recipes/data/remote/dto/RecipeDTO.kt
new file mode 100644
index 0000000..e8ff248
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/recipes/data/remote/dto/RecipeDTO.kt
@@ -0,0 +1,32 @@
+package com.moataz.phood.recipes.data.remote.dto
+
+import com.google.gson.annotations.SerializedName
+
+data class RecipeDTO(
+ @SerializedName("id")
+ val id: String?,
+ @SerializedName("name")
+ val name: String?,
+ @SerializedName("image")
+ val image: String?,
+ @SerializedName("difficulty")
+ val difficulty: Int?,
+ @SerializedName("headline")
+ val headline: String?,
+ @SerializedName("fats")
+ val fats: String?,
+ @SerializedName("carbos")
+ val carbos: String?,
+ @SerializedName("calories")
+ val calories: String?,
+ @SerializedName("proteins")
+ val proteins: String?,
+ @SerializedName("description")
+ val description: String?,
+ @SerializedName("time")
+ val time: String?,
+ @SerializedName("ingredients")
+ val ingredients: List?,
+ @SerializedName("products")
+ val products: List?,
+)
diff --git a/app/src/main/java/com/moataz/phood/recipes/data/repositories/RecipesRepositoryImpl.kt b/app/src/main/java/com/moataz/phood/recipes/data/repositories/RecipesRepositoryImpl.kt
new file mode 100644
index 0000000..2a364db
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/recipes/data/repositories/RecipesRepositoryImpl.kt
@@ -0,0 +1,56 @@
+package com.moataz.phood.recipes.data.repositories
+
+import com.moataz.phood.recipes.data.local.RecipesDao
+import com.moataz.phood.recipes.data.remote.RecipesService
+import com.moataz.phood.recipes.data.repositories.mapper.toRecipeEntity
+import com.moataz.phood.recipes.data.repositories.mapper.toRecipesLocal
+import com.moataz.phood.recipes.data.repositories.mapper.toRecipesRemote
+import com.moataz.phood.recipes.data.repositories.utils.NetworkHelper
+import com.moataz.phood.recipes.domain.entities.Recipe
+import com.moataz.phood.recipes.domain.repository.RecipesRepository
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import javax.inject.Inject
+
+class RecipesRepositoryImpl @Inject constructor(
+ private val recipesRemoteService: RecipesService,
+ private val recipesLocalDatabase: RecipesDao,
+ private val networkHelper: NetworkHelper,
+) : RecipesRepository {
+
+ override suspend fun getRecipesFromRemote(): List {
+ return recipesRemoteService.getRecipes().toRecipesRemote()
+ }
+
+ override suspend fun insertRecipe() {
+ recipesRemoteService.getRecipes().map { recipeDTO ->
+ recipesLocalDatabase.insertRecipe(recipeDTO.toRecipeEntity())
+ }
+ }
+
+ override suspend fun deleteRecipe() {
+ recipesLocalDatabase.deleteRecipe()
+ }
+
+ override suspend fun cachingRecipes() {
+ if (recipesLocalDatabase.getRecipes().isEmpty()) {
+ if (networkHelper.isInternetAvailable()) {
+ getRecipesFromRemote()
+ deleteRecipe()
+ insertRecipe()
+ }
+ }
+ }
+
+ override fun getRecipesByCategoryFromLocal(recipeType: String): Flow> {
+ return recipesLocalDatabase.getRecipesByCategory(recipeType).map { recipesEntities ->
+ recipesEntities.toRecipesLocal()
+ }
+ }
+
+ override fun getAllRecipesTypesFromLocal(): Flow> {
+ return recipesLocalDatabase.getAllRecipes().map { recipesEntities ->
+ recipesEntities.toRecipesLocal()
+ }
+ }
+}
diff --git a/app/src/main/java/com/moataz/phood/recipes/data/repositories/mapper/RecipesMapper.kt b/app/src/main/java/com/moataz/phood/recipes/data/repositories/mapper/RecipesMapper.kt
new file mode 100644
index 0000000..6da5a40
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/recipes/data/repositories/mapper/RecipesMapper.kt
@@ -0,0 +1,67 @@
+package com.moataz.phood.recipes.data.repositories.mapper
+
+import com.moataz.phood.recipes.data.local.entity.RecipeEntity
+import com.moataz.phood.recipes.data.remote.dto.RecipeDTO
+import com.moataz.phood.recipes.domain.entities.Recipe
+
+internal fun List.toRecipesRemote(): List {
+ return map { recipeDTO -> recipeDTO.toRecipe() }
+}
+
+internal fun RecipeDTO.toRecipe(): Recipe {
+ return Recipe(
+ id = id ?: "",
+ name = name ?: "",
+ image = image ?: "",
+ difficulty = difficulty ?: 0,
+ headline = headline ?: "",
+ fats = fats ?: "",
+ carbos = carbos ?: "",
+ calories = calories ?: "",
+ proteins = proteins ?: "",
+ description = description ?: "",
+ time = time ?: "",
+ ingredients = ingredients ?: emptyList(),
+ products = products ?: emptyList(),
+ )
+}
+
+internal fun List.toRecipesLocal(): List {
+ return map { recipeDTO -> recipeDTO.toRecipe() }
+}
+
+internal fun RecipeEntity.toRecipe(): Recipe {
+ return Recipe(
+ id = id,
+ name = name,
+ image = image,
+ difficulty = difficulty,
+ headline = headline,
+ fats = fats,
+ carbos = carbos,
+ calories = calories,
+ proteins = proteins,
+ description = description,
+ time = time,
+ ingredients = ingredients,
+ products = listOf(recipeType),
+ )
+}
+
+internal fun RecipeDTO.toRecipeEntity(): RecipeEntity {
+ return RecipeEntity(
+ id = id ?: "",
+ name = name ?: "",
+ image = image ?: "",
+ difficulty = difficulty ?: 0,
+ headline = headline ?: "",
+ fats = fats ?: "",
+ carbos = carbos ?: "",
+ calories = calories ?: "",
+ proteins = proteins ?: "",
+ description = description ?: "",
+ time = time ?: "",
+ ingredients = ingredients ?: emptyList(),
+ recipeType = products?.first() ?: "",
+ )
+}
diff --git a/app/src/main/java/com/moataz/phood/recipes/data/repositories/utils/NetworkHelper.kt b/app/src/main/java/com/moataz/phood/recipes/data/repositories/utils/NetworkHelper.kt
new file mode 100644
index 0000000..ce730ab
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/recipes/data/repositories/utils/NetworkHelper.kt
@@ -0,0 +1,28 @@
+package com.moataz.phood.recipes.data.repositories.utils
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.NetworkCapabilities
+import android.os.Build
+import dagger.hilt.android.qualifiers.ApplicationContext
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class NetworkHelper @Inject constructor(@ApplicationContext private val context: Context) {
+ @SuppressLint("MissingPermission")
+ fun isInternetAvailable(): Boolean {
+ val connectivityManager =
+ context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ val network = connectivityManager.activeNetwork ?: return false
+ val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
+ return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ } else {
+ val networkInfo = connectivityManager.activeNetworkInfo
+ return networkInfo?.isConnectedOrConnecting == true
+ }
+ }
+}
diff --git a/app/src/main/java/com/moataz/phood/recipes/domain/entities/Recipe.kt b/app/src/main/java/com/moataz/phood/recipes/domain/entities/Recipe.kt
new file mode 100644
index 0000000..df45f06
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/recipes/domain/entities/Recipe.kt
@@ -0,0 +1,17 @@
+package com.moataz.phood.recipes.domain.entities
+
+data class Recipe(
+ val id: String,
+ val name: String,
+ val image: String,
+ val difficulty: Int,
+ val headline: String,
+ val fats: String,
+ val carbos: String,
+ val calories: String,
+ val proteins: String,
+ val description: String,
+ val time: String,
+ val ingredients: List,
+ val products: List,
+)
diff --git a/app/src/main/java/com/moataz/phood/recipes/domain/repository/RecipesRepository.kt b/app/src/main/java/com/moataz/phood/recipes/domain/repository/RecipesRepository.kt
new file mode 100644
index 0000000..246016e
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/recipes/domain/repository/RecipesRepository.kt
@@ -0,0 +1,13 @@
+package com.moataz.phood.recipes.domain.repository
+
+import com.moataz.phood.recipes.domain.entities.Recipe
+import kotlinx.coroutines.flow.Flow
+
+interface RecipesRepository {
+ suspend fun getRecipesFromRemote(): List
+ suspend fun insertRecipe()
+ suspend fun deleteRecipe()
+ suspend fun cachingRecipes()
+ fun getRecipesByCategoryFromLocal(recipeType: String): Flow>
+ fun getAllRecipesTypesFromLocal(): Flow>
+}
diff --git a/app/src/main/java/com/moataz/phood/recipes/domain/usecases/GetRecipesByCategoriesUseCase.kt b/app/src/main/java/com/moataz/phood/recipes/domain/usecases/GetRecipesByCategoriesUseCase.kt
new file mode 100644
index 0000000..8efce3b
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/recipes/domain/usecases/GetRecipesByCategoriesUseCase.kt
@@ -0,0 +1,23 @@
+package com.moataz.phood.recipes.domain.usecases
+
+import com.moataz.phood.recipes.domain.entities.Recipe
+import com.moataz.phood.recipes.domain.repository.RecipesRepository
+import com.moataz.phood.recipes.domain.usecases.enums.RecipesTypes
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import javax.inject.Inject
+
+class GetRecipesByCategoriesUseCase @Inject constructor(
+ private val recipesRepository: RecipesRepository,
+) {
+ suspend operator fun invoke(category: String): Flow> {
+ recipesRepository.cachingRecipes()
+ return if (category == RecipesTypes.ALL.pathName) {
+ recipesRepository.getAllRecipesTypesFromLocal()
+ } else {
+ recipesRepository.getRecipesByCategoryFromLocal(category).map { recipes ->
+ recipes.filter { recipe -> recipe.products.first() == category }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/moataz/phood/recipes/domain/usecases/GetRecipesUseCase.kt b/app/src/main/java/com/moataz/phood/recipes/domain/usecases/GetRecipesUseCase.kt
new file mode 100644
index 0000000..68febb7
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/recipes/domain/usecases/GetRecipesUseCase.kt
@@ -0,0 +1,13 @@
+package com.moataz.phood.recipes.domain.usecases
+
+import com.moataz.phood.recipes.domain.entities.Recipe
+import com.moataz.phood.recipes.domain.repository.RecipesRepository
+import javax.inject.Inject
+
+class GetRecipesUseCase @Inject constructor(
+ private val recipesRepository: RecipesRepository,
+) {
+// suspend operator fun invoke(): List {
+// return recipesRepository.getRecipesByCategory()
+// }
+}
diff --git a/app/src/main/java/com/moataz/phood/recipes/domain/usecases/enums/RecipesTypes.kt b/app/src/main/java/com/moataz/phood/recipes/domain/usecases/enums/RecipesTypes.kt
new file mode 100644
index 0000000..fe74b75
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/recipes/domain/usecases/enums/RecipesTypes.kt
@@ -0,0 +1,5 @@
+package com.moataz.phood.recipes.domain.usecases.enums
+
+enum class RecipesTypes(val pathName: String) {
+ ALL("all"),
+}
diff --git a/app/src/main/java/com/moataz/phood/recipes/ui/view/adapters/RecipesAdapter.kt b/app/src/main/java/com/moataz/phood/recipes/ui/view/adapters/RecipesAdapter.kt
new file mode 100644
index 0000000..433662c
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/recipes/ui/view/adapters/RecipesAdapter.kt
@@ -0,0 +1,48 @@
+package com.moataz.phood.recipes.ui.view.adapters
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.databinding.DataBindingUtil
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.RecyclerView
+import com.moataz.phood.R
+import com.moataz.phood.databinding.ItemRecipeBinding
+import com.moataz.phood.recipes.ui.viewmodel.model.RecipeUI
+
+class RecipesAdapter(
+ private var recipes: List,
+) : RecyclerView.Adapter() {
+
+ fun setItems(newItems: List) {
+ val diffUtilResult =
+ DiffUtil.calculateDiff(RecipesDiffUtil(recipes, newItems, ::areContentsTheSame))
+ recipes = newItems
+ diffUtilResult.dispatchUpdatesTo(this)
+ }
+
+ private fun areContentsTheSame(oldItem: RecipeUI, newItem: RecipeUI): Boolean {
+ return oldItem == newItem
+ }
+
+ override fun getItemCount(): Int = recipes.size
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecipesViewHolder {
+ return RecipesViewHolder(
+ DataBindingUtil.inflate(
+ LayoutInflater.from(parent.context),
+ R.layout.item_recipe,
+ parent,
+ false,
+ ),
+ )
+ }
+
+ override fun onBindViewHolder(holder: RecipesViewHolder, position: Int) {
+ holder.binding.run {
+ recipe = recipes[position]
+ }
+ }
+
+ inner class RecipesViewHolder(val binding: ItemRecipeBinding) :
+ RecyclerView.ViewHolder(binding.root)
+}
diff --git a/app/src/main/java/com/moataz/phood/recipes/ui/view/adapters/RecipesDiffUtil.kt b/app/src/main/java/com/moataz/phood/recipes/ui/view/adapters/RecipesDiffUtil.kt
new file mode 100644
index 0000000..1d5893f
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/recipes/ui/view/adapters/RecipesDiffUtil.kt
@@ -0,0 +1,27 @@
+package com.moataz.phood.recipes.ui.view.adapters
+
+import androidx.recyclerview.widget.DiffUtil
+import com.moataz.phood.recipes.ui.viewmodel.model.RecipeUI
+
+class RecipesDiffUtil(
+ private val oldList: List,
+ private val newList: List,
+ private val areContentsTheSame: (RecipeUI, RecipeUI) -> Boolean,
+) : DiffUtil.Callback() {
+
+ override fun getOldListSize(): Int {
+ return oldList.size
+ }
+
+ override fun getNewListSize(): Int {
+ return newList.size
+ }
+
+ override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
+ return oldList[oldItemPosition] == newList[newItemPosition]
+ }
+
+ override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
+ return areContentsTheSame(oldList[oldItemPosition], newList[newItemPosition])
+ }
+}
diff --git a/app/src/main/java/com/moataz/phood/recipes/ui/view/databinding/LoadImageDataBinding.kt b/app/src/main/java/com/moataz/phood/recipes/ui/view/databinding/LoadImageDataBinding.kt
new file mode 100644
index 0000000..8d24139
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/recipes/ui/view/databinding/LoadImageDataBinding.kt
@@ -0,0 +1,12 @@
+package com.moataz.phood.recipes.ui.view.databinding
+
+import android.view.View
+import android.widget.ImageView
+import androidx.databinding.BindingAdapter
+import coil.load
+
+@BindingAdapter("imageUrl")
+fun loadImage(view: View, imageUrl: String?) {
+ val image: ImageView = view as ImageView
+ image.load(imageUrl)
+}
diff --git a/app/src/main/java/com/moataz/phood/recipes/ui/view/databinding/ValidationDataBinding.kt b/app/src/main/java/com/moataz/phood/recipes/ui/view/databinding/ValidationDataBinding.kt
new file mode 100644
index 0000000..00adf71
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/recipes/ui/view/databinding/ValidationDataBinding.kt
@@ -0,0 +1,10 @@
+package com.moataz.phood.recipes.ui.view.databinding
+
+import android.view.View
+import androidx.core.view.isVisible
+import androidx.databinding.BindingAdapter
+
+@BindingAdapter("app:showIfTrue")
+fun showIfTrue(view: View, condition: Boolean) {
+ view.isVisible = condition
+}
diff --git a/app/src/main/java/com/moataz/phood/recipes/ui/view/screens/RecipesFragment.kt b/app/src/main/java/com/moataz/phood/recipes/ui/view/screens/RecipesFragment.kt
new file mode 100644
index 0000000..d978803
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/recipes/ui/view/screens/RecipesFragment.kt
@@ -0,0 +1,59 @@
+package com.moataz.phood.recipes.ui.view.screens
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.databinding.DataBindingUtil
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.lifecycleScope
+import com.moataz.phood.R
+import com.moataz.phood.databinding.FragmentRecipesBinding
+import com.moataz.phood.recipes.ui.view.adapters.RecipesAdapter
+import com.moataz.phood.recipes.ui.viewmodel.RecipesViewModel
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.launch
+
+@AndroidEntryPoint
+class RecipesFragment : Fragment() {
+ private val viewModel: RecipesViewModel by viewModels()
+ private lateinit var recipesAdapter: RecipesAdapter
+ private lateinit var binding: FragmentRecipesBinding
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View {
+ binding = DataBindingUtil.inflate(
+ inflater,
+ R.layout.fragment_recipes,
+ container,
+ false,
+ )
+ binding.lifecycleOwner = viewLifecycleOwner
+ binding.viewModel = viewModel
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ initRecyclerView()
+ observeEvents()
+ }
+
+ private fun initRecyclerView() {
+ recipesAdapter = RecipesAdapter(emptyList())
+ binding.recipesRecyclerView.adapter = recipesAdapter
+ }
+
+ private fun observeEvents() {
+ lifecycleScope.launch {
+ viewModel.recipesUiState.collect { recipesUIState ->
+ recipesAdapter.setItems(recipesUIState.recipes)
+ binding.recipesRecyclerView.scrollToPosition(0)
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/moataz/phood/recipes/ui/viewmodel/RecipesViewModel.kt b/app/src/main/java/com/moataz/phood/recipes/ui/viewmodel/RecipesViewModel.kt
new file mode 100644
index 0000000..02332ad
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/recipes/ui/viewmodel/RecipesViewModel.kt
@@ -0,0 +1,67 @@
+package com.moataz.phood.recipes.ui.viewmodel
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.moataz.phood.recipes.domain.usecases.GetRecipesByCategoriesUseCase
+import com.moataz.phood.recipes.ui.viewmodel.enums.RecipesTypes
+import com.moataz.phood.recipes.ui.viewmodel.mapper.toRecipesUIModel
+import com.moataz.phood.recipes.ui.viewmodel.model.RecipesUIState
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class RecipesViewModel @Inject constructor(
+ private val getRecipesUseCase: GetRecipesByCategoriesUseCase,
+) : ViewModel() {
+
+ private val _recipesUiState = MutableStateFlow(RecipesUIState())
+ val recipesUiState get() = _recipesUiState.asStateFlow()
+
+ private val currentRecipeType = MutableStateFlow(RecipesTypes.ALL)
+
+ init {
+ getRecipes()
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ private fun getRecipes() {
+ viewModelScope.launch {
+ try {
+ currentRecipeType.flatMapLatest { recipeType ->
+ getRecipesUseCase(recipeType.pathName)
+ }.collectLatest { recipesModel ->
+ _recipesUiState.update { recipesUiState ->
+ recipesUiState.copy(
+ isLoading = false,
+ isSuccessful = true,
+ isError = false,
+ recipes = recipesModel.toRecipesUIModel(),
+ )
+ }
+ }
+ } catch (e: Exception) {
+ _recipesUiState.update { recipesUiState ->
+ recipesUiState.copy(
+ isLoading = false,
+ isSuccessful = false,
+ isError = true,
+ recipes = emptyList(),
+ )
+ }
+ }
+ }
+ }
+
+ fun onChipTypeClicked(recipeType: RecipesTypes) {
+ if (recipeType != currentRecipeType.value) {
+ currentRecipeType.value = recipeType
+ }
+ }
+}
diff --git a/app/src/main/java/com/moataz/phood/recipes/ui/viewmodel/enums/RecipesTypes.kt b/app/src/main/java/com/moataz/phood/recipes/ui/viewmodel/enums/RecipesTypes.kt
new file mode 100644
index 0000000..022ae8e
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/recipes/ui/viewmodel/enums/RecipesTypes.kt
@@ -0,0 +1,8 @@
+package com.moataz.phood.recipes.ui.viewmodel.enums
+
+enum class RecipesTypes(val pathName: String) {
+ ALL("all"),
+ FAMILY_BOX("family-box"),
+ CLASSIC_BOX("classic-box"),
+ VEGGIE_BOX("veggie-box"),
+}
diff --git a/app/src/main/java/com/moataz/phood/recipes/ui/viewmodel/mapper/RecipesUIMapper.kt b/app/src/main/java/com/moataz/phood/recipes/ui/viewmodel/mapper/RecipesUIMapper.kt
new file mode 100644
index 0000000..f59f28d
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/recipes/ui/viewmodel/mapper/RecipesUIMapper.kt
@@ -0,0 +1,25 @@
+package com.moataz.phood.recipes.ui.viewmodel.mapper
+
+import com.moataz.phood.recipes.domain.entities.Recipe
+import com.moataz.phood.recipes.ui.viewmodel.model.RecipeUI
+
+internal fun Recipe.toRecipeUIModel(): RecipeUI {
+ return RecipeUI(
+ id = id,
+ name = name,
+ image = image,
+ difficulty = difficulty,
+ headline = headline,
+ fats = fats,
+ carbos = carbos,
+ calories = calories,
+ proteins = proteins,
+ description = description,
+ time = time,
+ ingredients = ingredients,
+ )
+}
+
+internal fun List.toRecipesUIModel(): List {
+ return map { recipeUI -> recipeUI.toRecipeUIModel() }
+}
diff --git a/app/src/main/java/com/moataz/phood/recipes/ui/viewmodel/model/RecipeUI.kt b/app/src/main/java/com/moataz/phood/recipes/ui/viewmodel/model/RecipeUI.kt
new file mode 100644
index 0000000..b99d40b
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/recipes/ui/viewmodel/model/RecipeUI.kt
@@ -0,0 +1,16 @@
+package com.moataz.phood.recipes.ui.viewmodel.model
+
+data class RecipeUI(
+ val id: String,
+ val name: String,
+ val image: String,
+ val difficulty: Int,
+ val headline: String,
+ val fats: String,
+ val carbos: String,
+ val calories: String,
+ val proteins: String,
+ val description: String,
+ val time: String,
+ val ingredients: List,
+)
diff --git a/app/src/main/java/com/moataz/phood/recipes/ui/viewmodel/model/RecipesUIState.kt b/app/src/main/java/com/moataz/phood/recipes/ui/viewmodel/model/RecipesUIState.kt
new file mode 100644
index 0000000..76bfed2
--- /dev/null
+++ b/app/src/main/java/com/moataz/phood/recipes/ui/viewmodel/model/RecipesUIState.kt
@@ -0,0 +1,8 @@
+package com.moataz.phood.recipes.ui.viewmodel.model
+
+data class RecipesUIState(
+ val isLoading: Boolean = true,
+ val isSuccessful: Boolean = false,
+ val isError: Boolean? = false,
+ val recipes: List = emptyList(),
+)
diff --git a/app/src/main/res/color/chip_background_color.xml b/app/src/main/res/color/chip_background_color.xml
new file mode 100644
index 0000000..464c0e7
--- /dev/null
+++ b/app/src/main/res/color/chip_background_color.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/color/chip_text_color.xml b/app/src/main/res/color/chip_text_color.xml
new file mode 100644
index 0000000..d0664a5
--- /dev/null
+++ b/app/src/main/res/color/chip_text_color.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/color/selector.xml b/app/src/main/res/color/selector.xml
new file mode 100644
index 0000000..43bc2ca
--- /dev/null
+++ b/app/src/main/res/color/selector.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
deleted file mode 100644
index 2b068d1..0000000
--- a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/drawable/circle_button_style.xml b/app/src/main/res/drawable/circle_button_style.xml
new file mode 100644
index 0000000..96e5bfd
--- /dev/null
+++ b/app/src/main/res/drawable/circle_button_style.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/error.png b/app/src/main/res/drawable/error.png
new file mode 100644
index 0000000..719dd1e
Binary files /dev/null and b/app/src/main/res/drawable/error.png differ
diff --git a/app/src/main/res/drawable/ic_favorite.xml b/app/src/main/res/drawable/ic_favorite.xml
new file mode 100644
index 0000000..4d470b9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_favorite.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..65e6e15
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_search.xml b/app/src/main/res/drawable/ic_search.xml
new file mode 100644
index 0000000..f317243
--- /dev/null
+++ b/app/src/main/res/drawable/ic_search.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/img_login_video_background.jpg b/app/src/main/res/drawable/img_login_video_background.jpg
new file mode 100644
index 0000000..cdd3263
Binary files /dev/null and b/app/src/main/res/drawable/img_login_video_background.jpg differ
diff --git a/app/src/main/res/drawable/img_recipe_fake.webp b/app/src/main/res/drawable/img_recipe_fake.webp
new file mode 100644
index 0000000..1f11141
Binary files /dev/null and b/app/src/main/res/drawable/img_recipe_fake.webp differ
diff --git a/app/src/main/res/drawable/img_shadow_background.png b/app/src/main/res/drawable/img_shadow_background.png
new file mode 100644
index 0000000..a818de0
Binary files /dev/null and b/app/src/main/res/drawable/img_shadow_background.png differ
diff --git a/app/src/main/res/drawable/shp_login_button_background.xml b/app/src/main/res/drawable/shp_login_button_background.xml
new file mode 100644
index 0000000..da12304
--- /dev/null
+++ b/app/src/main/res/drawable/shp_login_button_background.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/shp_search_style.xml b/app/src/main/res/drawable/shp_search_style.xml
new file mode 100644
index 0000000..faafa5f
--- /dev/null
+++ b/app/src/main/res/drawable/shp_search_style.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml
new file mode 100644
index 0000000..851565e
--- /dev/null
+++ b/app/src/main/res/layout/activity_login.xml
@@ -0,0 +1,143 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 17eab17..83ca552 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -1,18 +1,25 @@
-
+
-
+
-
\ No newline at end of file
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_recipes.xml b/app/src/main/res/layout/fragment_recipes.xml
new file mode 100644
index 0000000..cfe38e2
--- /dev/null
+++ b/app/src/main/res/layout/fragment_recipes.xml
@@ -0,0 +1,198 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_recipe.xml b/app/src/main/res/layout/item_recipe.xml
new file mode 100644
index 0000000..ee23036
--- /dev/null
+++ b/app/src/main/res/layout/item_recipe.xml
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
index 6f3b755..036d09b 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -1,6 +1,5 @@
-
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
index 6f3b755..036d09b 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -1,6 +1,5 @@
-
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
index c209e78..02b7196 100644
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.webp and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..16a866c
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
index b2dfe3d..c771c9e 100644
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
index 4f0f1d6..7167fd1 100644
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.webp and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..af0d69f
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
index 62b611d..50d5e10 100644
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
index 948a307..f44cfdd 100644
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..c320325
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
index 1b9a695..6337ee8 100644
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
index 28d4b77..f6ceaaf 100644
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..23ead43
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
index 9287f50..4e114ed 100644
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
index aa7d642..1a70c3f 100644
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..a1c7c71
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
index 9126ae3..c9d9c5a 100644
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/navigation/main_nav.xml b/app/src/main/res/navigation/main_nav.xml
new file mode 100644
index 0000000..4803ee7
--- /dev/null
+++ b/app/src/main/res/navigation/main_nav.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-night/strings.xml b/app/src/main/res/values-night/strings.xml
new file mode 100644
index 0000000..fecf007
--- /dev/null
+++ b/app/src/main/res/values-night/strings.xml
@@ -0,0 +1,23 @@
+
+
+ Phood: Recipes App
+ Explore the world of recipes
+ Password
+ Email
+ Login
+ shadow background
+ Please enter a valid email address
+ Please enter a valid password
+ Phood
+ Search for your favorites recipes
+ Recipes
+ All
+ Family Box
+ Classic Box
+ Veggie Box
+ Favourites Recipes Button
+ Search Recipes Button
+ Food Image
+ Something went wrong, please try again
+ Error Icon
+
\ No newline at end of file
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
index fb5f101..13e45bb 100644
--- a/app/src/main/res/values-night/themes.xml
+++ b/app/src/main/res/values-night/themes.xml
@@ -2,6 +2,6 @@
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index c8524cd..be402d6 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -1,5 +1,6 @@
- #FF000000
+ #E6000000
#FFFFFFFF
+ #E6000000
\ No newline at end of file
diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml
new file mode 100644
index 0000000..beab31f
--- /dev/null
+++ b/app/src/main/res/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+
+
+ #000000
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 839bf3b..c0b1f5b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,3 +1,24 @@
- Phood
+ Phood: Recipes App
+ Explore the world of recipes
+ Password
+ Email
+ Login
+ shadow background
+ Please enter a valid email address
+ Please enter a valid password
+
+ Hello blank fragment
+ Phood
+ Search for your favorites recipes
+ Recipes
+ All
+ Family Box
+ Classic Box
+ Veggie Box
+ Favourites Recipes Button
+ Search Recipes Button
+ Food Image
+ Something went wrong, please try again
+ Error Icon
\ No newline at end of file
diff --git a/app/src/main/res/values/style.xml b/app/src/main/res/values/style.xml
new file mode 100644
index 0000000..0f48e29
--- /dev/null
+++ b/app/src/main/res/values/style.xml
@@ -0,0 +1,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 92fe34b..d4e88d1 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -2,8 +2,17 @@
+
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index e31c929..31bf66c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,5 +1,30 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ ext {
+ hilt_version = '2.45'
+ media3_version = '1.1.0'
+ datastore_version = "1.1.0-alpha04"
+ sdp_version = '1.1.0'
+ ssp_version = '1.1.0'
+ mockito_version = '3.12.4'
+ retrofit_version = '2.9.0'
+ coil_version = "2.3.0"
+ room_version = "2.5.2"
+ }
+
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
plugins {
-id 'com.android.application' version '8.1.0' apply false
+ id 'com.android.application' version '8.1.0' apply false
id 'org.jetbrains.kotlin.android' version '1.8.0' apply false
+ id 'com.google.devtools.ksp' version '1.8.21-1.0.11' apply false
+ id 'com.google.dagger.hilt.android' version '2.45' apply false
+}
+
+tasks.register('clean', Delete) {
+ delete rootProject.buildDir
}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
index 3c5031e..11a9b24 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -20,4 +20,5 @@ kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
-android.nonTransitiveRClass=true
\ No newline at end of file
+android.nonTransitiveRClass=true
+android.defaults.buildfeatures.buildconfig=true