diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 98f601ba4427b3266edc434b3b689167f3290ed9..24cf5ec87aa93da3b6eb68f368f4b276a57dd745 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -45,7 +45,14 @@ dependencies { implementation(libs.androidx.lifecycle.viewmodel.ktx) implementation(libs.androidx.navigation.fragment.ktx) implementation(libs.androidx.navigation.ui.ktx) + implementation(libs.androidx.annotation) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) + + // Retrofit + implementation(libs.retrofit2.retrofit) + implementation(libs.retrofit2.converter.gson) + implementation(libs.retrofit2.converter.scalars) + implementation(libs.okhttp3) } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4a51b78ff66e9fcbe857133f367055c0b22182a1..a76744ed0fee6ca61d9a398d985824b3afa9ed2c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> + <uses-permission android:name="android.permission.INTERNET" /> + <application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" @@ -12,6 +14,10 @@ android:supportsRtl="true" android:theme="@style/Theme.Bondoman" tools:targetApi="31"> + <activity + android:name=".ui.login.LoginActivity" + android:exported="false" + android:label="@string/title_activity_login" /> <activity android:name=".MainActivity" android:exported="true" diff --git a/app/src/main/java/com/onionsquad/bondoman/MainActivity.kt b/app/src/main/java/com/onionsquad/bondoman/MainActivity.kt index d92d0a8434e598970b72ff813ea90f82e68b30d0..a6ad2b9fb755fc340a1eb96c21c88a0dc4708055 100644 --- a/app/src/main/java/com/onionsquad/bondoman/MainActivity.kt +++ b/app/src/main/java/com/onionsquad/bondoman/MainActivity.kt @@ -1,13 +1,16 @@ package com.onionsquad.bondoman +import android.content.Intent import android.os.Bundle -import com.google.android.material.bottomnavigation.BottomNavigationView import androidx.appcompat.app.AppCompatActivity import androidx.navigation.findNavController import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.setupActionBarWithNavController import androidx.navigation.ui.setupWithNavController +import com.google.android.material.bottomnavigation.BottomNavigationView +import com.onionsquad.bondoman.auth.SessionManager import com.onionsquad.bondoman.databinding.ActivityMainBinding +import com.onionsquad.bondoman.ui.login.LoginActivity class MainActivity : AppCompatActivity() { @@ -16,6 +19,11 @@ class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + val sessionManager = SessionManager(this) + if (sessionManager.fetchAuthToken() == null) { + sendToLoginActivity() + } + binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) @@ -29,4 +37,11 @@ class MainActivity : AppCompatActivity() { setupActionBarWithNavController(navController, appBarConfiguration) navView.setupWithNavController(navController) } + + private fun sendToLoginActivity() { + val intent = Intent(this@MainActivity, LoginActivity::class.java) + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK) + startActivity(intent) + finish() + } } \ No newline at end of file diff --git a/app/src/main/java/com/onionsquad/bondoman/auth/SessionManager.kt b/app/src/main/java/com/onionsquad/bondoman/auth/SessionManager.kt new file mode 100644 index 0000000000000000000000000000000000000000..91fc448a741643829bf2833dc42eae409ba2ff56 --- /dev/null +++ b/app/src/main/java/com/onionsquad/bondoman/auth/SessionManager.kt @@ -0,0 +1,31 @@ +package com.onionsquad.bondoman.auth + +import android.content.Context +import android.util.Log +import com.google.gson.Gson +import com.onionsquad.bondoman.R + +class SessionManager(context: Context) { + private val sharedPreferences = context.getSharedPreferences( + context.getString(R.string.preference_file_key), + Context.MODE_PRIVATE + ) + + companion object { + const val USER_TOKEN = "user_token" + } + + fun fetchAuthToken(): Token? { + val tokenJson = sharedPreferences.getString(USER_TOKEN, null) + return if (tokenJson != null) Gson().fromJson(tokenJson, Token::class.java) else null + } + + fun saveAuthToken(token: Token) { + sharedPreferences.edit().putString(USER_TOKEN, Gson().toJson(token)).apply() + Log.d("TOKEN", "${fetchAuthToken()}") + } + + fun deleteAuthToken() { + sharedPreferences.edit().remove(USER_TOKEN).apply() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/onionsquad/bondoman/auth/Token.kt b/app/src/main/java/com/onionsquad/bondoman/auth/Token.kt new file mode 100644 index 0000000000000000000000000000000000000000..435b5dba0a1767c56fbcaa233912f555fb34b0c8 --- /dev/null +++ b/app/src/main/java/com/onionsquad/bondoman/auth/Token.kt @@ -0,0 +1,3 @@ +package com.onionsquad.bondoman.auth + +data class Token(val tokenString: String, val nim: String, val exp: Long, val iat: Long) diff --git a/app/src/main/java/com/onionsquad/bondoman/network/BondomanApiService.kt b/app/src/main/java/com/onionsquad/bondoman/network/BondomanApiService.kt new file mode 100644 index 0000000000000000000000000000000000000000..5ecec9762bcbbb22be1f663763a71a9c8b18343b --- /dev/null +++ b/app/src/main/java/com/onionsquad/bondoman/network/BondomanApiService.kt @@ -0,0 +1,36 @@ +package com.onionsquad.bondoman.network + +import okhttp3.OkHttpClient +import retrofit2.Call +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import retrofit2.converter.scalars.ScalarsConverterFactory +import retrofit2.http.Body +import retrofit2.http.Header +import retrofit2.http.Headers +import retrofit2.http.POST + + +private const val BASE_URL = "https://pbd-backend-2024.vercel.app/" +private val client = OkHttpClient.Builder().build() +private val retrofit = Retrofit.Builder() + .baseUrl(BASE_URL) + .addConverterFactory(ScalarsConverterFactory.create()) + .addConverterFactory(GsonConverterFactory.create()) + .client(client) + .build() + +interface BondomanApiService { + @Headers("Content-Type: application/json") + @POST("api/auth/login") + fun login(@Body loginData: LoginRequest): Call<String> + + @POST("api/auth/token") + fun token(@Header("Authorization") token: String): Call<TokenResponse> +} + +object BondomanApi { + val retrofitService: BondomanApiService by lazy { + retrofit.create(BondomanApiService::class.java) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/onionsquad/bondoman/network/LoginRequest.kt b/app/src/main/java/com/onionsquad/bondoman/network/LoginRequest.kt new file mode 100644 index 0000000000000000000000000000000000000000..c2a6d36c6bc1bf96f6c3af2aee461c9c78946eec --- /dev/null +++ b/app/src/main/java/com/onionsquad/bondoman/network/LoginRequest.kt @@ -0,0 +1,3 @@ +package com.onionsquad.bondoman.network + +data class LoginRequest(val email: String?, val password: String?) \ No newline at end of file diff --git a/app/src/main/java/com/onionsquad/bondoman/network/LoginResponse.kt b/app/src/main/java/com/onionsquad/bondoman/network/LoginResponse.kt new file mode 100644 index 0000000000000000000000000000000000000000..c264da3200c401f27b1c04755eb4aa154d44bbb1 --- /dev/null +++ b/app/src/main/java/com/onionsquad/bondoman/network/LoginResponse.kt @@ -0,0 +1,5 @@ +package com.onionsquad.bondoman.network + +import com.google.gson.annotations.SerializedName + +data class LoginResponse(val token: String?) diff --git a/app/src/main/java/com/onionsquad/bondoman/network/TokenResponse.kt b/app/src/main/java/com/onionsquad/bondoman/network/TokenResponse.kt new file mode 100644 index 0000000000000000000000000000000000000000..ed2cc004cb0bcc9941a021053d1a92fb622eab8b --- /dev/null +++ b/app/src/main/java/com/onionsquad/bondoman/network/TokenResponse.kt @@ -0,0 +1,3 @@ +package com.onionsquad.bondoman.network + +data class TokenResponse(val nim: String?, val exp: Long?, val iat: Long?) diff --git a/app/src/main/java/com/onionsquad/bondoman/ui/login/LoginActivity.kt b/app/src/main/java/com/onionsquad/bondoman/ui/login/LoginActivity.kt new file mode 100644 index 0000000000000000000000000000000000000000..3eace85f0832e12fce0cf2201091ba1bdfeb9c3b --- /dev/null +++ b/app/src/main/java/com/onionsquad/bondoman/ui/login/LoginActivity.kt @@ -0,0 +1,124 @@ +package com.onionsquad.bondoman.ui.login + +import android.app.Activity +import android.content.Intent +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProvider +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import android.text.Editable +import android.text.TextWatcher +import android.view.View +import android.view.inputmethod.EditorInfo +import android.widget.EditText +import android.widget.Toast +import com.onionsquad.bondoman.MainActivity +import com.onionsquad.bondoman.databinding.ActivityLoginBinding + +import com.onionsquad.bondoman.R +import com.onionsquad.bondoman.auth.SessionManager + +class LoginActivity : AppCompatActivity() { + + private lateinit var loginViewModel: LoginViewModel + private lateinit var binding: ActivityLoginBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding = ActivityLoginBinding.inflate(layoutInflater) + setContentView(binding.root) + + val email = binding.username + val password = binding.password + val login = binding.login + val loading = binding.loading + + val sessionManager = SessionManager(this) + + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + .apply { + loginFormState.observe(this@LoginActivity, Observer { + val loginState = it ?: return@Observer + + login.isEnabled = loginState.isDataValid + + if (loginState.emailError != null) { + email.error = getString(loginState.emailError) + } + }) + loginResult.observe(this@LoginActivity, Observer { + val loginResult = it ?: return@Observer + + loading.visibility = View.GONE + if (loginResult.error != null) { + showLoginFailed(loginResult.error) + } + if (loginResult.success != null) { + sessionManager.saveAuthToken(loginResult.success) + showLoginSuccess() + sendToMainActivity() + } + }) + } + + email.afterTextChanged { + loginViewModel.loginDataChanged( + email.text.toString(), + password.text.toString() + ) + } + + password.apply { + afterTextChanged { + loginViewModel.loginDataChanged( + email.text.toString(), + password.text.toString() + ) + } + + setOnEditorActionListener { _, actionId, _ -> + when (actionId) { + EditorInfo.IME_ACTION_DONE -> + loginViewModel.login( + email.text.toString(), + password.text.toString() + ) + } + false + } + + login.setOnClickListener { + loading.visibility = View.VISIBLE + loginViewModel.login(email.text.toString(), password.text.toString()) + } + } + } + + private fun sendToMainActivity() { + val intent = Intent(this@LoginActivity, MainActivity::class.java) + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK) + startActivity(intent) + finish() + } + + private fun showLoginSuccess() { + Toast.makeText(applicationContext, R.string.login_success, Toast.LENGTH_LONG).show() + } + + private fun showLoginFailed(error: String) { + Toast.makeText(applicationContext, error, Toast.LENGTH_SHORT).show() + } +} + +fun EditText.afterTextChanged(afterTextChanged: (String) -> Unit) { + this.addTextChangedListener(object : TextWatcher { + override fun afterTextChanged(editable: Editable?) { + afterTextChanged.invoke(editable.toString()) + } + + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/onionsquad/bondoman/ui/login/LoginFormState.kt b/app/src/main/java/com/onionsquad/bondoman/ui/login/LoginFormState.kt new file mode 100644 index 0000000000000000000000000000000000000000..dc8eb07260f3c148cec77ef1264538bf0588ed97 --- /dev/null +++ b/app/src/main/java/com/onionsquad/bondoman/ui/login/LoginFormState.kt @@ -0,0 +1,6 @@ +package com.onionsquad.bondoman.ui.login + +data class LoginFormState( + val emailError: Int? = null, + val isDataValid: Boolean = false +) \ No newline at end of file diff --git a/app/src/main/java/com/onionsquad/bondoman/ui/login/LoginResult.kt b/app/src/main/java/com/onionsquad/bondoman/ui/login/LoginResult.kt new file mode 100644 index 0000000000000000000000000000000000000000..25ce877ebd7c45ae4ca479ce9be4a4aad7c71ed5 --- /dev/null +++ b/app/src/main/java/com/onionsquad/bondoman/ui/login/LoginResult.kt @@ -0,0 +1,8 @@ +package com.onionsquad.bondoman.ui.login + +import com.onionsquad.bondoman.auth.Token + +data class LoginResult( + val success: Token? = null, + val error: String? = null +) \ No newline at end of file diff --git a/app/src/main/java/com/onionsquad/bondoman/ui/login/LoginViewModel.kt b/app/src/main/java/com/onionsquad/bondoman/ui/login/LoginViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..6cb095fbcddae41cb7ce9b7f78e243d2ea8766d3 --- /dev/null +++ b/app/src/main/java/com/onionsquad/bondoman/ui/login/LoginViewModel.kt @@ -0,0 +1,87 @@ +package com.onionsquad.bondoman.ui.login + +import android.util.Log +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import android.util.Patterns +import com.google.gson.Gson + +import com.onionsquad.bondoman.R +import com.onionsquad.bondoman.auth.SessionManager +import com.onionsquad.bondoman.auth.Token +import com.onionsquad.bondoman.network.BondomanApi +import com.onionsquad.bondoman.network.LoginRequest +import com.onionsquad.bondoman.network.LoginResponse +import com.onionsquad.bondoman.network.TokenResponse +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response + +class LoginViewModel : ViewModel() { + + private val _loginForm = MutableLiveData<LoginFormState>() + val loginFormState: LiveData<LoginFormState> = _loginForm + + private val _loginResult = MutableLiveData<LoginResult>() + val loginResult: LiveData<LoginResult> = _loginResult + + fun login(email: String, password: String) { + BondomanApi.retrofitService.login(LoginRequest(email, password)).enqueue( + object : Callback<String> { + override fun onResponse(call: Call<String>, response: Response<String>) { + if (response.isSuccessful) { + val body = response.body() + val tokenString = Gson().fromJson(body, LoginResponse::class.java).token!! + Log.d("TOKEN", tokenString) + fetchTokenInformation(tokenString) + } else { + val errBody = response.errorBody()!!.string() + val error = when { + errBody.contains("email") -> "Invalid email" + errBody.contains("password") -> "Invalid password" + else -> "Invalid email or password" + } + _loginResult.value = LoginResult(error = error) + } + } + + override fun onFailure(call: Call<String>, t: Throwable) { + _loginResult.value = LoginResult(error = t.message) + } + } + ) + } + + private fun fetchTokenInformation(tokenString: String) { + BondomanApi.retrofitService.token("Bearer $tokenString").enqueue( + object : Callback<TokenResponse> { + override fun onResponse( + call: Call<TokenResponse>, + response: Response<TokenResponse> + ) { + val token = response.body()!! + _loginResult.value = LoginResult( + success = Token(tokenString, token.nim!!, token.exp!!, token.iat!!) + ) + } + + override fun onFailure(call: Call<TokenResponse>, t: Throwable) { + _loginResult.value = LoginResult(error = t.message) + } + } + ) + } + + fun loginDataChanged(email: String, password: String) { + if (!isEmailValid(email)) { + _loginForm.value = LoginFormState(emailError = R.string.invalid_email) + } else { + _loginForm.value = LoginFormState(isDataValid = true) + } + } + + private fun isEmailValid(username: String): Boolean { + return Patterns.EMAIL_ADDRESS.matcher(username).matches() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/onionsquad/bondoman/ui/settings/SettingsFragment.kt b/app/src/main/java/com/onionsquad/bondoman/ui/settings/SettingsFragment.kt index dc153a26a1d0a02e397c9400600979bbc3a8be75..ca0aa81d6383bd4a0fccad0625fca1b9176f558b 100644 --- a/app/src/main/java/com/onionsquad/bondoman/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/com/onionsquad/bondoman/ui/settings/SettingsFragment.kt @@ -4,15 +4,17 @@ 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 androidx.lifecycle.ViewModelProvider +import androidx.navigation.fragment.findNavController +import com.onionsquad.bondoman.R +import com.onionsquad.bondoman.auth.SessionManager import com.onionsquad.bondoman.databinding.FragmentSettingsBinding class SettingsFragment : Fragment() { private var _binding: FragmentSettingsBinding? = null - // This property is only valid between onCreateView and - // onDestroyView. private val binding get() = _binding!! override fun onCreateView( @@ -20,11 +22,19 @@ class SettingsFragment : Fragment() { container: ViewGroup?, savedInstanceState: Bundle? ): View { - val transactionViewModel = - ViewModelProvider(this).get(SettingsViewModel::class.java) - _binding = FragmentSettingsBinding.inflate(inflater, container, false) + val sessionManager = SessionManager(requireContext()) + + binding.apply { + logoutButton.setOnClickListener { + sessionManager.deleteAuthToken() + Toast.makeText(requireContext(), "Log out success", Toast.LENGTH_SHORT).show() + findNavController().popBackStack(R.id.navigation_transaction, true) + requireActivity().recreate() + } + } + return binding.root } diff --git a/app/src/main/java/com/onionsquad/bondoman/ui/settings/SettingsViewModel.kt b/app/src/main/java/com/onionsquad/bondoman/ui/settings/SettingsViewModel.kt deleted file mode 100644 index a877767c5378529d02be28a4688b0589b13d6064..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/onionsquad/bondoman/ui/settings/SettingsViewModel.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.onionsquad.bondoman.ui.settings - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel - -class SettingsViewModel : ViewModel() { - private val _text = MutableLiveData<String>().apply { - value = "This is notifications Fragment" - } - val text: LiveData<String> = _text -} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_logout_24.xml b/app/src/main/res/drawable/ic_baseline_logout_24.xml new file mode 100644 index 0000000000000000000000000000000000000000..c22a96f911c6a8c7615c8e149ee796eddc23d449 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_logout_24.xml @@ -0,0 +1,5 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp"> + + <path android:fillColor="@android:color/white" android:pathData="M17,7l-1.41,1.41L18.17,11H8v2h10.17l-2.58,2.58L17,17l5,-5zM4,5h8V3H4c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h8v-2H4V5z"/> + +</vector> diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml new file mode 100644 index 0000000000000000000000000000000000000000..934a2edc6b2b4eacc44b02b986b6628f51873712 --- /dev/null +++ b/app/src/main/res/layout/activity_login.xml @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingLeft="@dimen/activity_horizontal_margin" + android:paddingTop="@dimen/activity_vertical_margin" + android:paddingRight="@dimen/activity_horizontal_margin" + android:paddingBottom="@dimen/activity_vertical_margin" + tools:context=".ui.login.LoginActivity"> + + <EditText + android:id="@+id/username" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="96dp" + android:autofillHints="@string/prompt_email" + android:hint="@string/prompt_email" + android:inputType="textEmailAddress" + android:selectAllOnFocus="true" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <EditText + android:id="@+id/password" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:autofillHints="@string/prompt_password" + android:hint="@string/prompt_password" + android:imeActionLabel="@string/action_sign_in" + android:imeOptions="actionDone" + android:inputType="textPassword" + android:selectAllOnFocus="true" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/username" /> + + <Button + android:id="@+id/login" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="start" + android:layout_marginTop="16dp" + android:layout_marginBottom="64dp" + android:enabled="false" + android:text="@string/action_sign_in" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/password" + app:layout_constraintVertical_bias="0.2" /> + + <ProgressBar + android:id="@+id/loading" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:layout_marginTop="64dp" + android:layout_marginBottom="64dp" + android:visibility="gone" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="@+id/password" + app:layout_constraintStart_toStartOf="@+id/password" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="0.3" /> + +</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml index 441476f744f7bedaef0590d7b4b632a82ee0354d..570a31e7a5eb1be342b0845e3542c86cfe97a81c 100644 --- a/app/src/main/res/layout/fragment_settings.xml +++ b/app/src/main/res/layout/fragment_settings.xml @@ -5,4 +5,14 @@ android:layout_height="match_parent" tools:context=".ui.settings.SettingsFragment"> + <Button + android:id="@+id/logout_button" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAlignment="textStart" + android:textColor="@color/design_default_color_error" + android:backgroundTint="@color/white" + android:drawableLeft="@drawable/ic_baseline_logout_24" + android:drawableTint="@color/design_default_color_error" + android:text="@string/action_sign_out" /> </FrameLayout> \ No newline at end of file diff --git a/app/src/main/res/navigation/mobile_navigation.xml b/app/src/main/res/navigation/mobile_navigation.xml index fc575e810de8c1a52bac57a9d44e78422bee791d..f7d84c6783037f11b61b8f406f6570a4d374a7a9 100644 --- a/app/src/main/res/navigation/mobile_navigation.xml +++ b/app/src/main/res/navigation/mobile_navigation.xml @@ -7,21 +7,21 @@ <fragment android:id="@+id/navigation_transaction" - android:name="com.onionsquad.bondoman.ui.graph.GraphFragment" + android:name="com.onionsquad.bondoman.ui.transaction.TransactionFragment" android:label="@string/title_transaction" - tools:layout="@layout/fragment_graph" /> + tools:layout="@layout/fragment_transaction" /> <fragment android:id="@+id/navigation_scan" - android:name="com.onionsquad.bondoman.ui.transaction.TransactionFragment" + android:name="com.onionsquad.bondoman.ui.scan.ScanFragment" android:label="@string/title_scan" - tools:layout="@layout/fragment_transaction" /> + tools:layout="@layout/fragment_scan" /> <fragment android:id="@+id/navigation_graph" - android:name="com.onionsquad.bondoman.ui.scan.ScanFragment" + android:name="com.onionsquad.bondoman.ui.graph.GraphFragment" android:label="@string/title_graph" - tools:layout="@layout/fragment_scan" /> + tools:layout="@layout/fragment_graph" /> <fragment android:id="@+id/navigation_settings" android:name="com.onionsquad.bondoman.ui.settings.SettingsFragment" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ad704cda77a7589edab34feae698dbb5b25a3abb..dfc80c0ba927362dd53849ff4c5986dcce5d03f0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -4,4 +4,13 @@ <string name="title_scan">Scan</string> <string name="title_graph">Graph</string> <string name="title_settings">Settings</string> + <string name="title_activity_login">Sign in</string> + <string name="prompt_email">Email</string> + <string name="prompt_password">Password</string> + <string name="action_sign_in">Sign in</string> + <string name="login_success">"Login success!"</string> + <string name="invalid_email">Not a valid email</string> + <string name="login_failed">"Login failed"</string> + <string name="preference_file_key">BondomanSharedPrefs</string> + <string name="action_sign_out">Sign out</string> </resources> \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c3441753224764706fc741a2309298b5f20ff6ad..29c47e56b19e76de706812cbc86d529a727fa6b8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,6 +12,9 @@ lifecycleLivedataKtx = "2.6.1" lifecycleViewmodelKtx = "2.6.1" navigationFragmentKtx = "2.6.0" navigationUiKtx = "2.6.0" +retrofit = "2.11.0" +okhttp3 = "4.12.0" +annotation = "1.7.1" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -25,6 +28,11 @@ androidx-lifecycle-livedata-ktx = { group = "androidx.lifecycle", name = "lifecy androidx-lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" } androidx-navigation-fragment-ktx = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigationFragmentKtx" } androidx-navigation-ui-ktx = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigationUiKtx" } +retrofit2-retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" } +retrofit2-converter-scalars = { group = "com.squareup.retrofit2", name = "converter-scalars", version.ref = "retrofit" } +retrofit2-converter-gson = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit" } +okhttp3 = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp3" } +androidx-annotation = { group = "androidx.annotation", name = "annotation", version.ref = "annotation" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" }