diff --git a/README.md b/README.md
index 0b38ad0..670e2cf 100644
--- a/README.md
+++ b/README.md
@@ -9,18 +9,18 @@ repository.
## What is this?
An unofficial Android client for [Mealie](https://hay-kot.github.io/mealie/). It enables you to
-easily access your recipes using via Android phone. The main goal is to have everything stored
-locally to access it without Internet connection which is impossible with web site version.
+easily access your recipes using an Android device. The main advantage over website is that
+recipe data is stored locally and can be accessed without the Internet connection.
## Status
-Current version is a very early alpha which supports a tiny portion of the Mealie capabilites. There
-is a lot of work ahead, do not expect much. Having said that, you can list your recipes and read the
-instructions/ingredients for each of them.
+Current version is a very early alpha which supports a small subset of the Mealie capabilities.
+It supports both API of older Mealie 0.5.6 and newer 1.0.0. Displays the list of recipes,
+information about each of the recipes. Moreover, you can create a recipe from the app!
## Screenshots
-
+
## How to install
@@ -28,6 +28,4 @@ Download the latest apk from the releases page.
## Contribution
-Any contribution is greatly appreciated. Including translations, new issues, feature requests and
-typo corrections. I won't specify any rules until I have to ask the same thing multiple times. Just
-use the common sense.
\ No newline at end of file
+Any contribution is greatly appreciated: translations, bug reports, feature requests and any PR.
\ No newline at end of file
diff --git a/app/src/main/java/gq/kirmanak/mealient/App.kt b/app/src/main/java/gq/kirmanak/mealient/App.kt
index 24332c9..fbd1c92 100644
--- a/app/src/main/java/gq/kirmanak/mealient/App.kt
+++ b/app/src/main/java/gq/kirmanak/mealient/App.kt
@@ -1,6 +1,7 @@
package gq.kirmanak.mealient
import android.app.Application
+import com.google.android.material.color.DynamicColors
import dagger.hilt.android.HiltAndroidApp
import gq.kirmanak.mealient.architecture.configuration.BuildConfiguration
import gq.kirmanak.mealient.data.analytics.Analytics
@@ -23,5 +24,6 @@ class App : Application() {
super.onCreate()
logger.v { "onCreate() called" }
analytics.setIsEnabled(!buildConfiguration.isDebug())
+ DynamicColors.applyToActivitiesIfAvailable(this)
}
}
diff --git a/app/src/main/java/gq/kirmanak/mealient/extensions/FragmentExtensions.kt b/app/src/main/java/gq/kirmanak/mealient/extensions/FragmentExtensions.kt
index f6ffa7c..0ab4993 100644
--- a/app/src/main/java/gq/kirmanak/mealient/extensions/FragmentExtensions.kt
+++ b/app/src/main/java/gq/kirmanak/mealient/extensions/FragmentExtensions.kt
@@ -2,19 +2,11 @@ package gq.kirmanak.mealient.extensions
import androidx.annotation.StringRes
import androidx.fragment.app.Fragment
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector
-import kotlinx.coroutines.launch
fun Fragment.collectWhenViewResumed(flow: Flow, collector: FlowCollector) {
- viewLifecycleOwner.lifecycleScope.launch {
- viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
- flow.collect(collector)
- }
- }
+ viewLifecycleOwner.collectWhenResumed(flow, collector)
}
fun Fragment.showLongToast(@StringRes text: Int) = context?.showLongToast(text) != null
diff --git a/app/src/main/java/gq/kirmanak/mealient/extensions/ViewExtensions.kt b/app/src/main/java/gq/kirmanak/mealient/extensions/ViewExtensions.kt
index 452b2c9..08ede24 100644
--- a/app/src/main/java/gq/kirmanak/mealient/extensions/ViewExtensions.kt
+++ b/app/src/main/java/gq/kirmanak/mealient/extensions/ViewExtensions.kt
@@ -2,6 +2,9 @@ package gq.kirmanak.mealient.extensions
import android.content.Context
import android.content.SharedPreferences
+import android.content.res.Configuration.UI_MODE_NIGHT_MASK
+import android.content.res.Configuration.UI_MODE_NIGHT_YES
+import android.os.Build
import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
@@ -10,10 +13,7 @@ import android.widget.Toast
import androidx.annotation.StringRes
import androidx.core.content.getSystemService
import androidx.core.widget.doAfterTextChanged
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.Observer
-import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.*
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.google.android.material.textfield.TextInputLayout
import gq.kirmanak.mealient.logging.Logger
@@ -21,10 +21,7 @@ import kotlinx.coroutines.channels.ChannelResult
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.onClosed
import kotlinx.coroutines.channels.onFailure
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
fun SwipeRefreshLayout.refreshRequestFlow(logger: Logger): Flow = callbackFlow {
@@ -114,4 +111,19 @@ private fun Context.showToast(text: String, length: Int) {
fun View.hideKeyboard() {
val imm = context.getSystemService()
imm?.hideSoftInputFromWindow(windowToken, 0)
+}
+
+fun Context.isDarkThemeOn(): Boolean {
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
+ resources.configuration.isNightModeActive
+ else
+ resources.configuration.uiMode and UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES
+}
+
+fun LifecycleOwner.collectWhenResumed(flow: Flow, collector: FlowCollector) {
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ flow.collect(collector)
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/activity/MainActivity.kt b/app/src/main/java/gq/kirmanak/mealient/ui/activity/MainActivity.kt
index 63270ce..434c8ce 100644
--- a/app/src/main/java/gq/kirmanak/mealient/ui/activity/MainActivity.kt
+++ b/app/src/main/java/gq/kirmanak/mealient/ui/activity/MainActivity.kt
@@ -1,20 +1,18 @@
package gq.kirmanak.mealient.ui.activity
import android.os.Bundle
-import android.view.Menu
import android.view.MenuItem
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
-import androidx.appcompat.widget.SearchView
-import androidx.appcompat.widget.SearchView.OnQueryTextListener
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
+import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.isVisible
+import androidx.core.view.iterator
+import androidx.drawerlayout.widget.DrawerLayout
import androidx.navigation.NavController
import androidx.navigation.NavDirections
import androidx.navigation.fragment.NavHostFragment
import by.kirich1409.viewbindingdelegate.viewBinding
-import com.google.android.material.shape.CornerFamily
-import com.google.android.material.shape.MaterialShapeDrawable
import dagger.hilt.android.AndroidEntryPoint
import gq.kirmanak.mealient.NavGraphDirections.Companion.actionGlobalAddRecipeFragment
import gq.kirmanak.mealient.NavGraphDirections.Companion.actionGlobalAuthenticationFragment
@@ -22,6 +20,8 @@ import gq.kirmanak.mealient.NavGraphDirections.Companion.actionGlobalBaseURLFrag
import gq.kirmanak.mealient.NavGraphDirections.Companion.actionGlobalRecipesListFragment
import gq.kirmanak.mealient.R
import gq.kirmanak.mealient.databinding.MainActivityBinding
+import gq.kirmanak.mealient.extensions.collectWhenResumed
+import gq.kirmanak.mealient.extensions.isDarkThemeOn
import gq.kirmanak.mealient.extensions.observeOnce
import gq.kirmanak.mealient.logging.Logger
import javax.inject.Inject
@@ -31,8 +31,6 @@ class MainActivity : AppCompatActivity(R.layout.main_activity) {
private val binding: MainActivityBinding by viewBinding(MainActivityBinding::bind, R.id.drawer)
private val viewModel by viewModels()
- private val title: String by lazy { getString(R.string.app_name) }
- private val uiState: MainActivityUiState get() = viewModel.uiState
private val navController: NavController
get() = binding.navHost.getFragment().navController
@@ -45,126 +43,98 @@ class MainActivity : AppCompatActivity(R.layout.main_activity) {
logger.v { "onCreate() called with: savedInstanceState = $savedInstanceState" }
splashScreen.setKeepOnScreenCondition { viewModel.startDestination.value == null }
setContentView(binding.root)
- configureToolbar()
+ setupUi()
configureNavGraph()
- viewModel.uiStateLive.observe(this, ::onUiStateChange)
- binding.navigationView.setNavigationItemSelectedListener(::onNavigationItemSelected)
}
private fun configureNavGraph() {
logger.v { "configureNavGraph() called" }
viewModel.startDestination.observeOnce(this) {
logger.d { "configureNavGraph: received destination" }
- val graph = navController.navInflater.inflate(R.navigation.nav_graph)
+ val controller = navController
+ val graph = controller.navInflater.inflate(R.navigation.nav_graph)
graph.setStartDestination(it)
- navController.setGraph(graph, intent.extras)
+ controller.setGraph(graph, intent.extras)
}
}
- private fun configureToolbar() {
- setSupportActionBar(binding.toolbar)
- binding.toolbar.setNavigationIcon(R.drawable.ic_toolbar)
- binding.toolbar.setNavigationOnClickListener { binding.drawer.open() }
- setToolbarRoundCorner()
+ private fun setupUi() {
+ binding.toolbar.setNavigationOnClickListener {
+ binding.toolbar.clearSearchFocus()
+ binding.drawer.open()
+ }
+ binding.toolbar.onSearchQueryChanged { query ->
+ viewModel.onSearchQuery(query.trim().takeUnless { it.isEmpty() })
+ }
+ binding.navigationView.setNavigationItemSelectedListener(::onNavigationItemSelected)
+ with(WindowInsetsControllerCompat(window, window.decorView)) {
+ val isAppearanceLightBars = !isDarkThemeOn()
+ isAppearanceLightNavigationBars = isAppearanceLightBars
+ isAppearanceLightStatusBars = isAppearanceLightBars
+ }
+ viewModel.uiStateLive.observe(this, ::onUiStateChange)
+ collectWhenResumed(viewModel.clearSearchViewFocus) {
+ logger.d { "clearSearchViewFocus(): received event" }
+ binding.toolbar.clearSearchFocus()
+ }
}
private fun onNavigationItemSelected(menuItem: MenuItem): Boolean {
logger.v { "onNavigationItemSelected() called with: menuItem = $menuItem" }
- menuItem.isChecked = true
+ if (menuItem.isChecked) {
+ logger.d { "Not navigating because it is the current destination" }
+ binding.drawer.close()
+ return true
+ }
val directions = when (menuItem.itemId) {
R.id.add_recipe -> actionGlobalAddRecipeFragment()
R.id.recipes_list -> actionGlobalRecipesListFragment()
- R.id.change_url -> actionGlobalBaseURLFragment()
+ R.id.change_url -> actionGlobalBaseURLFragment(false)
+ R.id.login -> actionGlobalAuthenticationFragment()
+ R.id.logout -> {
+ viewModel.logout()
+ return true
+ }
else -> throw IllegalArgumentException("Unknown menu item id: ${menuItem.itemId}")
}
+ menuItem.isChecked = true
navigateTo(directions)
- binding.drawer.close()
return true
}
private fun onUiStateChange(uiState: MainActivityUiState) {
logger.v { "onUiStateChange() called with: uiState = $uiState" }
- supportActionBar?.title = if (uiState.titleVisible) title else null
- binding.navigationView.isVisible = uiState.navigationVisible
- invalidateOptionsMenu()
- }
+ for (menuItem in binding.navigationView.menu.iterator()) {
+ val itemId = menuItem.itemId
+ when (itemId) {
+ R.id.logout -> menuItem.isVisible = uiState.canShowLogout
+ R.id.login -> menuItem.isVisible = uiState.canShowLogin
+ }
+ menuItem.isChecked = itemId == uiState.checkedMenuItemId
+ }
- private fun setToolbarRoundCorner() {
- logger.v { "setToolbarRoundCorner() called" }
- val drawables = listOf(
- binding.toolbarHolder.background as? MaterialShapeDrawable,
- binding.toolbar.background as? MaterialShapeDrawable,
+ binding.toolbar.isVisible = uiState.navigationVisible
+ binding.root.setDrawerLockMode(
+ if (uiState.navigationVisible) {
+ DrawerLayout.LOCK_MODE_UNLOCKED
+ } else {
+ DrawerLayout.LOCK_MODE_LOCKED_CLOSED
+ }
)
- logger.d { "setToolbarRoundCorner: drawables = $drawables" }
- val radius = resources.getDimension(R.dimen.main_activity_toolbar_corner_radius)
- for (drawable in drawables) {
- drawable?.apply {
- shapeAppearanceModel = shapeAppearanceModel.toBuilder()
- .setBottomLeftCorner(CornerFamily.ROUNDED, radius).build()
- }
+
+ binding.toolbar.isSearchVisible = uiState.searchVisible
+
+ if (uiState.searchVisible) {
+ binding.toolbarHolder.setBackgroundResource(R.drawable.bg_toolbar)
+ } else {
+ binding.toolbarHolder.background = null
}
}
- override fun onCreateOptionsMenu(menu: Menu): Boolean {
- logger.v { "onCreateOptionsMenu() called with: menu = $menu" }
- menuInflater.inflate(R.menu.main_toolbar, menu)
- menu.findItem(R.id.logout).isVisible = uiState.canShowLogout
- menu.findItem(R.id.login).isVisible = uiState.canShowLogin
- val searchItem = menu.findItem(R.id.search_recipe_action)
- searchItem.isVisible = uiState.searchVisible
- setupSearchItem(searchItem)
- return true
- }
-
- private fun setupSearchItem(searchItem: MenuItem) {
- logger.v { "setupSearchItem() called with: searchItem = $searchItem" }
- val searchView = searchItem.actionView as? SearchView
- if (searchView == null) {
- logger.e { "setupSearchItem: search item's actionView is null or not SearchView" }
- return
- }
-
- searchView.queryHint = getString(R.string.search_recipes_hint)
- searchView.isSubmitButtonEnabled = false
-
- searchView.setQuery(viewModel.lastSearchQuery, false)
- searchView.isIconified = viewModel.lastSearchQuery.isNullOrBlank()
-
- searchView.setOnCloseListener {
- logger.v { "onClose() called" }
- viewModel.onSearchQuery(null)
- false
- }
-
- searchView.setOnQueryTextListener(object : OnQueryTextListener {
- override fun onQueryTextSubmit(query: String?): Boolean = true
-
- override fun onQueryTextChange(newText: String?): Boolean {
- logger.v { "onQueryTextChange() called with: newText = $newText" }
- viewModel.onSearchQuery(newText?.trim()?.takeUnless { it.isEmpty() })
- return true
- }
- })
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- logger.v { "onOptionsItemSelected() called with: item = $item" }
- val result = when (item.itemId) {
- R.id.login -> {
- navigateTo(actionGlobalAuthenticationFragment())
- true
- }
- R.id.logout -> {
- viewModel.logout()
- true
- }
- else -> super.onOptionsItemSelected(item)
- }
- return result
- }
-
private fun navigateTo(directions: NavDirections) {
logger.v { "navigateTo() called with: directions = $directions" }
+ binding.toolbarHolder.setExpanded(true)
+ binding.drawer.close()
navController.navigate(directions)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/activity/MainActivityUiState.kt b/app/src/main/java/gq/kirmanak/mealient/ui/activity/MainActivityUiState.kt
index 3317d18..eda6f14 100644
--- a/app/src/main/java/gq/kirmanak/mealient/ui/activity/MainActivityUiState.kt
+++ b/app/src/main/java/gq/kirmanak/mealient/ui/activity/MainActivityUiState.kt
@@ -1,15 +1,14 @@
package gq.kirmanak.mealient.ui.activity
+import androidx.annotation.IdRes
+
data class MainActivityUiState(
- val loginButtonVisible: Boolean = false,
- val titleVisible: Boolean = true,
val isAuthorized: Boolean = false,
val navigationVisible: Boolean = false,
val searchVisible: Boolean = false,
+ @IdRes val checkedMenuItemId: Int? = null,
) {
- val canShowLogin: Boolean
- get() = !isAuthorized && loginButtonVisible
+ val canShowLogin: Boolean get() = !isAuthorized
- val canShowLogout: Boolean
- get() = isAuthorized && loginButtonVisible
+ val canShowLogout: Boolean get() = isAuthorized
}
diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/activity/MainActivityViewModel.kt b/app/src/main/java/gq/kirmanak/mealient/ui/activity/MainActivityViewModel.kt
index cb910bb..8022d30 100644
--- a/app/src/main/java/gq/kirmanak/mealient/ui/activity/MainActivityViewModel.kt
+++ b/app/src/main/java/gq/kirmanak/mealient/ui/activity/MainActivityViewModel.kt
@@ -8,8 +8,11 @@ import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
import gq.kirmanak.mealient.data.disclaimer.DisclaimerStorage
import gq.kirmanak.mealient.data.recipes.RecipeRepo
import gq.kirmanak.mealient.logging.Logger
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
@@ -32,8 +35,8 @@ class MainActivityViewModel @Inject constructor(
private val _startDestination = MutableLiveData()
val startDestination: LiveData = _startDestination
- var lastSearchQuery: String? = null
- private set
+ private val _clearSearchViewFocusChannel = Channel()
+ val clearSearchViewFocus: Flow = _clearSearchViewFocusChannel.receiveAsFlow()
init {
authRepo.isAuthorizedFlow
@@ -60,9 +63,11 @@ class MainActivityViewModel @Inject constructor(
fun onSearchQuery(query: String?) {
logger.v { "onSearchQuery() called with: query = $query" }
- if (lastSearchQuery != query) {
- lastSearchQuery = query
- recipeRepo.updateNameQuery(query)
- }
+ recipeRepo.updateNameQuery(query)
+ }
+
+ fun clearSearchViewFocus() {
+ logger.v { "clearSearchViewFocus() called" }
+ _clearSearchViewFocusChannel.trySend(Unit)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/activity/ToolbarView.kt b/app/src/main/java/gq/kirmanak/mealient/ui/activity/ToolbarView.kt
new file mode 100644
index 0000000..71215e2
--- /dev/null
+++ b/app/src/main/java/gq/kirmanak/mealient/ui/activity/ToolbarView.kt
@@ -0,0 +1,47 @@
+package gq.kirmanak.mealient.ui.activity
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import androidx.annotation.AttrRes
+import androidx.annotation.StyleRes
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.view.isVisible
+import androidx.core.widget.doAfterTextChanged
+import gq.kirmanak.mealient.databinding.ViewToolbarBinding
+import gq.kirmanak.mealient.extensions.hideKeyboard
+
+class ToolbarView @JvmOverloads constructor(
+ context: Context,
+ attributeSet: AttributeSet? = null,
+ @AttrRes defStyleAttr: Int = 0,
+ @StyleRes defStyleRes: Int = 0,
+) : ConstraintLayout(context, attributeSet, defStyleAttr, defStyleRes) {
+
+ private lateinit var binding: ViewToolbarBinding
+
+ var isSearchVisible: Boolean
+ get() = binding.searchEdit.isVisible
+ set(value) {
+ binding.searchEdit.isVisible = value
+ }
+
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ val inflater = LayoutInflater.from(context)
+ binding = ViewToolbarBinding.inflate(inflater, this)
+ }
+
+ fun setNavigationOnClickListener(listener: OnClickListener?) {
+ binding.navigationIcon.setOnClickListener(listener)
+ }
+
+ fun onSearchQueryChanged(block: (String) -> Unit) {
+ binding.searchEdit.doAfterTextChanged { block(it.toString()) }
+ }
+
+ fun clearSearchFocus() {
+ binding.searchEdit.clearFocus()
+ hideKeyboard()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/add/AddRecipeFragment.kt b/app/src/main/java/gq/kirmanak/mealient/ui/add/AddRecipeFragment.kt
index d311d68..c9cfeb0 100644
--- a/app/src/main/java/gq/kirmanak/mealient/ui/add/AddRecipeFragment.kt
+++ b/app/src/main/java/gq/kirmanak/mealient/ui/add/AddRecipeFragment.kt
@@ -39,10 +39,9 @@ class AddRecipeFragment : Fragment(R.layout.fragment_add_recipe) {
logger.v { "onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState" }
activityViewModel.updateUiState {
it.copy(
- loginButtonVisible = true,
- titleVisible = false,
navigationVisible = true,
searchVisible = false,
+ checkedMenuItemId = R.id.add_recipe,
)
}
viewModel.loadPreservedRequest()
diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/auth/AuthenticationFragment.kt b/app/src/main/java/gq/kirmanak/mealient/ui/auth/AuthenticationFragment.kt
index bd108f7..f28ae2d 100644
--- a/app/src/main/java/gq/kirmanak/mealient/ui/auth/AuthenticationFragment.kt
+++ b/app/src/main/java/gq/kirmanak/mealient/ui/auth/AuthenticationFragment.kt
@@ -32,12 +32,7 @@ class AuthenticationFragment : Fragment(R.layout.fragment_authentication) {
logger.v { "onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState" }
binding.button.setOnClickListener { onLoginClicked() }
activityViewModel.updateUiState {
- it.copy(
- loginButtonVisible = false,
- titleVisible = true,
- navigationVisible = false,
- searchVisible = false
- )
+ it.copy(navigationVisible = true, searchVisible = false, checkedMenuItemId = R.id.login)
}
viewModel.uiState.observe(viewLifecycleOwner, ::onUiStateChange)
}
@@ -66,7 +61,7 @@ class AuthenticationFragment : Fragment(R.layout.fragment_authentication) {
private fun onUiStateChange(uiState: OperationUiState) = with(binding) {
logger.v { "onUiStateChange() called with: authUiState = $uiState" }
if (uiState.isSuccess) {
- findNavController().popBackStack()
+ findNavController().navigateUp()
return
}
diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/baseurl/BaseURLFragment.kt b/app/src/main/java/gq/kirmanak/mealient/ui/baseurl/BaseURLFragment.kt
index 31b1bfa..5313d56 100644
--- a/app/src/main/java/gq/kirmanak/mealient/ui/baseurl/BaseURLFragment.kt
+++ b/app/src/main/java/gq/kirmanak/mealient/ui/baseurl/BaseURLFragment.kt
@@ -6,6 +6,7 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
+import androidx.navigation.fragment.navArgs
import by.kirich1409.viewbindingdelegate.viewBinding
import dagger.hilt.android.AndroidEntryPoint
import gq.kirmanak.mealient.R
@@ -24,6 +25,7 @@ class BaseURLFragment : Fragment(R.layout.fragment_base_url) {
private val binding by viewBinding(FragmentBaseUrlBinding::bind)
private val viewModel by viewModels()
private val activityViewModel by activityViewModels()
+ private val args by navArgs()
@Inject
lateinit var logger: Logger
@@ -35,10 +37,9 @@ class BaseURLFragment : Fragment(R.layout.fragment_base_url) {
viewModel.uiState.observe(viewLifecycleOwner, ::onUiStateChange)
activityViewModel.updateUiState {
it.copy(
- loginButtonVisible = false,
- titleVisible = true,
- navigationVisible = false,
- searchVisible = false
+ navigationVisible = !args.isOnboarding,
+ searchVisible = false,
+ checkedMenuItemId = R.id.change_url,
)
}
}
diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/disclaimer/DisclaimerFragment.kt b/app/src/main/java/gq/kirmanak/mealient/ui/disclaimer/DisclaimerFragment.kt
index bd024e4..d7db583 100644
--- a/app/src/main/java/gq/kirmanak/mealient/ui/disclaimer/DisclaimerFragment.kt
+++ b/app/src/main/java/gq/kirmanak/mealient/ui/disclaimer/DisclaimerFragment.kt
@@ -38,7 +38,7 @@ class DisclaimerFragment : Fragment(R.layout.fragment_disclaimer) {
private fun navigateNext() {
logger.v { "navigateNext() called" }
- findNavController().navigate(actionDisclaimerFragmentToBaseURLFragment())
+ findNavController().navigate(actionDisclaimerFragmentToBaseURLFragment(true))
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -58,12 +58,7 @@ class DisclaimerFragment : Fragment(R.layout.fragment_disclaimer) {
}
viewModel.startCountDown()
activityViewModel.updateUiState {
- it.copy(
- loginButtonVisible = false,
- titleVisible = true,
- navigationVisible = false,
- searchVisible = false
- )
+ it.copy(navigationVisible = false, searchVisible = false, checkedMenuItemId = null)
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/recipes/RecipesListFragment.kt b/app/src/main/java/gq/kirmanak/mealient/ui/recipes/RecipesListFragment.kt
index 4752876..79d1452 100644
--- a/app/src/main/java/gq/kirmanak/mealient/ui/recipes/RecipesListFragment.kt
+++ b/app/src/main/java/gq/kirmanak/mealient/ui/recipes/RecipesListFragment.kt
@@ -50,10 +50,9 @@ class RecipesListFragment : Fragment(R.layout.fragment_recipes_list) {
logger.v { "onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState" }
activityViewModel.updateUiState {
it.copy(
- loginButtonVisible = true,
- titleVisible = false,
navigationVisible = true,
searchVisible = true,
+ checkedMenuItemId = R.id.recipes_list
)
}
setupRecipeAdapter()
@@ -62,8 +61,8 @@ class RecipesListFragment : Fragment(R.layout.fragment_recipes_list) {
@SuppressLint("ClickableViewAccessibility")
private fun hideKeyboardOnScroll() {
- binding.recipes.setOnTouchListener { view, _ ->
- view?.hideKeyboard()
+ binding.recipes.setOnTouchListener { _, _ ->
+ activityViewModel.clearSearchViewFocus()
false
}
}
@@ -85,9 +84,7 @@ class RecipesListFragment : Fragment(R.layout.fragment_recipes_list) {
private fun isNavigatingSomewhere(): Boolean {
logger.v { "isNavigatingSomewhere() called" }
- val label = findNavController().currentDestination?.label
- logger.d { "isNavigatingSomewhere: current destination is $label" }
- return label != "fragment_recipes"
+ return findNavController().currentDestination?.id != R.id.recipesListFragment
}
private fun setupRecipeAdapter() {
@@ -158,18 +155,12 @@ private fun Throwable.toLoadErrorReasonText(): Int? = when (this) {
}
private fun PagingDataAdapter.refreshErrors(): Flow {
- return loadStateFlow
- .map { it.refresh }
- .valueUpdatesOnly()
- .filterIsInstance()
+ return loadStateFlow.map { it.refresh }.valueUpdatesOnly().filterIsInstance()
.map { it.error }
}
private fun PagingDataAdapter.appendPaginationEnd(): Flow {
- return loadStateFlow
- .map { it.append.endOfPaginationReached }
- .valueUpdatesOnly()
- .filter { it }
+ return loadStateFlow.map { it.append.endOfPaginationReached }.valueUpdatesOnly().filter { it }
.map { }
}
diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoFragment.kt b/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoFragment.kt
index 92868be..aac4ad7 100644
--- a/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoFragment.kt
+++ b/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoFragment.kt
@@ -1,6 +1,5 @@
package gq.kirmanak.mealient.ui.recipes.info
-import android.app.Dialog
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@@ -8,10 +7,8 @@ import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.fragment.app.viewModels
import by.kirich1409.viewbindingdelegate.viewBinding
-import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import dagger.hilt.android.AndroidEntryPoint
-import gq.kirmanak.mealient.R
import gq.kirmanak.mealient.databinding.FragmentRecipeInfoBinding
import gq.kirmanak.mealient.logging.Logger
import gq.kirmanak.mealient.ui.recipes.images.RecipeImageLoader
@@ -71,9 +68,6 @@ class RecipeInfoFragment : BottomSheetDialogFragment() {
instructionsAdapter.submitList(uiState.recipeInstructions)
}
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
- BottomSheetDialog(requireContext(), R.style.NoShapeBottomSheetDialog)
-
override fun onDestroyView() {
super.onDestroyView()
logger.v { "onDestroyView() called" }
diff --git a/app/src/main/res/drawable-night/ic_splash_screen_background.xml b/app/src/main/res/drawable-night/ic_splash_screen_background.xml
new file mode 100644
index 0000000..ce6d767
--- /dev/null
+++ b/app/src/main/res/drawable-night/ic_splash_screen_background.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable-night/ic_splash_screen_foreground.xml b/app/src/main/res/drawable-night/ic_splash_screen_foreground.xml
new file mode 100644
index 0000000..575a54f
--- /dev/null
+++ b/app/src/main/res/drawable-night/ic_splash_screen_foreground.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_toolbar.xml b/app/src/main/res/drawable/bg_toolbar.xml
new file mode 100644
index 0000000..b123a8a
--- /dev/null
+++ b/app/src/main/res/drawable/bg_toolbar.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_add.xml b/app/src/main/res/drawable/ic_add.xml
new file mode 100644
index 0000000..f2bce58
--- /dev/null
+++ b/app/src/main/res/drawable/ic_add.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_change.xml b/app/src/main/res/drawable/ic_change.xml
new file mode 100644
index 0000000..4039c66
--- /dev/null
+++ b/app/src/main/res/drawable/ic_change.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
index bb1400d..edd4c77 100644
--- a/app/src/main/res/drawable/ic_launcher_background.xml
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -1,31 +1,13 @@
-
-
-
-
-
-
-
-
-
-
-
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml
index a135f3f..89108aa 100644
--- a/app/src/main/res/drawable/ic_launcher_foreground.xml
+++ b/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -1,35 +1,18 @@
-
-
-
-
-
-
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_monochrome.xml b/app/src/main/res/drawable/ic_launcher_monochrome.xml
new file mode 100644
index 0000000..424afd8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_monochrome.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_list.xml b/app/src/main/res/drawable/ic_list.xml
new file mode 100644
index 0000000..69c90d2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_list.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_login.xml b/app/src/main/res/drawable/ic_login.xml
new file mode 100644
index 0000000..9c9dedc
--- /dev/null
+++ b/app/src/main/res/drawable/ic_login.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_logout.xml b/app/src/main/res/drawable/ic_logout.xml
new file mode 100644
index 0000000..46542bc
--- /dev/null
+++ b/app/src/main/res/drawable/ic_logout.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_menu.xml b/app/src/main/res/drawable/ic_menu.xml
new file mode 100644
index 0000000..d632557
--- /dev/null
+++ b/app/src/main/res/drawable/ic_menu.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_splash_screen_background.xml b/app/src/main/res/drawable/ic_splash_screen_background.xml
index dcd8800..6f25d0f 100644
--- a/app/src/main/res/drawable/ic_splash_screen_background.xml
+++ b/app/src/main/res/drawable/ic_splash_screen_background.xml
@@ -1,31 +1,13 @@
-
-
-
-
-
-
-
-
-
+ android:scaleX="7.1"
+ android:scaleY="7.1">
+
diff --git a/app/src/main/res/drawable/ic_splash_screen_foreground.xml b/app/src/main/res/drawable/ic_splash_screen_foreground.xml
index 98a9788..a8efbdb 100644
--- a/app/src/main/res/drawable/ic_splash_screen_foreground.xml
+++ b/app/src/main/res/drawable/ic_splash_screen_foreground.xml
@@ -1,35 +1,18 @@
-
-
-
-
-
-
+ android:width="288dp"
+ android:height="288dp"
+ android:viewportWidth="288"
+ android:viewportHeight="288">
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_toolbar.xml b/app/src/main/res/drawable/ic_toolbar.xml
deleted file mode 100644
index 7c9f16f..0000000
--- a/app/src/main/res/drawable/ic_toolbar.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/drawable/placeholder_recipe.xml b/app/src/main/res/drawable/placeholder_recipe.xml
index 3097227..5d47c6b 100644
--- a/app/src/main/res/drawable/placeholder_recipe.xml
+++ b/app/src/main/res/drawable/placeholder_recipe.xml
@@ -1,19 +1,19 @@
-
-
-
-
+ android:width="200dp"
+ android:height="122dp"
+ android:viewportWidth="300"
+ android:viewportHeight="182">
+
+
+
+
diff --git a/app/src/main/res/drawable/recipe_info_background.xml b/app/src/main/res/drawable/recipe_info_background.xml
deleted file mode 100644
index a700ba3..0000000
--- a/app/src/main/res/drawable/recipe_info_background.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_disclaimer.xml b/app/src/main/res/layout/fragment_disclaimer.xml
index 15a2098..14267c2 100644
--- a/app/src/main/res/layout/fragment_disclaimer.xml
+++ b/app/src/main/res/layout/fragment_disclaimer.xml
@@ -2,6 +2,7 @@
@@ -12,12 +13,10 @@
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:layout_marginTop="40dp"
- app:cardElevation="8dp"
- app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/okay"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:shapeAppearance="@style/ShapeAppearance.AllCornersRounded">
+ app:layout_constraintTop_toTopOf="parent">
+ app:layout_constraintTop_toBottomOf="@+id/main_text_holder"
+ tools:text="Okay (3 seconds)" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_recipe_info.xml b/app/src/main/res/layout/fragment_recipe_info.xml
index e564fa7..7f5e8b8 100644
--- a/app/src/main/res/layout/fragment_recipe_info.xml
+++ b/app/src/main/res/layout/fragment_recipe_info.xml
@@ -33,22 +33,23 @@
@@ -102,7 +102,7 @@
android:id="@+id/ingredients_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginHorizontal="10dp"
+ android:layout_marginHorizontal="@dimen/margin_small"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:itemCount="3"
tools:listitem="@layout/view_holder_ingredient" />
@@ -121,7 +121,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="35dp"
- android:layout_marginEnd="8dp"
+ android:layout_marginEnd="@dimen/margin_small"
android:text="@string/fragment_recipe_info_instructions_header"
android:textAppearance="?textAppearanceHeadline6"
app:layout_constraintBottom_toTopOf="@+id/instructions_list"
@@ -133,11 +133,11 @@
android:id="@+id/instructions_list"
android:layout_width="0dp"
android:layout_height="wrap_content"
+ app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/end_guide"
- app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
- app:layout_constraintTop_toBottomOf="@+id/instructions_header"
app:layout_constraintStart_toEndOf="@+id/start_guide"
+ app:layout_constraintTop_toBottomOf="@+id/instructions_header"
tools:itemCount="2"
tools:listitem="@layout/view_holder_instruction" />
diff --git a/app/src/main/res/layout/main_activity.xml b/app/src/main/res/layout/main_activity.xml
index 2706507..684e7ba 100644
--- a/app/src/main/res/layout/main_activity.xml
+++ b/app/src/main/res/layout/main_activity.xml
@@ -3,6 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer"
+ style="?drawerLayoutStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.activity.MainActivity"
@@ -10,21 +11,20 @@
+ android:layout_height="match_parent">
-
@@ -33,6 +33,7 @@
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:layout_marginTop="@dimen/margin_small"
app:defaultNavHost="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
@@ -42,5 +43,6 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
+ app:headerLayout="@layout/view_navigation_drawer_header"
app:menu="@menu/navigation_menu" />
diff --git a/app/src/main/res/layout/view_holder_instruction.xml b/app/src/main/res/layout/view_holder_instruction.xml
index cee4dda..e633e30 100644
--- a/app/src/main/res/layout/view_holder_instruction.xml
+++ b/app/src/main/res/layout/view_holder_instruction.xml
@@ -2,11 +2,10 @@
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/margin_small">
+ android:layout_marginVertical="@dimen/margin_small"
+ android:layout_marginStart="@dimen/margin_medium"
+ android:layout_marginEnd="@dimen/margin_medium">
\ No newline at end of file
diff --git a/app/src/main/res/layout/view_navigation_drawer_header.xml b/app/src/main/res/layout/view_navigation_drawer_header.xml
new file mode 100644
index 0000000..cf380d8
--- /dev/null
+++ b/app/src/main/res/layout/view_navigation_drawer_header.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/view_toolbar.xml b/app/src/main/res/layout/view_toolbar.xml
new file mode 100644
index 0000000..dbb6bc7
--- /dev/null
+++ b/app/src/main/res/layout/view_toolbar.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/main_toolbar.xml b/app/src/main/res/menu/main_toolbar.xml
index 242a16e..1f07637 100644
--- a/app/src/main/res/menu/main_toolbar.xml
+++ b/app/src/main/res/menu/main_toolbar.xml
@@ -2,23 +2,10 @@
\ No newline at end of file
diff --git a/app/src/main/res/menu/navigation_menu.xml b/app/src/main/res/menu/navigation_menu.xml
index c5b3ea1..e896a64 100644
--- a/app/src/main/res/menu/navigation_menu.xml
+++ b/app/src/main/res/menu/navigation_menu.xml
@@ -2,13 +2,31 @@
\ No newline at end of file
diff --git a/app/src/main/res/mipmap/ic_launcher.xml b/app/src/main/res/mipmap/ic_launcher.xml
index eca70cf..b070c76 100644
--- a/app/src/main/res/mipmap/ic_launcher.xml
+++ b/app/src/main/res/mipmap/ic_launcher.xml
@@ -2,4 +2,5 @@
+
\ No newline at end of file
diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml
index cb5ca08..39e13e5 100644
--- a/app/src/main/res/navigation/nav_graph.xml
+++ b/app/src/main/res/navigation/nav_graph.xml
@@ -51,8 +51,10 @@
+ app:popUpTo="@id/nav_graph" />
+
+ app:destination="@id/authenticationFragment" />
+ app:destination="@id/recipesListFragment"
+ app:popUpTo="@id/nav_graph" />
-
\ No newline at end of file
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index fea499a..36e0879 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -5,7 +5,7 @@
URL сервера
Войти
Изображение готового блюда
- Выйти
+ Выйти
Загрузка
Ингредиенты
Инструкции
@@ -21,11 +21,11 @@
Что-то пошло не так, попробуйте еще раз.
Проверьте формат URL: %s
Продолжить
- Войти
+ Войти
Название рецепта
Описание
- Рецепты
- Добавить рецепт
+ Рецепты
+ Добавить рецепт
Количество порций
Сохранить рецепт
Добавить шаг
@@ -47,6 +47,7 @@
неожиданный ответ
нет соединения
Ошибка загрузки.
- Сменить URL
+ Сменить URL
Найти рецепты
+ Открыть меню навигации
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
deleted file mode 100644
index 904f3be..0000000
--- a/app/src/main/res/values/colors.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-
- #7743B5
- #FFFFFF
- #EFDBFF
- #290054
- #655A70
- #FFFFFF
- #ECDDF7
- #201829
- #805159
- #FFFFFF
- #FFD9DF
- #321118
- #BA1B1B
- #FFDAD4
- #FFFFFF
- #410001
- #FFFBFC
- #1D1B1E
- #FFFBFC
- #1D1B1E
- #E8DFEB
- #4A454E
- #7B757E
- #F5EFF3
- #322F33
- #DBB8FF
- #000000
- #DBB8FF
- #DBB8FF
- #460283
- #5E289B
- #EFDBFF
- #CFC1DA
- #362D40
- #4D4357
- #ECDDF7
- #F2B7C0
- #4B252C
- #653A42
- #FFD9DF
- #FFB4A9
- #930006
- #680003
- #FFDAD4
- #1D1B1E
- #E7E1E5
- #1D1B1E
- #E7E1E5
- #4A454E
- #CCC4CF
- #958E98
- #1D1B1E
- #E7E1E5
- #7743B5
- #000000
- #7743B5
-
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 01d1f49..3b149f6 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -1,8 +1,5 @@
8dp
- 182dp
- @dimen/height_view_holder_recipe_image
- 32dp
- 15dp
+ 16dp
\ 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 457b90e..6c8778f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -5,8 +5,7 @@
Server URL
Login
Picture of the cooked meal
- @string/menu_main_toolbar_logout
- Logout
+ Logout
Loading…
@string/content_description_view_holder_recipe_image
Ingredients
@@ -18,8 +17,7 @@
Check URL format: %s
Proceed
@string/fragment_authentication_unknown_error
- @string/menu_main_toolbar_login
- Login
+ Login
Okay
Step: %d
E-mail can\'t be empty
@@ -28,8 +26,8 @@
Something went wrong, please try again.
Recipe name
Description
- Add recipe
- Recipes
+ Add recipe
+ Recipes
Recipe yield
Save recipe
New step
@@ -51,6 +49,8 @@
unauthorized
unexpected response
no connection
- Change URL
+ Change URL
Search recipes
+ @string/app_name
+ Open navigation drawer
\ No newline at end of file
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 2add8a8..3e79bdf 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -13,22 +13,6 @@
- @dimen/margin_small
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+