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

This commit is contained in:
Jeas0001 2025-05-13 10:32:02 +02:00
commit 1dd98e1a05
17 changed files with 205 additions and 67 deletions

View File

@ -44,6 +44,10 @@
android:name=".ui.dishes.CreateDishActivity" android:name=".ui.dishes.CreateDishActivity"
android:exported="false" /> android:exported="false" />
<activity
android:name=".ui.dishes.CreateDishAIActivity"
android:exported="false" />
<activity <activity
android:name=".ui.profile.ChangePasswordActivity" android:name=".ui.profile.ChangePasswordActivity"
android:exported="false" /> android:exported="false" />

View File

@ -37,7 +37,7 @@ fun request(ctx: Context, method: String, path: String, data: String?, autoRefre
outputStream.write(data.toByteArray()) outputStream.write(data.toByteArray())
outputStream.flush() outputStream.flush()
} }
Log.i("http", responseCode.toString())
if (responseCode == 401) { if (responseCode == 401) {
if (!autoRefresh || refreshToken == null || !refreshAuthToken(ctx, refreshToken)) { if (!autoRefresh || refreshToken == null || !refreshAuthToken(ctx, refreshToken)) {
val intent = Intent(ctx, LoginActivity::class.java) val intent = Intent(ctx, LoginActivity::class.java)
@ -87,6 +87,14 @@ inline fun <reified Req, reified Res> requestJson(ctx: Context, method: String,
} }
} }
if (response.body.isBlank()) {
// Return Unit or an empty default value depending on Res
return when (Res::class) {
Unit::class -> Unit as Res
else -> throw ApiRequestException("Expected JSON but got empty response", null)
}
}
try { try {
return Json.decodeFromString<Res>(response.body) return Json.decodeFromString<Res>(response.body)
} catch (e: SerializationException) { } catch (e: SerializationException) {

View File

@ -1,8 +1,11 @@
package tech.mercantec.easyeat.helpers package tech.mercantec.easyeat.helpers
import android.content.ClipDescription
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.Direction
import tech.mercantec.easyeat.models.Ingredient
import tech.mercantec.easyeat.models.Recipe 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,12 +112,19 @@ 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 @Serializable
data class CreateRecipeRequest(val recipe: Recipe) data class CreateRecipeRequest(val name: String, val description: String, val directions: List<Direction>, val ingredients: List<Ingredient>)
fun createRecipe(ctx: Context, recipe: Recipe) { fun createRecipe(ctx: Context, recipe: Recipe) {
val request = CreateRecipeRequest(recipe) val request = CreateRecipeRequest(recipe.name, recipe.description, recipe.directions, recipe.ingredients)
return requestJson<CreateRecipeRequest, Unit>(ctx, "POST", "/api/recipe/create", request)
requestJson<CreateRecipeRequest, Boolean>(ctx, "POST", "/api/recipe/create", request)
}
@Serializable
data class RecipeResponse(val id: Int, val name: String, val description: String)
fun getRecipies(ctx: Context): List<RecipeResponse> {
return requestJson<Unit, List<RecipeResponse>>(ctx, "GET", "/api/Recipe/getall", null)
} }

View File

@ -1,3 +1,3 @@
package tech.mercantec.easyeat.models package tech.mercantec.easyeat.models
public data class Dish(val name: String, val mainIngredient: String, val expense: Double) public data class DishListItem(val id: Int,val name: String, val description: String)

View File

@ -34,10 +34,5 @@ class MainActivity : AppCompatActivity() {
) )
setupActionBarWithNavController(navController, appBarConfiguration) setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController) navView.setupWithNavController(navController)
findViewById<FloatingActionButton>(tech.mercantec.easyeat.R.id.add_dish).setOnClickListener {
val intent = Intent(this, CreateDishActivity::class.java)
startActivity(intent)
}
} }
} }

View File

@ -0,0 +1,16 @@
package tech.mercantec.easyeat.ui.dishes
import android.os.Bundle
import android.widget.LinearLayout
import androidx.appcompat.app.AppCompatActivity
import tech.mercantec.easyeat.R
class CreateDishAIActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.create_dish_ai_form)
}
}

View File

@ -1,5 +1,6 @@
package tech.mercantec.easyeat.ui.dishes package tech.mercantec.easyeat.ui.dishes
import android.app.Activity
import android.app.ProgressDialog import android.app.ProgressDialog
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
@ -60,16 +61,7 @@ class CreateDishActivity : AppCompatActivity() {
ingredients = ingredientList 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) val progressDialog = ProgressDialog(this)
progressDialog.setMessage("Loading...") progressDialog.setMessage("Loading...")
progressDialog.show() progressDialog.show()
@ -92,15 +84,10 @@ class CreateDishActivity : AppCompatActivity() {
Toast.makeText(this, "Password changed successfully", Toast.LENGTH_LONG).show() Toast.makeText(this, "Password changed successfully", Toast.LENGTH_LONG).show()
} }
setResult(Activity.RESULT_OK)
finish() finish()
} }
} }
} }
private fun addIngredientRow() { private fun addIngredientRow() {

View File

@ -7,22 +7,20 @@ import android.view.ViewGroup
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.TextView import android.widget.TextView
import tech.mercantec.easyeat.R import tech.mercantec.easyeat.R
import tech.mercantec.easyeat.models.Dish import tech.mercantec.easyeat.models.DishListItem
class DishAdapter(context: Context, dishes: Array<Dish>) : class DishAdapter(context: Context, dishes: List<DishListItem>) :
ArrayAdapter<Dish>(context, 0, dishes) { ArrayAdapter<DishListItem>(context, 0, dishes) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val dish = getItem(position) val dish = getItem(position)
val view = convertView ?: LayoutInflater.from(context).inflate(R.layout.dish_list_item, parent, false) val view = convertView ?: LayoutInflater.from(context).inflate(R.layout.dish_list_item, parent, false)
val nameTextView = view.findViewById<TextView>(R.id.dishName) val nameTextView = view.findViewById<TextView>(R.id.dishName)
val ingredientTextView = view.findViewById<TextView>(R.id.mainIngredient) val descriptionTextView = view.findViewById<TextView>(R.id.descriptionTextView)
val expenseTextView = view.findViewById<TextView>(R.id.expense)
nameTextView.text = "Name: ${dish?.name}" nameTextView.text = "Name: ${dish?.name}"
ingredientTextView.text = "Main Ingredient: ${dish?.mainIngredient}" descriptionTextView.text = "Description: ${dish?.description}"
expenseTextView.text = "Expense: ${dish?.expense} kr"
return view return view
} }

View File

@ -1,21 +1,33 @@
package tech.mercantec.easyeat.ui.dishes package tech.mercantec.easyeat.ui.dishes
import android.app.Activity
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.util.Log
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.Button
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import tech.mercantec.easyeat.R
import tech.mercantec.easyeat.databinding.FragmentDishesBinding import tech.mercantec.easyeat.databinding.FragmentDishesBinding
import tech.mercantec.easyeat.models.Dish import tech.mercantec.easyeat.helpers.ApiRequestException
import tech.mercantec.easyeat.helpers.RecipeResponse
import tech.mercantec.easyeat.helpers.getRecipies
import tech.mercantec.easyeat.helpers.login
import tech.mercantec.easyeat.models.DishListItem
import tech.mercantec.easyeat.ui.MainActivity
import kotlin.concurrent.thread
class DishesFragment : Fragment() { class DishesFragment : Fragment() {
private var _binding: FragmentDishesBinding? = null private var _binding: FragmentDishesBinding? = null
private lateinit var createDishLauncher: ActivityResultLauncher<Intent>
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!! private val binding get() = _binding!!
override fun onCreateView( override fun onCreateView(
@ -26,42 +38,72 @@ class DishesFragment : Fragment() {
_binding = FragmentDishesBinding.inflate(inflater, container, false) _binding = FragmentDishesBinding.inflate(inflater, container, false)
val root: View = binding.root val root: View = binding.root
binding.addDish.setOnClickListener { loadRecipes()
val intent = Intent(requireContext(), CreateDishActivity::class.java)
startActivity(intent)
}
context?.let { context -> binding.addDish.setOnClickListener {
binding.dishesList.setOnItemClickListener { parent, view, position, id -> val dialogView = LayoutInflater.from(requireContext()).inflate(R.layout.create_dish_modal_dialog, null)
val selectedItem = parent.getItemAtPosition(position) as Dish
Toast.makeText(context, "you selected $selectedItem.name that costs ${selectedItem.expense} kr.", Toast.LENGTH_LONG).show() val dialog = android.app.AlertDialog.Builder(requireContext())
.setView(dialogView)
.setCancelable(true) // tap outside to dismiss
.create()
dialog.window?.setBackgroundDrawableResource(android.R.color.transparent)
dialogView.findViewById<Button>(R.id.createManualBtn).setOnClickListener {
val intent = Intent(requireContext(), CreateDishActivity::class.java)
createDishLauncher.launch(intent)
dialog.dismiss()
} }
val listItems = arrayOf( dialogView.findViewById<Button>(R.id.createAIBtn).setOnClickListener {
Dish("Spaghetti Bolognese", "Beef", 70.5), val intent = Intent(requireContext(), CreateDishAIActivity::class.java)
Dish("Margherita Pizza", "Cheese", 60.0), createDishLauncher.launch(intent)
Dish("Chicken Curry", "Chicken", 80.2), dialog.dismiss()
Dish("Vegetable Stir Fry", "Mixed Vegetables", 50.5), }
Dish("Sushi", "Fish", 100.0),
Dish("Beef Tacos", "Beef", 60.8),
Dish("Lentil Soup", "Lentils", 40.5),
Dish("Pasta Alfredo", "Cream", 60.9),
Dish("Caesar Salad", "Chicken", 50.8),
Dish("Falafel Wrap", "Chickpeas", 50.2)
)
binding.dishesList.adapter = DishAdapter(context, listItems) dialog.show()
} }
return root return root
}
fun DishesFragment.loadRecipes() {
thread {
val recipes: List<RecipeResponse>
try {
recipes = getRecipies(requireContext())
} catch (e: ApiRequestException) {
activity?.runOnUiThread {
Toast.makeText(requireContext(), e.message, Toast.LENGTH_LONG).show()
}
return@thread
}
val dishes = recipes.map {
DishListItem(it.id, it.name, it.description)
}
activity?.runOnUiThread {
val adapter = DishAdapter(requireContext(), dishes)
binding.dishesList.adapter = adapter
}
}
} }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
createDishLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
loadRecipes()
}
}
}
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
_binding = null _binding = null
} }
} }

View File

@ -0,0 +1,4 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@android:color/white" />
<corners android:radius="16dp" />
</shape>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/formforai"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="FormForAI"
tools:layout_editor_absoluteX="126dp"
tools:layout_editor_absoluteY="287dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/dialog_root"
android:layout_width="250dp"
android:layout_height="400dp"
android:orientation="vertical"
android:background="@drawable/rounded_background"
android:padding="0dp"
android:gravity="center">
<TextView
android:layout_width="match_parent"
android:layout_height="40dp"
android:text="@string/create_dish"
android:textAlignment="center"
android:textSize="30sp"/>
<Button
android:id="@+id/createManualBtn"
android:layout_width="200dp"
android:layout_height="0dp"
android:layout_weight="1"
android:text="@string/manually" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#CCCCCC" />
<Button
android:id="@+id/createAIBtn"
android:layout_width="200dp"
android:layout_height="0dp"
android:layout_weight="1"
android:text="@string/search_for_dishes" />
</LinearLayout>

View File

@ -12,12 +12,8 @@
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<TextView <TextView
android:id="@+id/mainIngredient" android:id="@+id/descriptionTextView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<TextView
android:id="@+id/expense"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout> </LinearLayout>

View File

@ -41,6 +41,9 @@
<string name="checked_desc">Checked</string> <string name="checked_desc">Checked</string>
<string name="delete_label">Delete</string> <string name="delete_label">Delete</string>
<string name="empty_shopping_list">Your shopping list is empty</string> <string name="empty_shopping_list">Your shopping list is empty</string>
<string name="search_for_dishes">Search For Dishes</string>
<string name="manually">Manually</string>
<string name="create_dish">Create Dish</string>
<string-array name="units"> <string-array name="units">
<item></item> <item></item>
<item>g</item> <item>g</item>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">localhost</domain>
</domain-config>
</network-security-config>

View File

@ -23,7 +23,14 @@ namespace API.BusinessLogic
var recipes = await _dbAccess.ReadRecipes(userId); var recipes = await _dbAccess.ReadRecipes(userId);
if (recipes == null || recipes.Count == 0) { return new ConflictObjectResult(new { message = "Could not find any recipes" }); } if (recipes == null || recipes.Count == 0) { return new ConflictObjectResult(new { message = "Could not find any recipes" }); }
return new OkObjectResult(recipes); var recipeDtos = recipes.Select(r => new GetAllRecipesDTO
{
Id = r.Id,
Name = r.Name,
Description = r.Description
}).ToList();
return new OkObjectResult(recipeDtos);
} }
// Gets a specifik with recipe with the ingredient and directions // Gets a specifik with recipe with the ingredient and directions

View File

@ -0,0 +1,12 @@
namespace API.Models.RecipeModels
{
public class GetAllRecipesDTO
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
}