Merge branch 'master' of git.reim.ar:ReiMerc/easyeat

This commit is contained in:
Jeas0001 2025-05-08 10:22:56 +02:00
commit bf588c62ed
19 changed files with 330 additions and 24 deletions

View File

@ -1,6 +1,7 @@
<component name="ProjectCodeStyleConfiguration"> <component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173"> <code_scheme name="Project" version="173">
<JetCodeStyleSettings> <JetCodeStyleSettings>
<option name="ALLOW_TRAILING_COMMA" value="true" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings> </JetCodeStyleSettings>
<codeStyleSettings language="XML"> <codeStyleSettings language="XML">

View File

@ -3,7 +3,7 @@ package tech.mercantec.easyeat.helpers
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import tech.mercantec.easyeat.models.Recipe
@Serializable @Serializable
data class LoginRequest(val emailUsr: String, val password: String) data class LoginRequest(val emailUsr: String, val password: String)
@ -109,3 +109,12 @@ fun changePassword(ctx: Context, oldPassword: String, newPassword: String) {
return requestJson<ChangePasswordRequest, Unit>(ctx, "PUT", "/api/User/change-password", request) return requestJson<ChangePasswordRequest, Unit>(ctx, "PUT", "/api/User/change-password", request)
} }
@Serializable
data class CreateRecipeRequest(val recipe: Recipe)
fun createRecipe(ctx: Context, recipe: Recipe) {
val request = CreateRecipeRequest(recipe)
return requestJson<CreateRecipeRequest, Unit>(ctx, "POST", "/api/recipe/create", request)
}

View File

@ -0,0 +1,22 @@
package tech.mercantec.easyeat.helpers
import android.content.Context
import kotlinx.serialization.Serializable
import tech.mercantec.easyeat.models.ShoppingListItem
fun getShoppingList(ctx: Context): Array<ShoppingListItem> {
return requestJson<Unit, Array<ShoppingListItem>>(ctx, "GET", "/api/ShoppingList/get", null)
}
@Serializable
data class AddShoppingItemRequest(val name: String, val amount: Double?, val unit: String?, val checked: Boolean)
fun addShoppingItem(ctx: Context, name: String, amount: Double?, unit: String?) {
val request = AddShoppingItemRequest(name, amount, unit, false)
requestJson<AddShoppingItemRequest, Boolean>(ctx, "POST", "/api/ShoppingList/add", request)
}
fun toggleShoppingItemChecked(ctx: Context, item: ShoppingListItem) {
requestJson<Unit, Boolean>(ctx, "PUT", "/api/ShoppingList/check?itemId=${item.id}", null)
}

View File

@ -1,7 +0,0 @@
package tech.mercantec.easyeat.models
data class Ingredient(
val Amount: Int,
val Unit: String,
val Element: String
)

View File

@ -0,0 +1,6 @@
package tech.mercantec.easyeat.models
import kotlinx.serialization.Serializable
@Serializable
data class ShoppingListItem(val id: Int, var name: String, var amount: Double?, var unit: String?, var checked: Boolean)

View File

@ -0,0 +1,24 @@
package tech.mercantec.easyeat.models
import kotlinx.serialization.Serializable
@Serializable
data class Recipe(
val name: String,
val description: String,
val directions: List<Direction>,
val ingredients: List<Ingredient>
)
@Serializable
data class Direction(
val instructions: String
)
@Serializable
data class Ingredient(
val amount: Double?,
val unit: String?,
val name: String
)

View File

@ -1,5 +1,7 @@
package tech.mercantec.easyeat.ui.dishes package tech.mercantec.easyeat.ui.dishes
import android.app.ProgressDialog
import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
@ -9,9 +11,18 @@ import android.widget.EditText
import android.widget.ImageButton import android.widget.ImageButton
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.Spinner import android.widget.Spinner
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContentProviderCompat.requireContext
import tech.mercantec.easyeat.R import tech.mercantec.easyeat.R
import tech.mercantec.easyeat.helpers.ApiRequestException
import tech.mercantec.easyeat.helpers.changePassword
import tech.mercantec.easyeat.helpers.createRecipe
import tech.mercantec.easyeat.helpers.request
import tech.mercantec.easyeat.models.Direction
import tech.mercantec.easyeat.models.Ingredient import tech.mercantec.easyeat.models.Ingredient
import tech.mercantec.easyeat.models.Recipe
import kotlin.concurrent.thread
class CreateDishActivity : AppCompatActivity() { class CreateDishActivity : AppCompatActivity() {
@ -35,16 +46,61 @@ class CreateDishActivity : AppCompatActivity() {
val saveButton: Button = findViewById(R.id.saveDishButton) val saveButton: Button = findViewById(R.id.saveDishButton)
saveButton.setOnClickListener { saveButton.setOnClickListener {
val ingredientList = collectIngredients() val ingredientList = collectIngredients()
val name = findViewById<EditText>(R.id.dishName).text.toString().trim()
val description = findViewById<EditText>(R.id.dishDescription).text.toString().trim()
val instructions = findViewById<EditText>(R.id.instructions).text.toString().trim() val instructions = findViewById<EditText>(R.id.instructions).text.toString().trim()
// Debug/log example val directions: List<Direction> = instructions
for (ingredient in ingredientList) { .split("\n")
Log.d("INGREDIENT", "Name: ${ingredient.Element}, Amount: ${ingredient.Amount}, Unit: ${ingredient.Unit}") .map { line -> line.trim() }
.filter { it.isNotEmpty() }
.map { line -> Direction(instructions = line) }
val recipe = Recipe(
name = findViewById<EditText>(R.id.dishName).text.toString().trim(),
description = findViewById<EditText>(R.id.dishDescription).text.toString().trim(),
directions = directions,
ingredients = ingredientList
)
Log.i("recipe name:", recipe.name)
Log.i("recipe name:", recipe.description)
for (x in recipe.directions) {
Log.i("recipe name:", x.instructions)
}
for(x in recipe.ingredients){
Log.i("recipe name:", x.name)
Log.i("recipe name:", x.amount.toString())
Log.i("recipe name:", x.unit.toString())
}
val progressDialog = ProgressDialog(this)
progressDialog.setMessage("Loading...")
progressDialog.show()
thread {
try {
createRecipe(this, recipe)
} catch (e: ApiRequestException) {
runOnUiThread {
Toast.makeText(this, e.message, Toast.LENGTH_LONG).show()
}
return@thread
} finally {
runOnUiThread {
progressDialog.hide()
}
}
runOnUiThread {
Toast.makeText(this, "Password changed successfully", Toast.LENGTH_LONG).show()
}
finish()
} }
} }
} }
private fun addIngredientRow() { private fun addIngredientRow() {
@ -84,7 +140,7 @@ class CreateDishActivity : AppCompatActivity() {
// Optional: Only add non-empty rows // Optional: Only add non-empty rows
if (element.isNotEmpty() && amount.isNotEmpty()) { if (element.isNotEmpty() && amount.isNotEmpty()) {
ingredients.add(Ingredient(Element = element, Amount = amount.toInt(), Unit = unit)) ingredients.add(Ingredient(name = element, amount = amount.toDouble(), unit = unit))
} }
} }

View File

@ -0,0 +1,60 @@
package tech.mercantec.easyeat.ui.shopping_list
import android.content.Context
import android.graphics.Paint
import android.icu.text.DecimalFormat
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import tech.mercantec.easyeat.R
import tech.mercantec.easyeat.models.ShoppingListItem
class ShoppingItemAdapter(context: Context, items: ArrayList<ShoppingListItem>)
: ArrayAdapter<ShoppingListItem>(context, R.layout.shopping_list_item, items) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val item = getItem(position)
val view = convertView ?: LayoutInflater.from(context).inflate(R.layout.shopping_list_item, parent, false)
item?.let { item ->
val checkmarkView = view.findViewById<ImageView>(R.id.checkmark)
val amountView = view.findViewById<TextView>(R.id.amount)
val unitView = view.findViewById<TextView>(R.id.unit)
val nameView = view.findViewById<TextView>(R.id.name)
val textViews = listOf(amountView, unitView, nameView)
amountView.text = DecimalFormat("#.##").format(item.amount)
unitView.text = item.unit
nameView.text = item.name
if (item.checked) {
textViews.forEach {
it.paintFlags = it.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
val color = TypedValue()
context.theme.resolveAttribute(R.attr.colorDisabled, color, true)
it.setTextColor(ContextCompat.getColor(context, color.resourceId))
checkmarkView.visibility = View.VISIBLE
}
} else {
textViews.forEach {
it.paintFlags = it.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
val color = TypedValue()
context.theme.resolveAttribute(android.R.attr.textColorSecondary, color, true)
it.setTextColor(ContextCompat.getColor(context, color.resourceId))
checkmarkView.visibility = View.INVISIBLE
}
}
}
return view
}
}

View File

@ -1,15 +1,26 @@
package tech.mercantec.easyeat.ui.shopping_list package tech.mercantec.easyeat.ui.shopping_list
import android.app.AlertDialog import android.app.AlertDialog
import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.EditText
import android.widget.Spinner import android.widget.Spinner
import android.widget.Toast
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import tech.mercantec.easyeat.R import tech.mercantec.easyeat.R
import tech.mercantec.easyeat.databinding.FragmentShoppingListBinding import tech.mercantec.easyeat.databinding.FragmentShoppingListBinding
import tech.mercantec.easyeat.helpers.ApiRequestException
import tech.mercantec.easyeat.helpers.addShoppingItem
import tech.mercantec.easyeat.helpers.getShoppingList
import tech.mercantec.easyeat.helpers.toggleShoppingItemChecked
import tech.mercantec.easyeat.models.Dish
import tech.mercantec.easyeat.models.ShoppingListItem
import java.util.ArrayList
import kotlin.concurrent.thread
class ShoppingListFragment : Fragment() { class ShoppingListFragment : Fragment() {
@ -27,17 +38,73 @@ class ShoppingListFragment : Fragment() {
_binding = FragmentShoppingListBinding.inflate(inflater, container, false) _binding = FragmentShoppingListBinding.inflate(inflater, container, false)
val root: View = binding.root val root: View = binding.root
thread {
val items: Array<ShoppingListItem>
try {
items = getShoppingList(requireContext())
} catch (e: ApiRequestException) {
activity?.runOnUiThread {
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
}
return@thread
}
activity?.runOnUiThread {
binding.shoppingList.adapter = ShoppingItemAdapter(requireContext(), ArrayList(items.toMutableList()))
}
}
binding.shoppingList.setOnItemClickListener { parent, view, position, id ->
val item = parent.getItemAtPosition(position) as ShoppingListItem
item.checked = !item.checked
thread {
try {
toggleShoppingItemChecked(requireContext(), item)
} catch (e: ApiRequestException) {
Toast.makeText(requireContext(), e.message, Toast.LENGTH_LONG).show()
}
}
val adapter = parent.adapter as ShoppingItemAdapter
adapter.remove(item)
adapter.insert(item, position)
}
binding.addToShoppingList.setOnClickListener { binding.addToShoppingList.setOnClickListener {
val view = requireActivity().layoutInflater.inflate(R.layout.dialog_add_to_shopping_list, null) val view = requireActivity().layoutInflater.inflate(R.layout.dialog_add_to_shopping_list, null)
val dialog = AlertDialog.Builder(activity) val dialog = AlertDialog.Builder(activity)
.setView(view) .setView(view)
.setPositiveButton(R.string.add_label, { dialog, id -> .setPositiveButton(R.string.add_label) { dialog, id ->
val dialog = dialog as AlertDialog
}) val amount = view.findViewById<EditText>(R.id.amount).text.toString().toDouble()
.setNegativeButton(R.string.cancel_label, { dialog, id -> val unit = view.findViewById<Spinner>(R.id.unit_selector).selectedItem.toString().ifEmpty { null }
val name = view.findViewById<EditText>(R.id.name).text.toString()
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).isEnabled = false
thread {
try {
addShoppingItem(requireContext(), name, amount, unit)
} catch (e: ApiRequestException) {
activity?.runOnUiThread {
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
}
} finally {
activity?.runOnUiThread {
dialog.dismiss()
}
}
}
}
.setNegativeButton(R.string.cancel_label) { dialog, _ ->
dialog.cancel() dialog.cancel()
}) }
.create() .create()
dialog.show() dialog.show()

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M382,720L154,492L211,435L382,606L749,239L806,296L382,720Z"/>
</vector>

View File

@ -2,11 +2,9 @@
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container" android:id="@+id/container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:paddingTop="?attr/actionBarSize">
<fragment <fragment
android:id="@+id/nav_host_fragment_activity_main" android:id="@+id/nav_host_fragment_activity_main"

View File

@ -30,12 +30,14 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/amount_label" android:layout_below="@id/amount_label"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:inputType="numberDecimal"
android:importantForAutofill="no"
android:hint="@string/ingredient_amount_hint" android:hint="@string/ingredient_amount_hint"
/> />
<Spinner <Spinner
android:id="@+id/unit_selector" android:id="@+id/unit_selector"
android:layout_width="72dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_toEndOf="@id/amount" android:layout_toEndOf="@id/amount"
android:layout_below="@+id/amount_label" android:layout_below="@+id/amount_label"
@ -57,6 +59,8 @@
android:layout_below="@id/name_label" android:layout_below="@id/name_label"
android:layout_toEndOf="@id/unit_selector" android:layout_toEndOf="@id/unit_selector"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:inputType="text"
android:importantForAutofill="no"
android:hint="@string/ingredient_name_hint" android:hint="@string/ingredient_name_hint"
/> />

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal"
android:padding="12dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/checkmark"
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@drawable/ic_check_24px"
app:tint="@color/green"
android:contentDescription="@string/checked_desc"
/>
<TextView
android:id="@+id/amount"
android:layout_width="70dp"
android:layout_height="wrap_content"
android:textSize="24sp"
android:textAlignment="textEnd"
android:textStyle="bold"
/>
<TextView
android:id="@+id/unit"
android:layout_marginStart="3dp"
android:layout_width="70dp"
android:layout_height="wrap_content"
android:textSize="24sp"
android:textStyle="bold"
/>
<TextView
android:id="@+id/name"
android:layout_marginStart="3dp"
android:textSize="24sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>

View File

@ -4,6 +4,7 @@
<item name="colorPrimaryVariant">@color/dark_cyan</item> <item name="colorPrimaryVariant">@color/dark_cyan</item>
<item name="colorOnPrimary">@color/white</item> <item name="colorOnPrimary">@color/white</item>
<item name="colorSecondary">@color/dark_gray</item> <item name="colorSecondary">@color/dark_gray</item>
<item name="colorDisabled">@color/dark_gray</item>
<item name="colorSurface">@color/dark_cyan</item> <item name="colorSurface">@color/dark_cyan</item>
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item> <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<item name="android:colorBackground">@color/black</item> <item name="android:colorBackground">@color/black</item>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="EasyEat">
<attr name="colorDisabled" format="color" />
</declare-styleable>
</resources>

View File

@ -7,4 +7,5 @@
<color name="black">#FF242424</color> <color name="black">#FF242424</color>
<color name="white">#FFF9F9F9</color> <color name="white">#FFF9F9F9</color>
<color name="red">#D62D2D </color> <color name="red">#D62D2D </color>
<color name="green">#4CAF50</color>
</resources> </resources>

View File

@ -38,7 +38,9 @@
<string name="ingredient_name_hint">Beef</string> <string name="ingredient_name_hint">Beef</string>
<string name="change_password_label">Change password</string> <string name="change_password_label">Change password</string>
<string name="logout_label">Log out</string> <string name="logout_label">Log out</string>
<string name="checked_desc">Checked</string>
<string-array name="units"> <string-array name="units">
<item></item>
<item>g</item> <item>g</item>
<item>kg</item> <item>kg</item>
<item>ml</item> <item>ml</item>

View File

@ -4,6 +4,7 @@
<item name="colorPrimaryVariant">@color/dark_cyan</item> <item name="colorPrimaryVariant">@color/dark_cyan</item>
<item name="colorOnPrimary">@color/white</item> <item name="colorOnPrimary">@color/white</item>
<item name="colorSecondary">@color/dark_gray</item> <item name="colorSecondary">@color/dark_gray</item>
<item name="colorDisabled">@color/gray</item>
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item> <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<item name="android:colorBackground">@color/white</item> <item name="android:colorBackground">@color/white</item>
</style> </style>

View File

@ -52,7 +52,7 @@ namespace API.Controllers
/// <param name="recipe">The recipe to be added</param> /// <param name="recipe">The recipe to be added</param>
/// <returns>returns a okobjectresult with a boolean that is true if it fails it returns a confliftobjectresult with a message of why it failed</returns> /// <returns>returns a okobjectresult with a boolean that is true if it fails it returns a confliftobjectresult with a message of why it failed</returns>
[Authorize] [Authorize]
[HttpPost("create/{RecipesId}")] [HttpPost("create")]
public async Task<IActionResult> CreateRecipe([FromBody] RecipeDTO recipe) public async Task<IActionResult> CreateRecipe([FromBody] RecipeDTO recipe)
{ {
var claims = HttpContext.User.Claims; var claims = HttpContext.User.Claims;