Skip to content

Commit

Permalink
Merge branch 'refs/heads/main' into fix/misc/auth-map-extract-strings
Browse files Browse the repository at this point in the history
# Conflicts:
#	app/src/main/res/values/strings.xml
  • Loading branch information
taghizadlaura committed Dec 19, 2024
2 parents 63f6f1b + b8f3902 commit 3f31a69
Show file tree
Hide file tree
Showing 31 changed files with 562 additions and 326 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ package com.android.periodpals.services

import android.Manifest
import androidx.activity.ComponentActivity
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.Lifecycle
import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.rule.GrantPermissionRule
import com.android.periodpals.model.authentication.AuthenticationViewModel
import com.android.periodpals.model.location.Location
import com.android.periodpals.model.user.AuthenticationUserData
import com.android.periodpals.model.user.UserViewModel
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
Expand All @@ -17,6 +20,7 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
import org.mockito.Mockito.`when`

@RunWith(AndroidJUnit4::class)
class GPSServiceImplInstrumentedTest {
Expand All @@ -31,6 +35,7 @@ class GPSServiceImplInstrumentedTest {
private lateinit var scenario: ActivityScenario<ComponentActivity>
private lateinit var activity: ComponentActivity
private lateinit var gpsService: GPSServiceImpl
private lateinit var authenticationViewModel: AuthenticationViewModel
private lateinit var userViewModel: UserViewModel

// Default location
Expand All @@ -39,6 +44,7 @@ class GPSServiceImplInstrumentedTest {

@Before
fun setup() {
authenticationViewModel = mock(AuthenticationViewModel::class.java)
userViewModel = mock(UserViewModel::class.java)

scenario = ActivityScenario.launch(ComponentActivity::class.java)
Expand All @@ -49,15 +55,20 @@ class GPSServiceImplInstrumentedTest {

scenario.onActivity { activity ->
this.activity = activity
gpsService = GPSServiceImpl(this.activity, userViewModel)
gpsService = GPSServiceImpl(this.activity, authenticationViewModel, userViewModel)
}

// Once the GPSService has been initialized, set its state to resumed
scenario.moveToState(Lifecycle.State.RESUMED)

`when`(authenticationViewModel.authUserData)
.thenReturn(mutableStateOf(AuthenticationUserData("test", "test")))
}

@After
fun tearDownService() {
`when`(authenticationViewModel.authUserData)
.thenReturn(mutableStateOf(AuthenticationUserData("test", "test")))
gpsService.cleanup()
}

Expand Down
5 changes: 3 additions & 2 deletions app/src/main/java/com/android/periodpals/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,9 @@ class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

gpsService = GPSServiceImpl(this, userViewModel)
pushNotificationsService = PushNotificationsServiceImpl(this, userViewModel)
gpsService = GPSServiceImpl(this, authenticationViewModel, userViewModel)
pushNotificationsService =
PushNotificationsServiceImpl(this, authenticationViewModel, userViewModel)
timerManager = TimerManager(this)
timerViewModel = TimerViewModel(timerModel, timerManager)

Expand Down
19 changes: 17 additions & 2 deletions app/src/main/java/com/android/periodpals/model/user/UserModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,25 @@ interface UserRepository {
/**
* Loads the user profile for the given user ID.
*
* @param idUser The ID of the user profile to be loaded.
* @param onSuccess callback to be called on successful call on this function returning the
* UserDto
* @param onFailure callback to be called when error is caught
*/
suspend fun loadUserProfile(onSuccess: (UserDto) -> Unit, onFailure: (Exception) -> Unit)
suspend fun loadUserProfile(
idUser: String,
onSuccess: (UserDto) -> Unit,
onFailure: (Exception) -> Unit
)

/**
* Loads all user profiles.
*
* @param onSuccess callback to be called on successful call on this function returning the list
* of UserDto
* @param onFailure callback to be called when error is caught
*/
suspend fun loadUserProfiles(onSuccess: (List<UserDto>) -> Unit, onFailure: (Exception) -> Unit)

/**
* Creates the user profile.
Expand All @@ -25,7 +39,8 @@ interface UserRepository {
* else create new.
*
* @param userDto The user profile to be checked
* @param onSuccess callback block
* @param onSuccess callback block to be called on success
* @param onFailure callback block to be called when exception is caught
*/
suspend fun upsertUserProfile(
userDto: UserDto,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@ private const val USERS = "users"
class UserRepositorySupabase(private val supabase: SupabaseClient) : UserRepository {

override suspend fun loadUserProfile(
idUser: String,
onSuccess: (UserDto) -> Unit,
onFailure: (Exception) -> Unit,
) {
try {
val result =
withContext(Dispatchers.Main) {
supabase.postgrest[USERS]
.select {}
.select { filter { eq("user_id", idUser) } }
.decodeSingle<UserDto>() // RLS rules only allows user to check their own line
}
Log.d(TAG, "loadUserProfile: Success")
Expand All @@ -36,6 +37,23 @@ class UserRepositorySupabase(private val supabase: SupabaseClient) : UserReposit
}
}

override suspend fun loadUserProfiles(
onSuccess: (List<UserDto>) -> Unit,
onFailure: (Exception) -> Unit,
) {
try {
val result =
withContext(Dispatchers.Main) {
supabase.postgrest[USERS].select {}.decodeList<UserDto>()
}
Log.d(TAG, "loadUserProfiles: Success")
onSuccess(result)
} catch (e: Exception) {
Log.d(TAG, "loadUserProfiles: fail to load user profile: ${e.message}")
onFailure(e)
}
}

override suspend fun createUserProfile(
user: User,
onSuccess: () -> Unit,
Expand Down Expand Up @@ -122,7 +140,7 @@ class UserRepositorySupabase(private val supabase: SupabaseClient) : UserReposit
) {
try {
withContext(Dispatchers.Main) {
val file = supabase.storage.from("avatars").downloadAuthenticated("$filePath.jpg")
val file = supabase.storage.from("avatars").downloadPublic("$filePath.jpg")
Log.d(TAG, "downloadFile: Success")
onSuccess(file)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ class UserViewModel(private val userRepository: UserRepositorySupabase) : ViewMo

private val _user = mutableStateOf<User?>(null)
val user: State<User?> = _user
private val _users = mutableStateOf<List<User>?>(null)
val users: State<List<User>?> = _users
private val _avatar = mutableStateOf<ByteArray?>(null)
val avatar: State<ByteArray?> = _avatar

Expand All @@ -76,48 +78,24 @@ class UserViewModel(private val userRepository: UserRepositorySupabase) : ViewMo
name = PROFILE_IMAGE_STATE_NAME, validators = profileImageValidators),
))

/**
* Initializes the user profile.
*
* @param onSuccess Callback function to be called when the user profile is successfully loaded.
* @param onFailure Callback function to be called when there is an error loading the user
* profile.
*/
fun init(
onSuccess: () -> Unit = { Log.d(TAG, "init success callback") },
onFailure: (Exception) -> Unit = { e: Exception ->
Log.d(TAG, "init failure callback: ${e.message}")
},
) {
loadUser(
onSuccess = {
user.value?.let {
downloadFile(
it.imageUrl,
onSuccess = { onSuccess() },
onFailure = { e: Exception -> onFailure(Exception(e)) },
)
}
},
onFailure = { e: Exception -> onFailure(Exception(e)) },
)
}

/**
* Loads the user profile and updates the user state.
*
* @param idUser The ID of the user profile to be loaded.
* @param onSuccess Callback function to be called when the user profile is successfully loaded.
* @param onFailure Callback function to be called when there is an error loading the user
* profile.
*/
fun loadUser(
idUser: String,
onSuccess: () -> Unit = { Log.d(TAG, "loadUser success callback") },
onFailure: (Exception) -> Unit = { e: Exception ->
Log.d(TAG, "loadUser failure callback: ${e.message}")
},
) {
viewModelScope.launch {
userRepository.loadUserProfile(
idUser,
onSuccess = { userDto ->
Log.d(TAG, "loadUserProfile: Successful")
_user.value = userDto.asUser()
Expand All @@ -132,6 +110,35 @@ class UserViewModel(private val userRepository: UserRepositorySupabase) : ViewMo
}
}

/**
* Loads all user profiles and updates the user state.
*
* @param onSuccess Callback function to be called when the user profiles are successfully loaded.
* @param onFailure Callback function to be called when there is an error loading the user
* profiles.
*/
fun loadUsers(
onSuccess: () -> Unit = { Log.d(TAG, "loadUsers success callback") },
onFailure: (Exception) -> Unit = { e: Exception ->
Log.d(TAG, "loadUsers failure callback: ${e.message}")
},
) {
viewModelScope.launch {
userRepository.loadUserProfiles(
onSuccess = { userDtos ->
Log.d(TAG, "loadUsers: Successful")
_users.value = userDtos.map { it.asUser() }
onSuccess()
},
onFailure = { e: Exception ->
Log.d(TAG, "loadUsers: fail to load user profiles: ${e.message}")
_users.value = null
onFailure(e)
},
)
}
}

/**
* Saves the user profile.
*
Expand Down
6 changes: 0 additions & 6 deletions app/src/main/java/com/android/periodpals/resources/C.kt
Original file line number Diff line number Diff line change
Expand Up @@ -213,12 +213,6 @@ object C {
const val STOP_BUTTON = "Stop button"
const val USEFUL_TIP = "usefulTip"
const val USEFUL_TIP_TEXT = "usefulTipText"

// Displayed texts
const val DISPLAYED_TEXT_ONE =
"Start your tampon timer.\n" + "You’ll be reminded to change it !"
const val DISPLAYED_TEXT_TWO =
"You’ve got this. Stay strong !\n" + "Don’t forget to stay hydrated !"
}
}
}
4 changes: 2 additions & 2 deletions app/src/main/java/com/android/periodpals/resources/Dimens.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ data class Dimens(
val roundedPercent: Int = 50,
)

// Width <= 360dp
// Width <= 300dp
val CompactSmallDimens = Dimens(small1 = 3.dp, iconSize = 20.dp)

// 360dp < Width <= 500dp
// 300dp < Width <= 500dp
/** Reference padding and spacing values for a medium screen size. */
val CompactMediumDimens =
Dimens(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.app.ActivityCompat
import com.android.periodpals.model.authentication.AuthenticationViewModel
import com.android.periodpals.model.location.Location
import com.android.periodpals.model.location.parseLocationGIS
import com.android.periodpals.model.user.UserViewModel
Expand Down Expand Up @@ -49,6 +50,7 @@ private enum class REQUEST_TYPE {
*/
class GPSServiceImpl(
private val activity: ComponentActivity,
private val authenticationViewModel: AuthenticationViewModel,
private val userViewModel: UserViewModel,
) : GPSService {
private var _location = MutableStateFlow(Location.DEFAULT_LOCATION)
Expand Down Expand Up @@ -196,7 +198,10 @@ class GPSServiceImpl(
*/
private fun uploadUserLocation() {
Log.d(TAG_UPLOAD_LOCATION, "Uploading user location")
authenticationViewModel.loadAuthenticationUserData(
onFailure = { Log.d(TAG_UPLOAD_LOCATION, "Authentication data is null") })
userViewModel.loadUser(
authenticationViewModel.authUserData.value!!.uid,
onSuccess = {
val newUser =
userViewModel.user.value?.copy(locationGIS = parseLocationGIS(_location.value))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import com.android.periodpals.R
import com.android.periodpals.model.authentication.AuthenticationViewModel
import com.android.periodpals.model.user.UserViewModel
import com.google.firebase.FirebaseApp
import com.google.firebase.messaging.FirebaseMessaging
Expand All @@ -37,6 +38,7 @@ private const val TIMEOUT = 1000L
*/
class PushNotificationsServiceImpl(
private val activity: ComponentActivity,
private val authenticationViewModel: AuthenticationViewModel?,
private val userViewModel: UserViewModel?,
) : FirebaseMessagingService(), PushNotificationsService {

Expand All @@ -50,7 +52,7 @@ class PushNotificationsServiceImpl(
handlePermissionResult(it)
}

constructor() : this(ComponentActivity(), null) {
constructor() : this(ComponentActivity(), null, null) {
Log.e(TAG, "went through empty constructor")
}

Expand Down Expand Up @@ -229,7 +231,10 @@ class PushNotificationsServiceImpl(
Log.e(TAG, "UserViewModel not available")
return
}
authenticationViewModel?.loadAuthenticationUserData(
onFailure = { Log.d(TAG, "Authentication data is null") })
userViewModel.loadUser(
authenticationViewModel?.authUserData?.value!!.uid,
onSuccess = {
Log.d(TAG, "Uploading token to server")
val newUser = userViewModel.user.value?.copy(fcmToken = token)
Expand Down
Loading

0 comments on commit 3f31a69

Please sign in to comment.