Merge pull request #41 from kirmanak/material-3
Start migration to material 3
This commit is contained in:
@@ -22,6 +22,9 @@ android {
|
|||||||
arguments += ["room.schemaLocation": "$projectDir/schemas".toString()]
|
arguments += ["room.schemaLocation": "$projectDir/schemas".toString()]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildConfigField "Boolean", "DEBUG_PICASSO", "false"
|
||||||
|
buildConfigField "Boolean", "LOG_NETWORK", "false"
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import dagger.hilt.InstallIn
|
|||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
import dagger.multibindings.IntoSet
|
import dagger.multibindings.IntoSet
|
||||||
|
import gq.kirmanak.mealient.BuildConfig
|
||||||
import leakcanary.LeakCanary
|
import leakcanary.LeakCanary
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.logging.HttpLoggingInterceptor
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
@@ -30,7 +31,10 @@ object DebugModule {
|
|||||||
@IntoSet
|
@IntoSet
|
||||||
fun provideLoggingInterceptor(): Interceptor {
|
fun provideLoggingInterceptor(): Interceptor {
|
||||||
val interceptor = HttpLoggingInterceptor { message -> Timber.tag("OkHttp").v(message) }
|
val interceptor = HttpLoggingInterceptor { message -> Timber.tag("OkHttp").v(message) }
|
||||||
interceptor.level = HttpLoggingInterceptor.Level.BODY
|
interceptor.level = when {
|
||||||
|
BuildConfig.LOG_NETWORK -> HttpLoggingInterceptor.Level.BODY
|
||||||
|
else -> HttpLoggingInterceptor.Level.BASIC
|
||||||
|
}
|
||||||
return interceptor
|
return interceptor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
android:roundIcon="@mipmap/ic_launcher"
|
android:roundIcon="@mipmap/ic_launcher"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
tools:ignore="UnusedAttribute"
|
tools:ignore="UnusedAttribute"
|
||||||
android:theme="@style/Theme.Mealient">
|
android:theme="@style/AppTheme">
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.activity.MainActivity"
|
android:name=".ui.activity.MainActivity"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import android.widget.ImageView
|
|||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import gq.kirmanak.mealient.R
|
import gq.kirmanak.mealient.R
|
||||||
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
||||||
import gq.kirmanak.mealient.data.recipes.RecipeImageLoader
|
import gq.kirmanak.mealient.ui.images.ImageLoader
|
||||||
import gq.kirmanak.mealient.ui.ImageLoader
|
import gq.kirmanak.mealient.ui.recipes.RecipeImageLoader
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
|||||||
import gq.kirmanak.mealient.data.network.RetrofitBuilder
|
import gq.kirmanak.mealient.data.network.RetrofitBuilder
|
||||||
import gq.kirmanak.mealient.data.network.ServiceFactory
|
import gq.kirmanak.mealient.data.network.ServiceFactory
|
||||||
import gq.kirmanak.mealient.data.network.createServiceFactory
|
import gq.kirmanak.mealient.data.network.createServiceFactory
|
||||||
import gq.kirmanak.mealient.data.recipes.RecipeImageLoader
|
|
||||||
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
||||||
import gq.kirmanak.mealient.data.recipes.db.RecipeStorage
|
import gq.kirmanak.mealient.data.recipes.db.RecipeStorage
|
||||||
import gq.kirmanak.mealient.data.recipes.db.RecipeStorageImpl
|
import gq.kirmanak.mealient.data.recipes.db.RecipeStorageImpl
|
||||||
@@ -19,6 +18,7 @@ import gq.kirmanak.mealient.data.recipes.impl.RecipeRepoImpl
|
|||||||
import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource
|
import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource
|
||||||
import gq.kirmanak.mealient.data.recipes.network.RecipeDataSourceImpl
|
import gq.kirmanak.mealient.data.recipes.network.RecipeDataSourceImpl
|
||||||
import gq.kirmanak.mealient.data.recipes.network.RecipeService
|
import gq.kirmanak.mealient.data.recipes.network.RecipeService
|
||||||
|
import gq.kirmanak.mealient.ui.recipes.RecipeImageLoader
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import javax.inject.Named
|
import javax.inject.Named
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import dagger.Module
|
|||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
import gq.kirmanak.mealient.ui.ImageLoader
|
import gq.kirmanak.mealient.ui.images.ImageLoader
|
||||||
import gq.kirmanak.mealient.ui.picasso.ImageLoaderPicasso
|
import gq.kirmanak.mealient.ui.images.ImageLoaderPicasso
|
||||||
import gq.kirmanak.mealient.ui.picasso.PicassoBuilder
|
import gq.kirmanak.mealient.ui.images.PicassoBuilder
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
|
|||||||
@@ -12,16 +12,14 @@ import com.google.android.material.shape.MaterialShapeDrawable
|
|||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import gq.kirmanak.mealient.R
|
import gq.kirmanak.mealient.R
|
||||||
import gq.kirmanak.mealient.databinding.MainActivityBinding
|
import gq.kirmanak.mealient.databinding.MainActivityBinding
|
||||||
import gq.kirmanak.mealient.ui.auth.AuthenticationState
|
|
||||||
import gq.kirmanak.mealient.ui.auth.AuthenticationState.AUTHORIZED
|
|
||||||
import gq.kirmanak.mealient.ui.auth.AuthenticationState.UNAUTHORIZED
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
private lateinit var binding: MainActivityBinding
|
private lateinit var binding: MainActivityBinding
|
||||||
private val viewModel by viewModels<MainActivityViewModel>()
|
private val viewModel by viewModels<MainActivityViewModel>()
|
||||||
private var lastAuthenticationState: AuthenticationState? = null
|
private val title: String by lazy { getString(R.string.app_name) }
|
||||||
|
private val uiState: MainActivityUiState get() = viewModel.uiState
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -31,7 +29,13 @@ class MainActivity : AppCompatActivity() {
|
|||||||
setSupportActionBar(binding.toolbar)
|
setSupportActionBar(binding.toolbar)
|
||||||
supportActionBar?.setIcon(R.drawable.ic_toolbar)
|
supportActionBar?.setIcon(R.drawable.ic_toolbar)
|
||||||
setToolbarRoundCorner()
|
setToolbarRoundCorner()
|
||||||
listenToAuthStatuses()
|
viewModel.uiStateLive.observe(this, ::onUiStateChange)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onUiStateChange(uiState: MainActivityUiState) {
|
||||||
|
Timber.v("onUiStateChange() called with: uiState = $uiState")
|
||||||
|
supportActionBar?.title = if (uiState.titleVisible) title else null
|
||||||
|
invalidateOptionsMenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setToolbarRoundCorner() {
|
private fun setToolbarRoundCorner() {
|
||||||
@@ -51,22 +55,11 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun listenToAuthStatuses() {
|
|
||||||
Timber.v("listenToAuthStatuses() called")
|
|
||||||
viewModel.authenticationStateLive.observe(this, ::onAuthStateUpdate)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onAuthStateUpdate(authState: AuthenticationState) {
|
|
||||||
Timber.v("onAuthStateUpdate() called with: it = $authState")
|
|
||||||
lastAuthenticationState = authState
|
|
||||||
invalidateOptionsMenu()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
Timber.v("onCreateOptionsMenu() called with: menu = $menu")
|
Timber.v("onCreateOptionsMenu() called with: menu = $menu")
|
||||||
menuInflater.inflate(R.menu.main_toolbar, menu)
|
menuInflater.inflate(R.menu.main_toolbar, menu)
|
||||||
menu.findItem(R.id.logout).isVisible = lastAuthenticationState == AUTHORIZED
|
menu.findItem(R.id.logout).isVisible = uiState.canShowLogout
|
||||||
menu.findItem(R.id.login).isVisible = lastAuthenticationState == UNAUTHORIZED
|
menu.findItem(R.id.login).isVisible = uiState.canShowLogin
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package gq.kirmanak.mealient.ui.activity
|
||||||
|
|
||||||
|
data class MainActivityUiState(
|
||||||
|
val loginButtonVisible: Boolean = false,
|
||||||
|
val titleVisible: Boolean = true,
|
||||||
|
val isAuthorized: Boolean = false,
|
||||||
|
) {
|
||||||
|
val canShowLogin: Boolean
|
||||||
|
get() = !isAuthorized && loginButtonVisible
|
||||||
|
|
||||||
|
val canShowLogout: Boolean
|
||||||
|
get() = isAuthorized && loginButtonVisible
|
||||||
|
}
|
||||||
@@ -1,14 +1,10 @@
|
|||||||
package gq.kirmanak.mealient.ui.activity
|
package gq.kirmanak.mealient.ui.activity
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.*
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.asLiveData
|
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import gq.kirmanak.mealient.data.auth.AuthRepo
|
import gq.kirmanak.mealient.data.auth.AuthRepo
|
||||||
import gq.kirmanak.mealient.ui.auth.AuthenticationState
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.combine
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -18,16 +14,22 @@ class MainActivityViewModel @Inject constructor(
|
|||||||
private val authRepo: AuthRepo,
|
private val authRepo: AuthRepo,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val showLoginButtonFlow = MutableStateFlow(false)
|
private val _uiState = MutableLiveData(MainActivityUiState())
|
||||||
var showLoginButton: Boolean by showLoginButtonFlow::value
|
val uiStateLive: LiveData<MainActivityUiState>
|
||||||
|
get() = _uiState.distinctUntilChanged()
|
||||||
|
var uiState: MainActivityUiState
|
||||||
|
get() = checkNotNull(_uiState.value) { "UiState must not be null" }
|
||||||
|
private set(value) = _uiState.postValue(value)
|
||||||
|
|
||||||
private val authenticationStateFlow = combine(
|
init {
|
||||||
showLoginButtonFlow,
|
authRepo.isAuthorizedFlow
|
||||||
authRepo.isAuthorizedFlow,
|
.onEach { isAuthorized -> updateUiState { it.copy(isAuthorized = isAuthorized) } }
|
||||||
AuthenticationState::determineState
|
.launchIn(viewModelScope)
|
||||||
)
|
}
|
||||||
val authenticationStateLive: LiveData<AuthenticationState>
|
|
||||||
get() = authenticationStateFlow.asLiveData()
|
fun updateUiState(updater: (MainActivityUiState) -> MainActivityUiState) {
|
||||||
|
uiState = updater(uiState)
|
||||||
|
}
|
||||||
|
|
||||||
fun logout() {
|
fun logout() {
|
||||||
Timber.v("logout() called")
|
Timber.v("logout() called")
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ package gq.kirmanak.mealient.ui.auth
|
|||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||||
@@ -12,19 +12,20 @@ import gq.kirmanak.mealient.R
|
|||||||
import gq.kirmanak.mealient.data.network.NetworkError
|
import gq.kirmanak.mealient.data.network.NetworkError
|
||||||
import gq.kirmanak.mealient.databinding.FragmentAuthenticationBinding
|
import gq.kirmanak.mealient.databinding.FragmentAuthenticationBinding
|
||||||
import gq.kirmanak.mealient.extensions.checkIfInputIsEmpty
|
import gq.kirmanak.mealient.extensions.checkIfInputIsEmpty
|
||||||
|
import gq.kirmanak.mealient.ui.activity.MainActivityViewModel
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class AuthenticationFragment : Fragment(R.layout.fragment_authentication) {
|
class AuthenticationFragment : Fragment(R.layout.fragment_authentication) {
|
||||||
private val binding by viewBinding(FragmentAuthenticationBinding::bind)
|
private val binding by viewBinding(FragmentAuthenticationBinding::bind)
|
||||||
private val viewModel by viewModels<AuthenticationViewModel>()
|
private val viewModel by viewModels<AuthenticationViewModel>()
|
||||||
|
private val activityViewModel by activityViewModels<MainActivityViewModel>()
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
Timber.v("onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState")
|
Timber.v("onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState")
|
||||||
binding.button.setOnClickListener { onLoginClicked() }
|
binding.button.setOnClickListener { onLoginClicked() }
|
||||||
(requireActivity() as? AppCompatActivity)?.supportActionBar?.title =
|
activityViewModel.updateUiState { it.copy(loginButtonVisible = false, titleVisible = true) }
|
||||||
getString(R.string.app_name)
|
|
||||||
viewModel.authenticationResult.observe(viewLifecycleOwner, ::onAuthenticationResult)
|
viewModel.authenticationResult.observe(viewLifecycleOwner, ::onAuthenticationResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package gq.kirmanak.mealient.ui.baseurl
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||||
@@ -11,6 +12,7 @@ import gq.kirmanak.mealient.R
|
|||||||
import gq.kirmanak.mealient.data.network.NetworkError
|
import gq.kirmanak.mealient.data.network.NetworkError
|
||||||
import gq.kirmanak.mealient.databinding.FragmentBaseUrlBinding
|
import gq.kirmanak.mealient.databinding.FragmentBaseUrlBinding
|
||||||
import gq.kirmanak.mealient.extensions.checkIfInputIsEmpty
|
import gq.kirmanak.mealient.extensions.checkIfInputIsEmpty
|
||||||
|
import gq.kirmanak.mealient.ui.activity.MainActivityViewModel
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
@@ -18,12 +20,14 @@ class BaseURLFragment : Fragment(R.layout.fragment_base_url) {
|
|||||||
|
|
||||||
private val binding by viewBinding(FragmentBaseUrlBinding::bind)
|
private val binding by viewBinding(FragmentBaseUrlBinding::bind)
|
||||||
private val viewModel by viewModels<BaseURLViewModel>()
|
private val viewModel by viewModels<BaseURLViewModel>()
|
||||||
|
private val activityViewModel by activityViewModels<MainActivityViewModel>()
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
Timber.v("onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState")
|
Timber.v("onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState")
|
||||||
binding.button.setOnClickListener(::onProceedClick)
|
binding.button.setOnClickListener(::onProceedClick)
|
||||||
viewModel.checkURLResult.observe(viewLifecycleOwner, ::onCheckURLResult)
|
viewModel.checkURLResult.observe(viewLifecycleOwner, ::onCheckURLResult)
|
||||||
|
activityViewModel.updateUiState { it.copy(loginButtonVisible = false, titleVisible = true) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onProceedClick(view: View) {
|
private fun onProceedClick(view: View) {
|
||||||
|
|||||||
@@ -2,20 +2,22 @@ package gq.kirmanak.mealient.ui.disclaimer
|
|||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import gq.kirmanak.mealient.R
|
import gq.kirmanak.mealient.R
|
||||||
import gq.kirmanak.mealient.databinding.FragmentDisclaimerBinding
|
import gq.kirmanak.mealient.databinding.FragmentDisclaimerBinding
|
||||||
|
import gq.kirmanak.mealient.ui.activity.MainActivityViewModel
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class DisclaimerFragment : Fragment(R.layout.fragment_disclaimer) {
|
class DisclaimerFragment : Fragment(R.layout.fragment_disclaimer) {
|
||||||
private val binding by viewBinding(FragmentDisclaimerBinding::bind)
|
private val binding by viewBinding(FragmentDisclaimerBinding::bind)
|
||||||
private val viewModel by viewModels<DisclaimerViewModel>()
|
private val viewModel by viewModels<DisclaimerViewModel>()
|
||||||
|
private val activityViewModel by activityViewModels<MainActivityViewModel>()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -48,7 +50,6 @@ class DisclaimerFragment : Fragment(R.layout.fragment_disclaimer) {
|
|||||||
binding.okay.isClickable = it == 0
|
binding.okay.isClickable = it == 0
|
||||||
}
|
}
|
||||||
viewModel.startCountDown()
|
viewModel.startCountDown()
|
||||||
(requireActivity() as? AppCompatActivity)?.supportActionBar?.title =
|
activityViewModel.updateUiState { it.copy(loginButtonVisible = false, titleVisible = true) }
|
||||||
getString(R.string.app_name)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package gq.kirmanak.mealient.ui
|
package gq.kirmanak.mealient.ui.images
|
||||||
|
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
package gq.kirmanak.mealient.ui.picasso
|
package gq.kirmanak.mealient.ui.images
|
||||||
|
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import com.squareup.picasso.Picasso
|
import com.squareup.picasso.Picasso
|
||||||
import gq.kirmanak.mealient.ui.ImageLoader
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package gq.kirmanak.mealient.ui.picasso
|
package gq.kirmanak.mealient.ui.images
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.squareup.picasso.OkHttp3Downloader
|
import com.squareup.picasso.OkHttp3Downloader
|
||||||
@@ -22,7 +22,7 @@ class PicassoBuilder @Inject constructor(
|
|||||||
Timber.v("buildPicasso() called")
|
Timber.v("buildPicasso() called")
|
||||||
val builder = Picasso.Builder(context)
|
val builder = Picasso.Builder(context)
|
||||||
builder.downloader(OkHttp3Downloader(okHttpClient))
|
builder.downloader(OkHttp3Downloader(okHttpClient))
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG_PICASSO) {
|
||||||
builder.loggingEnabled(true)
|
builder.loggingEnabled(true)
|
||||||
builder.indicatorsEnabled(true)
|
builder.indicatorsEnabled(true)
|
||||||
builder.listener { _, uri, exception ->
|
builder.listener { _, uri, exception ->
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package gq.kirmanak.mealient.data.recipes
|
package gq.kirmanak.mealient.ui.recipes
|
||||||
|
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
|
|
||||||
@@ -5,7 +5,6 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.paging.cachedIn
|
import androidx.paging.cachedIn
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import gq.kirmanak.mealient.data.recipes.RecipeImageLoader
|
|
||||||
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
||||||
import gq.kirmanak.mealient.data.recipes.db.entity.RecipeSummaryEntity
|
import gq.kirmanak.mealient.data.recipes.db.entity.RecipeSummaryEntity
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package gq.kirmanak.mealient.ui.recipes
|
|||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
@@ -26,9 +25,8 @@ class RecipesFragment : Fragment(R.layout.fragment_recipes) {
|
|||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
Timber.v("onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState")
|
Timber.v("onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState")
|
||||||
activityViewModel.showLoginButton = true
|
activityViewModel.updateUiState { it.copy(loginButtonVisible = true, titleVisible = false) }
|
||||||
setupRecipeAdapter()
|
setupRecipeAdapter()
|
||||||
(requireActivity() as? AppCompatActivity)?.supportActionBar?.title = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun navigateToRecipeInfo(recipeSummaryEntity: RecipeSummaryEntity) {
|
private fun navigateToRecipeInfo(recipeSummaryEntity: RecipeSummaryEntity) {
|
||||||
@@ -64,6 +62,5 @@ class RecipesFragment : Fragment(R.layout.fragment_recipes) {
|
|||||||
Timber.v("onDestroyView() called")
|
Timber.v("onDestroyView() called")
|
||||||
// Prevent RV leaking through mObservers list in adapter
|
// Prevent RV leaking through mObservers list in adapter
|
||||||
binding.recipes.adapter = null
|
binding.recipes.adapter = null
|
||||||
activityViewModel.showLoginButton = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15,19 +15,15 @@ import dagger.hilt.android.AndroidEntryPoint
|
|||||||
import gq.kirmanak.mealient.R
|
import gq.kirmanak.mealient.R
|
||||||
import gq.kirmanak.mealient.databinding.FragmentRecipeInfoBinding
|
import gq.kirmanak.mealient.databinding.FragmentRecipeInfoBinding
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class RecipeInfoFragment : BottomSheetDialogFragment() {
|
class RecipeInfoFragment : BottomSheetDialogFragment() {
|
||||||
|
|
||||||
private val binding by viewBinding(FragmentRecipeInfoBinding::bind)
|
private val binding by viewBinding(FragmentRecipeInfoBinding::bind)
|
||||||
private val arguments by navArgs<RecipeInfoFragmentArgs>()
|
private val arguments by navArgs<RecipeInfoFragmentArgs>()
|
||||||
private val viewModel by viewModels<RecipeInfoViewModel>()
|
private val viewModel by viewModels<RecipeInfoViewModel>()
|
||||||
|
private val ingredientsAdapter = RecipeIngredientsAdapter()
|
||||||
@Inject
|
private val instructionsAdapter = RecipeInstructionsAdapter()
|
||||||
lateinit var ingredientsAdapter: RecipeIngredientsAdapter
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var instructionsAdapter: RecipeInstructionsAdapter
|
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
@@ -42,22 +38,27 @@ class RecipeInfoFragment : BottomSheetDialogFragment() {
|
|||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
Timber.v("onViewCreated() called")
|
Timber.v("onViewCreated() called")
|
||||||
|
|
||||||
binding.ingredientsList.adapter = ingredientsAdapter
|
with(binding) {
|
||||||
binding.instructionsList.adapter = instructionsAdapter
|
ingredientsList.adapter = ingredientsAdapter
|
||||||
|
instructionsList.adapter = instructionsAdapter
|
||||||
viewModel.loadRecipeImage(binding.image, arguments.recipeSlug)
|
|
||||||
viewModel.loadRecipeInfo(arguments.recipeId, arguments.recipeSlug)
|
|
||||||
|
|
||||||
viewModel.recipeInfo.observe(viewLifecycleOwner) {
|
|
||||||
Timber.d("onViewCreated: full info $it")
|
|
||||||
binding.title.text = it.recipeSummaryEntity.name
|
|
||||||
binding.description.text = it.recipeSummaryEntity.description
|
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.listsVisibility.observe(viewLifecycleOwner) {
|
with(viewModel) {
|
||||||
Timber.d("onViewCreated: lists visibility $it")
|
loadRecipeImage(binding.image, arguments.recipeSlug)
|
||||||
binding.ingredientsHolder.isVisible = it.areIngredientsVisible
|
loadRecipeInfo(arguments.recipeId, arguments.recipeSlug)
|
||||||
binding.instructionsGroup.isVisible = it.areInstructionsVisible
|
uiState.observe(viewLifecycleOwner, ::onUiStateChange)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onUiStateChange(uiState: RecipeInfoUiState) = with(binding) {
|
||||||
|
Timber.v("onUiStateChange() called")
|
||||||
|
ingredientsHolder.isVisible = uiState.areIngredientsVisible
|
||||||
|
instructionsGroup.isVisible = uiState.areInstructionsVisible
|
||||||
|
uiState.recipeInfo?.let {
|
||||||
|
title.text = it.recipeSummaryEntity.name
|
||||||
|
description.text = it.recipeSummaryEntity.description
|
||||||
|
ingredientsAdapter.submitList(it.recipeIngredients)
|
||||||
|
instructionsAdapter.submitList(it.recipeInstructions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package gq.kirmanak.mealient.ui.recipes.info
|
package gq.kirmanak.mealient.ui.recipes.info
|
||||||
|
|
||||||
data class RecipeInfoListsVisibility(
|
import gq.kirmanak.mealient.data.recipes.impl.FullRecipeInfo
|
||||||
|
|
||||||
|
data class RecipeInfoUiState(
|
||||||
val areIngredientsVisible: Boolean = false,
|
val areIngredientsVisible: Boolean = false,
|
||||||
val areInstructionsVisible: Boolean = false,
|
val areInstructionsVisible: Boolean = false,
|
||||||
|
val recipeInfo: FullRecipeInfo? = null,
|
||||||
)
|
)
|
||||||
@@ -6,30 +6,21 @@ import androidx.lifecycle.MutableLiveData
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import gq.kirmanak.mealient.data.recipes.RecipeImageLoader
|
|
||||||
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
||||||
import gq.kirmanak.mealient.data.recipes.impl.FullRecipeInfo
|
|
||||||
import gq.kirmanak.mealient.extensions.runCatchingExceptCancel
|
import gq.kirmanak.mealient.extensions.runCatchingExceptCancel
|
||||||
|
import gq.kirmanak.mealient.ui.recipes.RecipeImageLoader
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class RecipeInfoViewModel
|
class RecipeInfoViewModel @Inject constructor(
|
||||||
@Inject
|
|
||||||
constructor(
|
|
||||||
private val recipeRepo: RecipeRepo,
|
private val recipeRepo: RecipeRepo,
|
||||||
private val recipeImageLoader: RecipeImageLoader,
|
private val recipeImageLoader: RecipeImageLoader,
|
||||||
private val recipeIngredientsAdapter: RecipeIngredientsAdapter,
|
|
||||||
private val recipeInstructionsAdapter: RecipeInstructionsAdapter,
|
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _recipeInfo = MutableLiveData<FullRecipeInfo>()
|
private val _uiState = MutableLiveData(RecipeInfoUiState())
|
||||||
val recipeInfo: LiveData<FullRecipeInfo>
|
val uiState: LiveData<RecipeInfoUiState> get() = _uiState
|
||||||
get() = _recipeInfo
|
|
||||||
private val _listsVisibility = MutableLiveData(RecipeInfoListsVisibility())
|
|
||||||
val listsVisibility: LiveData<RecipeInfoListsVisibility>
|
|
||||||
get() = _listsVisibility
|
|
||||||
|
|
||||||
fun loadRecipeImage(view: ImageView, recipeSlug: String) {
|
fun loadRecipeImage(view: ImageView, recipeSlug: String) {
|
||||||
Timber.v("loadRecipeImage() called with: view = $view, recipeSlug = $recipeSlug")
|
Timber.v("loadRecipeImage() called with: view = $view, recipeSlug = $recipeSlug")
|
||||||
@@ -38,21 +29,16 @@ constructor(
|
|||||||
|
|
||||||
fun loadRecipeInfo(recipeId: Long, recipeSlug: String) {
|
fun loadRecipeInfo(recipeId: Long, recipeSlug: String) {
|
||||||
Timber.v("loadRecipeInfo() called with: recipeId = $recipeId, recipeSlug = $recipeSlug")
|
Timber.v("loadRecipeInfo() called with: recipeId = $recipeId, recipeSlug = $recipeSlug")
|
||||||
_listsVisibility.value = RecipeInfoListsVisibility()
|
_uiState.value = RecipeInfoUiState()
|
||||||
recipeIngredientsAdapter.submitList(null)
|
|
||||||
recipeInstructionsAdapter.submitList(null)
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
runCatchingExceptCancel { recipeRepo.loadRecipeInfo(recipeId, recipeSlug) }
|
runCatchingExceptCancel { recipeRepo.loadRecipeInfo(recipeId, recipeSlug) }
|
||||||
.onSuccess {
|
.onSuccess {
|
||||||
Timber.d("loadRecipeInfo: received recipe info = $it")
|
Timber.d("loadRecipeInfo: received recipe info = $it")
|
||||||
_recipeInfo.value = it
|
_uiState.value = RecipeInfoUiState(
|
||||||
recipeIngredientsAdapter.submitList(it.recipeIngredients)
|
areIngredientsVisible = it.recipeIngredients.isNotEmpty(),
|
||||||
recipeInstructionsAdapter.submitList(it.recipeInstructions)
|
areInstructionsVisible = it.recipeInstructions.isNotEmpty(),
|
||||||
_listsVisibility.value =
|
recipeInfo = it,
|
||||||
RecipeInfoListsVisibility(
|
)
|
||||||
areIngredientsVisible = it.recipeIngredients.isNotEmpty(),
|
|
||||||
areInstructionsVisible = it.recipeInstructions.isNotEmpty()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
.onFailure { Timber.e(it, "loadRecipeInfo: can't load recipe info") }
|
.onFailure { Timber.e(it, "loadRecipeInfo: can't load recipe info") }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,11 +9,8 @@ import gq.kirmanak.mealient.data.recipes.db.entity.RecipeIngredientEntity
|
|||||||
import gq.kirmanak.mealient.databinding.ViewHolderIngredientBinding
|
import gq.kirmanak.mealient.databinding.ViewHolderIngredientBinding
|
||||||
import gq.kirmanak.mealient.ui.recipes.info.RecipeIngredientsAdapter.RecipeIngredientViewHolder
|
import gq.kirmanak.mealient.ui.recipes.info.RecipeIngredientsAdapter.RecipeIngredientViewHolder
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Singleton
|
class RecipeIngredientsAdapter :
|
||||||
class RecipeIngredientsAdapter @Inject constructor() :
|
|
||||||
ListAdapter<RecipeIngredientEntity, RecipeIngredientViewHolder>(RecipeIngredientDiffCallback) {
|
ListAdapter<RecipeIngredientEntity, RecipeIngredientViewHolder>(RecipeIngredientDiffCallback) {
|
||||||
|
|
||||||
class RecipeIngredientViewHolder(
|
class RecipeIngredientViewHolder(
|
||||||
|
|||||||
@@ -10,11 +10,8 @@ import gq.kirmanak.mealient.data.recipes.db.entity.RecipeInstructionEntity
|
|||||||
import gq.kirmanak.mealient.databinding.ViewHolderInstructionBinding
|
import gq.kirmanak.mealient.databinding.ViewHolderInstructionBinding
|
||||||
import gq.kirmanak.mealient.ui.recipes.info.RecipeInstructionsAdapter.RecipeInstructionViewHolder
|
import gq.kirmanak.mealient.ui.recipes.info.RecipeInstructionsAdapter.RecipeInstructionViewHolder
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Singleton
|
class RecipeInstructionsAdapter :
|
||||||
class RecipeInstructionsAdapter @Inject constructor() :
|
|
||||||
ListAdapter<RecipeInstructionEntity, RecipeInstructionViewHolder>(RecipeInstructionDiffCallback) {
|
ListAdapter<RecipeInstructionEntity, RecipeInstructionViewHolder>(RecipeInstructionDiffCallback) {
|
||||||
|
|
||||||
private object RecipeInstructionDiffCallback :
|
private object RecipeInstructionDiffCallback :
|
||||||
|
|||||||
@@ -23,12 +23,11 @@ class SplashViewModel @Inject constructor(
|
|||||||
init {
|
init {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
delay(1000)
|
delay(1000)
|
||||||
_nextDestination.value = if (!disclaimerStorage.isDisclaimerAccepted())
|
_nextDestination.value = when {
|
||||||
SplashFragmentDirections.actionSplashFragmentToDisclaimerFragment()
|
!disclaimerStorage.isDisclaimerAccepted() -> SplashFragmentDirections.actionSplashFragmentToDisclaimerFragment()
|
||||||
else if (baseURLStorage.getBaseURL() == null)
|
baseURLStorage.getBaseURL() == null -> SplashFragmentDirections.actionSplashFragmentToBaseURLFragment()
|
||||||
SplashFragmentDirections.actionSplashFragmentToBaseURLFragment()
|
else -> SplashFragmentDirections.actionSplashFragmentToRecipesFragment()
|
||||||
else
|
}
|
||||||
SplashFragmentDirections.actionSplashFragmentToRecipesFragment()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,11 +42,11 @@
|
|||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/button"
|
android:id="@+id/button"
|
||||||
android:text="@string/fragment_authentication_button_login"
|
style="@style/SmallMarginButton"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
android:text="@string/fragment_authentication_button_login"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
style="@style/SmallMarginButton"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/password_input_layout" />
|
app:layout_constraintTop_toBottomOf="@+id/password_input_layout" />
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -8,20 +8,16 @@
|
|||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
<com.google.android.material.card.MaterialCardView
|
||||||
android:id="@+id/main_text_holder"
|
android:id="@+id/main_text_holder"
|
||||||
style="@style/Widget.MaterialComponents.CardView"
|
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginHorizontal="20dp"
|
android:layout_marginHorizontal="20dp"
|
||||||
android:layout_marginTop="40dp"
|
android:layout_marginTop="40dp"
|
||||||
android:elevation="0dp"
|
app:cardElevation="8dp"
|
||||||
app:cardCornerRadius="@dimen/rounded_corner_size_default"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:cardForegroundColor="#26C4C4C4"
|
|
||||||
app:layout_constraintBottom_toTopOf="@+id/okay"
|
app:layout_constraintBottom_toTopOf="@+id/okay"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:shapeAppearance="@style/ShapeAppearance.AllCornersRounded">
|
||||||
app:strokeColor="#FFDCC8BF"
|
|
||||||
app:strokeWidth="1dp">
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/main_text"
|
android:id="@+id/main_text"
|
||||||
@@ -36,11 +32,9 @@
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/okay"
|
android:id="@+id/okay"
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="@dimen/margin_small"
|
|
||||||
android:clickable="false"
|
android:clickable="false"
|
||||||
tools:text="Okay (3 seconds)"
|
tools:text="Okay (3 seconds)"
|
||||||
|
style="@style/SmallMarginButton"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/main_text_holder" />
|
app:layout_constraintTop_toBottomOf="@+id/main_text_holder" />
|
||||||
|
|||||||
@@ -77,7 +77,7 @@
|
|||||||
android:layout_marginHorizontal="8dp"
|
android:layout_marginHorizontal="8dp"
|
||||||
android:layout_marginTop="11dp"
|
android:layout_marginTop="11dp"
|
||||||
android:layout_marginBottom="20dp"
|
android:layout_marginBottom="20dp"
|
||||||
app:cardCornerRadius="@dimen/rounded_corner_size_default"
|
app:shapeAppearance="@style/ShapeAppearance.AllCornersRounded"
|
||||||
app:cardElevation="10dp"
|
app:cardElevation="10dp"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/instructions_header"
|
app:layout_constraintBottom_toTopOf="@+id/instructions_header"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/end_guide"
|
app:layout_constraintEnd_toStartOf="@+id/end_guide"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_margin="8dp"
|
android:layout_margin="8dp"
|
||||||
app:cardCornerRadius="@dimen/rounded_corner_size_default"
|
app:shapeAppearance="@style/ShapeAppearance.AllCornersRounded"
|
||||||
app:cardElevation="10dp"
|
app:cardElevation="10dp"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
android:id="@+id/login"
|
android:id="@+id/login"
|
||||||
android:contentDescription="@string/menu_main_toolbar_content_description_login"
|
android:contentDescription="@string/menu_main_toolbar_content_description_login"
|
||||||
android:title="@string/menu_main_toolbar_login"
|
android:title="@string/menu_main_toolbar_login"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="ifRoom" />
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/logout"
|
android:id="@+id/logout"
|
||||||
|
|||||||
@@ -1,17 +1,31 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<!-- Base application theme. -->
|
|
||||||
<style name="Theme.Mealient" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
<style name="AppTheme" parent="Theme.Material3.Dark.NoActionBar">
|
||||||
<!-- Primary brand color. -->
|
<item name="colorPrimary">@color/md_theme_dark_primary</item>
|
||||||
<item name="colorPrimary">@color/purple_200</item>
|
<item name="colorOnPrimary">@color/md_theme_dark_onPrimary</item>
|
||||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
<item name="colorPrimaryContainer">@color/md_theme_dark_primaryContainer</item>
|
||||||
<item name="colorOnPrimary">@color/black</item>
|
<item name="colorOnPrimaryContainer">@color/md_theme_dark_onPrimaryContainer</item>
|
||||||
<!-- Secondary brand color. -->
|
<item name="colorSecondary">@color/md_theme_dark_secondary</item>
|
||||||
<item name="colorSecondary">@color/teal_200</item>
|
<item name="colorOnSecondary">@color/md_theme_dark_onSecondary</item>
|
||||||
<item name="colorSecondaryVariant">@color/teal_200</item>
|
<item name="colorSecondaryContainer">@color/md_theme_dark_secondaryContainer</item>
|
||||||
<item name="colorOnSecondary">@color/black</item>
|
<item name="colorOnSecondaryContainer">@color/md_theme_dark_onSecondaryContainer</item>
|
||||||
<!-- Status bar color. -->
|
<item name="colorTertiary">@color/md_theme_dark_tertiary</item>
|
||||||
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
|
<item name="colorOnTertiary">@color/md_theme_dark_onTertiary</item>
|
||||||
<!-- Customize your theme here. -->
|
<item name="colorTertiaryContainer">@color/md_theme_dark_tertiaryContainer</item>
|
||||||
<item name="android:overScrollMode">never</item>
|
<item name="colorOnTertiaryContainer">@color/md_theme_dark_onTertiaryContainer</item>
|
||||||
</style>
|
<item name="colorError">@color/md_theme_dark_error</item>
|
||||||
|
<item name="colorErrorContainer">@color/md_theme_dark_errorContainer</item>
|
||||||
|
<item name="colorOnError">@color/md_theme_dark_onError</item>
|
||||||
|
<item name="colorOnErrorContainer">@color/md_theme_dark_onErrorContainer</item>
|
||||||
|
<item name="android:colorBackground">@color/md_theme_dark_background</item>
|
||||||
|
<item name="colorOnBackground">@color/md_theme_dark_onBackground</item>
|
||||||
|
<item name="colorSurface">@color/md_theme_dark_surface</item>
|
||||||
|
<item name="colorOnSurface">@color/md_theme_dark_onSurface</item>
|
||||||
|
<item name="colorSurfaceVariant">@color/md_theme_dark_surfaceVariant</item>
|
||||||
|
<item name="colorOnSurfaceVariant">@color/md_theme_dark_onSurfaceVariant</item>
|
||||||
|
<item name="colorOutline">@color/md_theme_dark_outline</item>
|
||||||
|
<item name="colorOnSurfaceInverse">@color/md_theme_dark_inverseOnSurface</item>
|
||||||
|
<item name="colorSurfaceInverse">@color/md_theme_dark_inverseSurface</item>
|
||||||
|
<item name="colorPrimaryInverse">@color/md_theme_dark_primaryInverse</item>
|
||||||
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,12 +1,58 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
<resources>
|
||||||
<color name="purple_200">#FFBB86FC</color>
|
<color name="md_theme_light_primary">#7743B5</color>
|
||||||
<color name="purple_500">#FF6200EE</color>
|
<color name="md_theme_light_onPrimary">#FFFFFF</color>
|
||||||
<color name="purple_700">#FF3700B3</color>
|
<color name="md_theme_light_primaryContainer">#EFDBFF</color>
|
||||||
<color name="teal_200">#FF03DAC5</color>
|
<color name="md_theme_light_onPrimaryContainer">#290054</color>
|
||||||
<color name="teal_700">#FF018786</color>
|
<color name="md_theme_light_secondary">#655A70</color>
|
||||||
<color name="black">#FF000000</color>
|
<color name="md_theme_light_onSecondary">#FFFFFF</color>
|
||||||
<color name="white">#FFFFFFFF</color>
|
<color name="md_theme_light_secondaryContainer">#ECDDF7</color>
|
||||||
|
<color name="md_theme_light_onSecondaryContainer">#201829</color>
|
||||||
<color name="primary">#FF9D76DC</color>
|
<color name="md_theme_light_tertiary">#805159</color>
|
||||||
|
<color name="md_theme_light_onTertiary">#FFFFFF</color>
|
||||||
|
<color name="md_theme_light_tertiaryContainer">#FFD9DF</color>
|
||||||
|
<color name="md_theme_light_onTertiaryContainer">#321118</color>
|
||||||
|
<color name="md_theme_light_error">#BA1B1B</color>
|
||||||
|
<color name="md_theme_light_errorContainer">#FFDAD4</color>
|
||||||
|
<color name="md_theme_light_onError">#FFFFFF</color>
|
||||||
|
<color name="md_theme_light_onErrorContainer">#410001</color>
|
||||||
|
<color name="md_theme_light_background">#FFFBFC</color>
|
||||||
|
<color name="md_theme_light_onBackground">#1D1B1E</color>
|
||||||
|
<color name="md_theme_light_surface">#FFFBFC</color>
|
||||||
|
<color name="md_theme_light_onSurface">#1D1B1E</color>
|
||||||
|
<color name="md_theme_light_surfaceVariant">#E8DFEB</color>
|
||||||
|
<color name="md_theme_light_onSurfaceVariant">#4A454E</color>
|
||||||
|
<color name="md_theme_light_outline">#7B757E</color>
|
||||||
|
<color name="md_theme_light_inverseOnSurface">#F5EFF3</color>
|
||||||
|
<color name="md_theme_light_inverseSurface">#322F33</color>
|
||||||
|
<color name="md_theme_light_inversePrimary">#DBB8FF</color>
|
||||||
|
<color name="md_theme_light_shadow">#000000</color>
|
||||||
|
<color name="md_theme_light_primaryInverse">#DBB8FF</color>
|
||||||
|
<color name="md_theme_dark_primary">#DBB8FF</color>
|
||||||
|
<color name="md_theme_dark_onPrimary">#460283</color>
|
||||||
|
<color name="md_theme_dark_primaryContainer">#5E289B</color>
|
||||||
|
<color name="md_theme_dark_onPrimaryContainer">#EFDBFF</color>
|
||||||
|
<color name="md_theme_dark_secondary">#CFC1DA</color>
|
||||||
|
<color name="md_theme_dark_onSecondary">#362D40</color>
|
||||||
|
<color name="md_theme_dark_secondaryContainer">#4D4357</color>
|
||||||
|
<color name="md_theme_dark_onSecondaryContainer">#ECDDF7</color>
|
||||||
|
<color name="md_theme_dark_tertiary">#F2B7C0</color>
|
||||||
|
<color name="md_theme_dark_onTertiary">#4B252C</color>
|
||||||
|
<color name="md_theme_dark_tertiaryContainer">#653A42</color>
|
||||||
|
<color name="md_theme_dark_onTertiaryContainer">#FFD9DF</color>
|
||||||
|
<color name="md_theme_dark_error">#FFB4A9</color>
|
||||||
|
<color name="md_theme_dark_errorContainer">#930006</color>
|
||||||
|
<color name="md_theme_dark_onError">#680003</color>
|
||||||
|
<color name="md_theme_dark_onErrorContainer">#FFDAD4</color>
|
||||||
|
<color name="md_theme_dark_background">#1D1B1E</color>
|
||||||
|
<color name="md_theme_dark_onBackground">#E7E1E5</color>
|
||||||
|
<color name="md_theme_dark_surface">#1D1B1E</color>
|
||||||
|
<color name="md_theme_dark_onSurface">#E7E1E5</color>
|
||||||
|
<color name="md_theme_dark_surfaceVariant">#4A454E</color>
|
||||||
|
<color name="md_theme_dark_onSurfaceVariant">#CCC4CF</color>
|
||||||
|
<color name="md_theme_dark_outline">#958E98</color>
|
||||||
|
<color name="md_theme_dark_inverseOnSurface">#1D1B1E</color>
|
||||||
|
<color name="md_theme_dark_inverseSurface">#E7E1E5</color>
|
||||||
|
<color name="md_theme_dark_inversePrimary">#7743B5</color>
|
||||||
|
<color name="md_theme_dark_shadow">#000000</color>
|
||||||
|
<color name="md_theme_dark_primaryInverse">#7743B5</color>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<style name="SmallMarginTextInputLayoutStyle" parent="Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
<style name="SmallMarginTextInputLayoutStyle">
|
||||||
<item name="android:layout_width">0dp</item>
|
<item name="android:layout_width">0dp</item>
|
||||||
<item name="android:layout_height">wrap_content</item>
|
<item name="android:layout_height">wrap_content</item>
|
||||||
<item name="android:layout_margin">@dimen/margin_small</item>
|
<item name="android:layout_margin">@dimen/margin_small</item>
|
||||||
@@ -13,17 +13,17 @@
|
|||||||
<item name="android:layout_margin">@dimen/margin_small</item>
|
<item name="android:layout_margin">@dimen/margin_small</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="ShapeAppearance.AllCornersRounded" parent="ShapeAppearance.MaterialComponents">
|
<style name="ShapeAppearance.AllCornersRounded" parent="ShapeAppearance.Material3.LargeComponent">
|
||||||
<item name="cornerSize">@dimen/rounded_corner_size_default</item>
|
<item name="cornerSize">@dimen/rounded_corner_size_default</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- This is a workaround to support always round corners of the bottom sheet
|
<!-- This is a workaround to support always round corners of the bottom sheet
|
||||||
See more at https://github.com/material-components/material-components-android/pull/437#issuecomment-852461685 -->
|
See more at https://github.com/material-components/material-components-android/pull/437#issuecomment-852461685 -->
|
||||||
<style name="NoShapeBottomSheetDialog" parent="ThemeOverlay.MaterialComponents.BottomSheetDialog">
|
<style name="NoShapeBottomSheetDialog" parent="ThemeOverlay.Material3.BottomSheetDialog">
|
||||||
<item name="bottomSheetStyle">@style/NoShapeBottomSheet</item>
|
<item name="bottomSheetStyle">@style/NoShapeBottomSheet</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="NoShapeBottomSheet" parent="Widget.MaterialComponents.BottomSheet.Modal">
|
<style name="NoShapeBottomSheet" parent="Widget.Material3.BottomSheet.Modal">
|
||||||
<item name="shapeAppearance">@null</item>
|
<item name="shapeAppearance">@null</item>
|
||||||
<item name="shapeAppearanceOverlay">@null</item>
|
<item name="shapeAppearanceOverlay">@null</item>
|
||||||
<item name="android:background">@drawable/recipe_info_background</item>
|
<item name="android:background">@drawable/recipe_info_background</item>
|
||||||
|
|||||||
@@ -1,17 +1,32 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<!-- Base application theme. -->
|
|
||||||
<style name="Theme.Mealient" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
<style name="AppTheme" parent="Theme.Material3.Light.NoActionBar">
|
||||||
<!-- Primary brand color. -->
|
<item name="colorPrimary">@color/md_theme_light_primary</item>
|
||||||
<item name="colorPrimary">@color/primary</item>
|
<item name="colorOnPrimary">@color/md_theme_light_onPrimary</item>
|
||||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
<item name="colorPrimaryContainer">@color/md_theme_light_primaryContainer</item>
|
||||||
<item name="colorOnPrimary">@color/white</item>
|
<item name="colorOnPrimaryContainer">@color/md_theme_light_onPrimaryContainer</item>
|
||||||
<!-- Secondary brand color. -->
|
<item name="colorSecondary">@color/md_theme_light_secondary</item>
|
||||||
<item name="colorSecondary">@color/teal_200</item>
|
<item name="colorOnSecondary">@color/md_theme_light_onSecondary</item>
|
||||||
<item name="colorSecondaryVariant">@color/teal_700</item>
|
<item name="colorSecondaryContainer">@color/md_theme_light_secondaryContainer</item>
|
||||||
<item name="colorOnSecondary">@color/black</item>
|
<item name="colorOnSecondaryContainer">@color/md_theme_light_onSecondaryContainer</item>
|
||||||
<!-- Status bar color. -->
|
<item name="colorTertiary">@color/md_theme_light_tertiary</item>
|
||||||
<item name="android:statusBarColor">?attr/colorPrimary</item>
|
<item name="colorOnTertiary">@color/md_theme_light_onTertiary</item>
|
||||||
<!-- Customize your theme here. -->
|
<item name="colorTertiaryContainer">@color/md_theme_light_tertiaryContainer</item>
|
||||||
<item name="android:overScrollMode">never</item>
|
<item name="colorOnTertiaryContainer">@color/md_theme_light_onTertiaryContainer</item>
|
||||||
</style>
|
<item name="colorError">@color/md_theme_light_error</item>
|
||||||
|
<item name="colorErrorContainer">@color/md_theme_light_errorContainer</item>
|
||||||
|
<item name="colorOnError">@color/md_theme_light_onError</item>
|
||||||
|
<item name="colorOnErrorContainer">@color/md_theme_light_onErrorContainer</item>
|
||||||
|
<item name="android:colorBackground">@color/md_theme_light_background</item>
|
||||||
|
<item name="colorOnBackground">@color/md_theme_light_onBackground</item>
|
||||||
|
<item name="colorSurface">@color/md_theme_light_surface</item>
|
||||||
|
<item name="colorOnSurface">@color/md_theme_light_onSurface</item>
|
||||||
|
<item name="colorSurfaceVariant">@color/md_theme_light_surfaceVariant</item>
|
||||||
|
<item name="colorOnSurfaceVariant">@color/md_theme_light_onSurfaceVariant</item>
|
||||||
|
<item name="colorOutline">@color/md_theme_light_outline</item>
|
||||||
|
<item name="colorOnSurfaceInverse">@color/md_theme_light_inverseOnSurface</item>
|
||||||
|
<item name="colorSurfaceInverse">@color/md_theme_light_inverseSurface</item>
|
||||||
|
<item name="colorPrimaryInverse">@color/md_theme_light_primaryInverse</item>
|
||||||
|
<item name="android:overScrollMode">never</item>
|
||||||
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -2,7 +2,7 @@ package gq.kirmanak.mealient.data.recipes.impl
|
|||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
||||||
import gq.kirmanak.mealient.ui.ImageLoader
|
import gq.kirmanak.mealient.ui.images.ImageLoader
|
||||||
import io.mockk.MockKAnnotations
|
import io.mockk.MockKAnnotations
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
import io.mockk.impl.annotations.MockK
|
import io.mockk.impl.annotations.MockK
|
||||||
|
|||||||
Reference in New Issue
Block a user