diff --git a/app/app/src/main/AndroidManifest.xml b/app/app/src/main/AndroidManifest.xml index 215816d..b07b3bd 100644 --- a/app/app/src/main/AndroidManifest.xml +++ b/app/app/src/main/AndroidManifest.xml @@ -25,8 +25,7 @@ + android:exported="false" /> { + val ingredients = mutableListOf() + + for (i in 0 until ingredientContainer.childCount) { + val ingredientView = ingredientContainer.getChildAt(i) + + // Find views inside this ingredient row + val nameEditText = ingredientView.findViewById(R.id.ingredientNameEditText) + val amountEditText = ingredientView.findViewById(R.id.ingredientAmountEditText) + val spinner = ingredientView.findViewById(R.id.spinner) + + val element = nameEditText.text.toString().trim() + val amount = amountEditText.text.toString().trim() + val unit = spinner.selectedItem.toString() + + // Optional: Only add non-empty rows + if (element.isNotEmpty() && amount.isNotEmpty()) { + ingredients.add(Ingredient(Element = element, Amount = amount.toInt(), Unit = unit)) + } + } + + return ingredients + } + } diff --git a/app/app/src/main/java/tech/mercantec/easyeat/LoginActivity.kt b/app/app/src/main/java/tech/mercantec/easyeat/LoginActivity.kt index 3091da5..a56504a 100644 --- a/app/app/src/main/java/tech/mercantec/easyeat/LoginActivity.kt +++ b/app/app/src/main/java/tech/mercantec/easyeat/LoginActivity.kt @@ -27,7 +27,7 @@ class LoginActivity : AppCompatActivity() { thread { try { - login(email, password) + login(this, email, password) } catch (e: ApiRequestException) { runOnUiThread { Toast.makeText(this, e.message, Toast.LENGTH_LONG).show() diff --git a/app/app/src/main/java/tech/mercantec/easyeat/MainActivity.kt b/app/app/src/main/java/tech/mercantec/easyeat/MainActivity.kt index 8355af8..8675456 100644 --- a/app/app/src/main/java/tech/mercantec/easyeat/MainActivity.kt +++ b/app/app/src/main/java/tech/mercantec/easyeat/MainActivity.kt @@ -12,8 +12,10 @@ import androidx.navigation.ui.setupActionBarWithNavController import androidx.navigation.ui.setupWithNavController import com.google.android.material.floatingactionbutton.FloatingActionButton import tech.mercantec.easyeat.databinding.ActivityMainBinding +import tech.mercantec.easyeat.helpers.getUserInfo import tech.mercantec.easyeat.models.Dish import tech.mercantec.easyeat.ui.dishes.DishAdapter +import kotlin.concurrent.thread class MainActivity : AppCompatActivity() { @@ -30,17 +32,15 @@ class MainActivity : AppCompatActivity() { val navController = findNavController(R.id.nav_host_fragment_activity_main) val appBarConfiguration = AppBarConfiguration( setOf( - R.id.navigation_dishes, R.id.navigation_shopping_list, R.id.navigation_notifications + R.id.navigation_dishes, R.id.navigation_shopping_list, R.id.navigation_profile ) ) setupActionBarWithNavController(navController, appBarConfiguration) navView.setupWithNavController(navController) - findViewById(R.id.add_dish).setOnClickListener { val intent = Intent(this, CreateDishActivity::class.java) startActivity(intent) } - } } diff --git a/app/app/src/main/java/tech/mercantec/easyeat/RegisterActivity.kt b/app/app/src/main/java/tech/mercantec/easyeat/RegisterActivity.kt index 52059b7..d3c0037 100644 --- a/app/app/src/main/java/tech/mercantec/easyeat/RegisterActivity.kt +++ b/app/app/src/main/java/tech/mercantec/easyeat/RegisterActivity.kt @@ -34,7 +34,7 @@ class RegisterActivity : AppCompatActivity() { thread { try { - register(email, username, password) + register(this, email, username, password) } catch (e: ApiRequestException) { runOnUiThread { Toast.makeText(this, e.message, Toast.LENGTH_LONG).show() diff --git a/app/app/src/main/java/tech/mercantec/easyeat/SplashActivity.kt b/app/app/src/main/java/tech/mercantec/easyeat/SplashActivity.kt index 414bc9a..d6141ce 100644 --- a/app/app/src/main/java/tech/mercantec/easyeat/SplashActivity.kt +++ b/app/app/src/main/java/tech/mercantec/easyeat/SplashActivity.kt @@ -3,15 +3,13 @@ package tech.mercantec.easyeat import android.app.Activity import android.content.Intent import android.os.Bundle +import tech.mercantec.easyeat.helpers.isLoggedIn class SplashActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - // TODO do auth check - val loggedIn = false - - if (!loggedIn) { + if (!isLoggedIn(this)) { startActivity(Intent(this, WelcomeActivity::class.java)) return diff --git a/app/app/src/main/java/tech/mercantec/easyeat/helpers/api.kt b/app/app/src/main/java/tech/mercantec/easyeat/helpers/api.kt index 0adb879..e9a472d 100644 --- a/app/app/src/main/java/tech/mercantec/easyeat/helpers/api.kt +++ b/app/app/src/main/java/tech/mercantec/easyeat/helpers/api.kt @@ -1,6 +1,7 @@ package tech.mercantec.easyeat.helpers import android.content.Context +import android.content.Intent import android.util.Log import android.widget.Toast import kotlinx.serialization.Serializable @@ -10,19 +11,27 @@ import java.net.HttpURLConnection import java.net.URL import kotlinx.serialization.json.* import kotlinx.serialization.serializer +import tech.mercantec.easyeat.LoginActivity import java.io.IOException class ApiRequestException(message: String, cause: Throwable?) : Exception(message, cause) class HttpResponse(val body: String, val code: Int) -fun request(method: String, path: String, data: String?): HttpResponse { +fun request(ctx: Context, method: String, path: String, data: String?, autoRefresh: Boolean = true): HttpResponse { val url = URL(BuildConfig.API_BASE_URL + path) + val prefs = ctx.getSharedPreferences("easyeat", Context.MODE_PRIVATE) + val authToken = prefs.getString("auth-token", null) + val refreshToken = prefs.getString("auth-token", null) + try { with(url.openConnection() as HttpURLConnection) { requestMethod = method + if (authToken != null) + setRequestProperty("Authorization", "Bearer $authToken") + if (data != null) { setRequestProperty("Content-Type", "application/json") @@ -30,6 +39,18 @@ fun request(method: String, path: String, data: String?): HttpResponse { outputStream.flush() } + if (responseCode == 401 && refreshToken != null) { + if (!autoRefresh || !refreshAuthToken(ctx, refreshToken)) { + val intent = Intent(ctx, LoginActivity::class.java) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + ctx.startActivity(intent) + + throw ApiRequestException("Please sign in again", null) + } + + return request(ctx, method, path, data, false) + } + if (responseCode >= 400) return HttpResponse(errorStream.readBytes().decodeToString(), responseCode) @@ -45,13 +66,13 @@ fun request(method: String, path: String, data: String?): HttpResponse { @Serializable class HttpErrorResponse(val message: String) -inline fun requestJson(method: String, path: String, data: Req?): Res { +inline fun requestJson(ctx: Context, method: String, path: String, data: Req?): Res { val requestJson = if (data != null) Json.encodeToString(serializer(), data) else null - val response = request(method, path, requestJson) + val response = request(ctx, method, path, requestJson) if (response.code >= 400) { try { diff --git a/app/app/src/main/java/tech/mercantec/easyeat/helpers/auth.kt b/app/app/src/main/java/tech/mercantec/easyeat/helpers/auth.kt index 0467bc4..6e5e54b 100644 --- a/app/app/src/main/java/tech/mercantec/easyeat/helpers/auth.kt +++ b/app/app/src/main/java/tech/mercantec/easyeat/helpers/auth.kt @@ -1,6 +1,7 @@ package tech.mercantec.easyeat.helpers import android.content.Context +import android.util.Log import kotlinx.serialization.Serializable @Serializable @@ -9,20 +10,76 @@ data class LoginRequest(val emailUsr: String, val password: String) @Serializable data class LoginResponse(val token: String, val userName: String, val id: Int, val refreshToken: String) -fun login(email: String, password: String) { +fun login(ctx: Context, email: String, password: String) { val request = LoginRequest(email, password) - val response = requestJson("POST", "/api/User/login", request) - - // TODO save tokens + val response = requestJson(ctx, "POST", "/api/User/login", request) + with (ctx.getSharedPreferences("easyeat", Context.MODE_PRIVATE).edit()) { + putInt("user-id", response.id) + putString("username", response.userName) + putString("auth-token", response.token) + putString("refresh-token", response.refreshToken) + apply() + } } @Serializable data class CreateUserRequest(val email: String, val userName: String, val password: String) -fun register(email: String, username: String, password: String) { +fun register(ctx: Context, email: String, username: String, password: String) { val request = CreateUserRequest(email, username, password) - requestJson("POST", "/api/User/create", request) + requestJson(ctx, "POST", "/api/User/create", request) +} + +@Serializable +data class RefreshTokenRequest(val refreshToken: String) + +@Serializable +data class RefreshTokenResponse(val token: String, val refreshToken: String) + +fun refreshAuthToken(ctx: Context, refreshToken: String): Boolean { + val response: RefreshTokenResponse + try { + response = requestJson( + ctx, + "POST", "/api/User/refreshtoken", + RefreshTokenRequest(refreshToken) + ) + } catch (e: ApiRequestException) { + if (e.message != null) + Log.e("EasyEat", e.message!!) + + return false + } + + with (ctx.getSharedPreferences("easyeat", Context.MODE_PRIVATE).edit()) { + putString("auth-token", response.token) + putString("refresh-token", response.refreshToken) + apply() + } + + return true +} + +fun isLoggedIn(ctx: Context): Boolean { + val prefs = ctx.getSharedPreferences("easyeat", Context.MODE_PRIVATE) + val authToken = prefs.getString("auth-token", null) + val refreshToken = prefs.getString("refresh-token", null) ?: return false + + if (authToken == null) { + return refreshAuthToken(ctx, refreshToken) + } + + return true +} + +@Serializable +data class UserInfoResponse(val id: Int, val userName: String, val email: String) + +fun getUserInfo(ctx: Context): UserInfoResponse { + val response = requestJson(ctx, "GET", "/api/User/get", null) + + return response } diff --git a/app/app/src/main/java/tech/mercantec/easyeat/models/Ingredient.kt b/app/app/src/main/java/tech/mercantec/easyeat/models/Ingredient.kt new file mode 100644 index 0000000..7413734 --- /dev/null +++ b/app/app/src/main/java/tech/mercantec/easyeat/models/Ingredient.kt @@ -0,0 +1,7 @@ +package tech.mercantec.easyeat.models + +data class Ingredient( + val Amount: Int, + val Unit: String, + val Element: String +) \ No newline at end of file diff --git a/app/app/src/main/java/tech/mercantec/easyeat/ui/dishes/DishesFragment.kt b/app/app/src/main/java/tech/mercantec/easyeat/ui/dishes/DishesFragment.kt index c54aeda..72feca8 100644 --- a/app/app/src/main/java/tech/mercantec/easyeat/ui/dishes/DishesFragment.kt +++ b/app/app/src/main/java/tech/mercantec/easyeat/ui/dishes/DishesFragment.kt @@ -1,11 +1,13 @@ package tech.mercantec.easyeat.ui.dishes +import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Toast import androidx.fragment.app.Fragment +import tech.mercantec.easyeat.CreateDishActivity import tech.mercantec.easyeat.databinding.FragmentDishesBinding import tech.mercantec.easyeat.models.Dish @@ -25,6 +27,11 @@ class DishesFragment : Fragment() { _binding = FragmentDishesBinding.inflate(inflater, container, false) val root: View = binding.root + binding.addDish.setOnClickListener { + val intent = Intent(requireContext(), CreateDishActivity::class.java) + startActivity(intent) + } + context?.let { context -> binding.dishesList.setOnItemClickListener { parent, view, position, id -> val selectedItem = parent.getItemAtPosition(position) as Dish @@ -47,9 +54,13 @@ class DishesFragment : Fragment() { binding.dishesList.adapter = DishAdapter(context, listItems) } + return root } + + + override fun onDestroyView() { super.onDestroyView() _binding = null diff --git a/app/app/src/main/java/tech/mercantec/easyeat/ui/notifications/NotificationsFragment.kt b/app/app/src/main/java/tech/mercantec/easyeat/ui/notifications/NotificationsFragment.kt deleted file mode 100644 index d84d797..0000000 --- a/app/app/src/main/java/tech/mercantec/easyeat/ui/notifications/NotificationsFragment.kt +++ /dev/null @@ -1,42 +0,0 @@ -package tech.mercantec.easyeat.ui.notifications - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.TextView -import androidx.fragment.app.Fragment -import androidx.lifecycle.ViewModelProvider -import tech.mercantec.easyeat.databinding.FragmentNotificationsBinding - -class NotificationsFragment : Fragment() { - - private var _binding: FragmentNotificationsBinding? = null - - // This property is only valid between onCreateView and - // onDestroyView. - private val binding get() = _binding!! - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - val notificationsViewModel = - ViewModelProvider(this).get(NotificationsViewModel::class.java) - - _binding = FragmentNotificationsBinding.inflate(inflater, container, false) - val root: View = binding.root - - val textView: TextView = binding.textNotifications - notificationsViewModel.text.observe(viewLifecycleOwner) { - textView.text = it - } - return root - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } -} \ No newline at end of file diff --git a/app/app/src/main/java/tech/mercantec/easyeat/ui/notifications/NotificationsViewModel.kt b/app/app/src/main/java/tech/mercantec/easyeat/ui/notifications/NotificationsViewModel.kt deleted file mode 100644 index 3575983..0000000 --- a/app/app/src/main/java/tech/mercantec/easyeat/ui/notifications/NotificationsViewModel.kt +++ /dev/null @@ -1,13 +0,0 @@ -package tech.mercantec.easyeat.ui.notifications - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel - -class NotificationsViewModel : ViewModel() { - - private val _text = MutableLiveData().apply { - value = "This is notifications Fragment" - } - val text: LiveData = _text -} \ No newline at end of file diff --git a/app/app/src/main/java/tech/mercantec/easyeat/ui/profile/ProfileFragment.kt b/app/app/src/main/java/tech/mercantec/easyeat/ui/profile/ProfileFragment.kt new file mode 100644 index 0000000..506f555 --- /dev/null +++ b/app/app/src/main/java/tech/mercantec/easyeat/ui/profile/ProfileFragment.kt @@ -0,0 +1,39 @@ +package tech.mercantec.easyeat.ui.profile + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.fragment.app.Fragment +import tech.mercantec.easyeat.databinding.FragmentProfileBinding +import tech.mercantec.easyeat.helpers.ApiRequestException +import tech.mercantec.easyeat.helpers.UserInfoResponse +import tech.mercantec.easyeat.helpers.getUserInfo +import kotlin.concurrent.thread + +class ProfileFragment : Fragment() { + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val binding = FragmentProfileBinding.inflate(inflater, container, false) + + thread { + val userInfo: UserInfoResponse + try { + userInfo = getUserInfo(requireContext()) + } catch (e: ApiRequestException) { + activity?.runOnUiThread { + Toast.makeText(activity?.applicationContext, e.message, Toast.LENGTH_LONG).show() + } + + return@thread + } + + activity?.runOnUiThread { + binding.username.text = userInfo.userName + binding.email.text = userInfo.email + } + } + + return binding.root + } +} diff --git a/app/app/src/main/res/drawable/ic_account_circle_24px.xml b/app/app/src/main/res/drawable/ic_account_circle_24px.xml new file mode 100644 index 0000000..3f33c15 --- /dev/null +++ b/app/app/src/main/res/drawable/ic_account_circle_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/app/src/main/res/drawable/ic_list_alt_black_24px.xml b/app/app/src/main/res/drawable/ic_list_alt_24px.xml similarity index 100% rename from app/app/src/main/res/drawable/ic_list_alt_black_24px.xml rename to app/app/src/main/res/drawable/ic_list_alt_24px.xml diff --git a/app/app/src/main/res/drawable/ic_notifications_black_24dp.xml b/app/app/src/main/res/drawable/ic_notifications_black_24dp.xml deleted file mode 100644 index 78b75c3..0000000 --- a/app/app/src/main/res/drawable/ic_notifications_black_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/app/src/main/res/drawable/ic_person_24px.xml b/app/app/src/main/res/drawable/ic_person_24px.xml new file mode 100644 index 0000000..6fcf704 --- /dev/null +++ b/app/app/src/main/res/drawable/ic_person_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/app/src/main/res/drawable/ic_restaurant_black_24px.xml b/app/app/src/main/res/drawable/ic_restaurant_24px.xml similarity index 100% rename from app/app/src/main/res/drawable/ic_restaurant_black_24px.xml rename to app/app/src/main/res/drawable/ic_restaurant_24px.xml diff --git a/app/app/src/main/res/layout/activity_create_dish_form.xml b/app/app/src/main/res/layout/activity_create_dish_form.xml index 62e333a..cfb4252 100644 --- a/app/app/src/main/res/layout/activity_create_dish_form.xml +++ b/app/app/src/main/res/layout/activity_create_dish_form.xml @@ -27,5 +27,13 @@ android:text="Add Ingredient" android:layout_gravity="center_horizontal" android:layout_marginTop="16dp"/> + +