Skip to content

Commit 91a52a4

Browse files
committed
Merge branch 'main' of github.com:PeriodPals/periodpals into feat/settings/clean-up-settings-screen-notifs-and-theme-delete-text-and-add-preferred-distance-option
2 parents d95b555 + 3969efc commit 91a52a4

35 files changed

+679
-372
lines changed

app/src/androidTest/java/com/android/periodpals/services/GPSServiceImplInstrumentedTest.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ package com.android.periodpals.services
22

33
import android.Manifest
44
import androidx.activity.ComponentActivity
5+
import androidx.compose.runtime.mutableStateOf
56
import androidx.lifecycle.Lifecycle
67
import androidx.test.core.app.ActivityScenario
78
import androidx.test.ext.junit.runners.AndroidJUnit4
89
import androidx.test.rule.GrantPermissionRule
10+
import com.android.periodpals.model.authentication.AuthenticationViewModel
911
import com.android.periodpals.model.location.Location
12+
import com.android.periodpals.model.user.AuthenticationUserData
1013
import com.android.periodpals.model.user.UserViewModel
1114
import kotlinx.coroutines.flow.first
1215
import kotlinx.coroutines.test.runTest
@@ -17,6 +20,7 @@ import org.junit.Rule
1720
import org.junit.Test
1821
import org.junit.runner.RunWith
1922
import org.mockito.Mockito.mock
23+
import org.mockito.Mockito.`when`
2024

2125
@RunWith(AndroidJUnit4::class)
2226
class GPSServiceImplInstrumentedTest {
@@ -31,6 +35,7 @@ class GPSServiceImplInstrumentedTest {
3135
private lateinit var scenario: ActivityScenario<ComponentActivity>
3236
private lateinit var activity: ComponentActivity
3337
private lateinit var gpsService: GPSServiceImpl
38+
private lateinit var authenticationViewModel: AuthenticationViewModel
3439
private lateinit var userViewModel: UserViewModel
3540

3641
// Default location
@@ -39,6 +44,7 @@ class GPSServiceImplInstrumentedTest {
3944

4045
@Before
4146
fun setup() {
47+
authenticationViewModel = mock(AuthenticationViewModel::class.java)
4248
userViewModel = mock(UserViewModel::class.java)
4349

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

5056
scenario.onActivity { activity ->
5157
this.activity = activity
52-
gpsService = GPSServiceImpl(this.activity, userViewModel)
58+
gpsService = GPSServiceImpl(this.activity, authenticationViewModel, userViewModel)
5359
}
5460

5561
// Once the GPSService has been initialized, set its state to resumed
5662
scenario.moveToState(Lifecycle.State.RESUMED)
63+
64+
`when`(authenticationViewModel.authUserData)
65+
.thenReturn(mutableStateOf(AuthenticationUserData("test", "test")))
5766
}
5867

5968
@After
6069
fun tearDownService() {
70+
`when`(authenticationViewModel.authUserData)
71+
.thenReturn(mutableStateOf(AuthenticationUserData("test", "test")))
6172
gpsService.cleanup()
6273
}
6374

app/src/main/java/com/android/periodpals/MainActivity.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,9 @@ class MainActivity : ComponentActivity() {
9898
override fun onCreate(savedInstanceState: Bundle?) {
9999
super.onCreate(savedInstanceState)
100100

101-
gpsService = GPSServiceImpl(this, userViewModel)
102-
pushNotificationsService = PushNotificationsServiceImpl(this, userViewModel)
101+
gpsService = GPSServiceImpl(this, authenticationViewModel, userViewModel)
102+
pushNotificationsService =
103+
PushNotificationsServiceImpl(this, authenticationViewModel, userViewModel)
103104
timerManager = TimerManager(this)
104105
timerViewModel = TimerViewModel(timerModel, timerManager)
105106

app/src/main/java/com/android/periodpals/model/user/UserModel.kt

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,25 @@ interface UserRepository {
55
/**
66
* Loads the user profile for the given user ID.
77
*
8+
* @param idUser The ID of the user profile to be loaded.
89
* @param onSuccess callback to be called on successful call on this function returning the
910
* UserDto
1011
* @param onFailure callback to be called when error is caught
1112
*/
12-
suspend fun loadUserProfile(onSuccess: (UserDto) -> Unit, onFailure: (Exception) -> Unit)
13+
suspend fun loadUserProfile(
14+
idUser: String,
15+
onSuccess: (UserDto) -> Unit,
16+
onFailure: (Exception) -> Unit
17+
)
18+
19+
/**
20+
* Loads all user profiles.
21+
*
22+
* @param onSuccess callback to be called on successful call on this function returning the list
23+
* of UserDto
24+
* @param onFailure callback to be called when error is caught
25+
*/
26+
suspend fun loadUserProfiles(onSuccess: (List<UserDto>) -> Unit, onFailure: (Exception) -> Unit)
1327

1428
/**
1529
* Creates the user profile.
@@ -25,7 +39,8 @@ interface UserRepository {
2539
* else create new.
2640
*
2741
* @param userDto The user profile to be checked
28-
* @param onSuccess callback block
42+
* @param onSuccess callback block to be called on success
43+
* @param onFailure callback block to be called when exception is caught
2944
*/
3045
suspend fun upsertUserProfile(
3146
userDto: UserDto,

app/src/main/java/com/android/periodpals/model/user/UserModelSupabase.kt

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,15 @@ private const val USERS = "users"
1818
class UserRepositorySupabase(private val supabase: SupabaseClient) : UserRepository {
1919

2020
override suspend fun loadUserProfile(
21+
idUser: String,
2122
onSuccess: (UserDto) -> Unit,
2223
onFailure: (Exception) -> Unit,
2324
) {
2425
try {
2526
val result =
2627
withContext(Dispatchers.Main) {
2728
supabase.postgrest[USERS]
28-
.select {}
29+
.select { filter { eq("user_id", idUser) } }
2930
.decodeSingle<UserDto>() // RLS rules only allows user to check their own line
3031
}
3132
Log.d(TAG, "loadUserProfile: Success")
@@ -36,6 +37,23 @@ class UserRepositorySupabase(private val supabase: SupabaseClient) : UserReposit
3637
}
3738
}
3839

40+
override suspend fun loadUserProfiles(
41+
onSuccess: (List<UserDto>) -> Unit,
42+
onFailure: (Exception) -> Unit,
43+
) {
44+
try {
45+
val result =
46+
withContext(Dispatchers.Main) {
47+
supabase.postgrest[USERS].select {}.decodeList<UserDto>()
48+
}
49+
Log.d(TAG, "loadUserProfiles: Success")
50+
onSuccess(result)
51+
} catch (e: Exception) {
52+
Log.d(TAG, "loadUserProfiles: fail to load user profile: ${e.message}")
53+
onFailure(e)
54+
}
55+
}
56+
3957
override suspend fun createUserProfile(
4058
user: User,
4159
onSuccess: () -> Unit,
@@ -122,7 +140,7 @@ class UserRepositorySupabase(private val supabase: SupabaseClient) : UserReposit
122140
) {
123141
try {
124142
withContext(Dispatchers.Main) {
125-
val file = supabase.storage.from("avatars").downloadAuthenticated("$filePath.jpg")
143+
val file = supabase.storage.from("avatars").downloadPublic("$filePath.jpg")
126144
Log.d(TAG, "downloadFile: Success")
127145
onSuccess(file)
128146
}

app/src/main/java/com/android/periodpals/model/user/UserViewModel.kt

Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ class UserViewModel(private val userRepository: UserRepositorySupabase) : ViewMo
6262

6363
private val _user = mutableStateOf<User?>(null)
6464
val user: State<User?> = _user
65+
private val _users = mutableStateOf<List<User>?>(null)
66+
val users: State<List<User>?> = _users
6567
private val _avatar = mutableStateOf<ByteArray?>(null)
6668
val avatar: State<ByteArray?> = _avatar
6769

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

79-
/**
80-
* Initializes the user profile.
81-
*
82-
* @param onSuccess Callback function to be called when the user profile is successfully loaded.
83-
* @param onFailure Callback function to be called when there is an error loading the user
84-
* profile.
85-
*/
86-
fun init(
87-
onSuccess: () -> Unit = { Log.d(TAG, "init success callback") },
88-
onFailure: (Exception) -> Unit = { e: Exception ->
89-
Log.d(TAG, "init failure callback: ${e.message}")
90-
},
91-
) {
92-
loadUser(
93-
onSuccess = {
94-
user.value?.let {
95-
downloadFile(
96-
it.imageUrl,
97-
onSuccess = { onSuccess() },
98-
onFailure = { e: Exception -> onFailure(Exception(e)) },
99-
)
100-
}
101-
},
102-
onFailure = { e: Exception -> onFailure(Exception(e)) },
103-
)
104-
}
105-
10681
/**
10782
* Loads the user profile and updates the user state.
10883
*
84+
* @param idUser The ID of the user profile to be loaded.
10985
* @param onSuccess Callback function to be called when the user profile is successfully loaded.
11086
* @param onFailure Callback function to be called when there is an error loading the user
11187
* profile.
11288
*/
11389
fun loadUser(
90+
idUser: String,
11491
onSuccess: () -> Unit = { Log.d(TAG, "loadUser success callback") },
11592
onFailure: (Exception) -> Unit = { e: Exception ->
11693
Log.d(TAG, "loadUser failure callback: ${e.message}")
11794
},
11895
) {
11996
viewModelScope.launch {
12097
userRepository.loadUserProfile(
98+
idUser,
12199
onSuccess = { userDto ->
122100
Log.d(TAG, "loadUserProfile: Successful")
123101
_user.value = userDto.asUser()
@@ -132,6 +110,35 @@ class UserViewModel(private val userRepository: UserRepositorySupabase) : ViewMo
132110
}
133111
}
134112

113+
/**
114+
* Loads all user profiles and updates the user state.
115+
*
116+
* @param onSuccess Callback function to be called when the user profiles are successfully loaded.
117+
* @param onFailure Callback function to be called when there is an error loading the user
118+
* profiles.
119+
*/
120+
fun loadUsers(
121+
onSuccess: () -> Unit = { Log.d(TAG, "loadUsers success callback") },
122+
onFailure: (Exception) -> Unit = { e: Exception ->
123+
Log.d(TAG, "loadUsers failure callback: ${e.message}")
124+
},
125+
) {
126+
viewModelScope.launch {
127+
userRepository.loadUserProfiles(
128+
onSuccess = { userDtos ->
129+
Log.d(TAG, "loadUsers: Successful")
130+
_users.value = userDtos.map { it.asUser() }
131+
onSuccess()
132+
},
133+
onFailure = { e: Exception ->
134+
Log.d(TAG, "loadUsers: fail to load user profiles: ${e.message}")
135+
_users.value = null
136+
onFailure(e)
137+
},
138+
)
139+
}
140+
}
141+
135142
/**
136143
* Saves the user profile.
137144
*

app/src/main/java/com/android/periodpals/resources/C.kt

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -199,12 +199,6 @@ object C {
199199
const val STOP_BUTTON = "Stop button"
200200
const val USEFUL_TIP = "usefulTip"
201201
const val USEFUL_TIP_TEXT = "usefulTipText"
202-
203-
// Displayed texts
204-
const val DISPLAYED_TEXT_ONE =
205-
"Start your tampon timer.\n" + "You’ll be reminded to change it !"
206-
const val DISPLAYED_TEXT_TWO =
207-
"You’ve got this. Stay strong !\n" + "Don’t forget to stay hydrated !"
208202
}
209203
}
210204
}

app/src/main/java/com/android/periodpals/services/GPSServiceImpl.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import androidx.activity.ComponentActivity
99
import androidx.activity.result.ActivityResultLauncher
1010
import androidx.activity.result.contract.ActivityResultContracts
1111
import androidx.core.app.ActivityCompat
12+
import com.android.periodpals.model.authentication.AuthenticationViewModel
1213
import com.android.periodpals.model.location.Location
1314
import com.android.periodpals.model.location.parseLocationGIS
1415
import com.android.periodpals.model.user.UserViewModel
@@ -49,6 +50,7 @@ private enum class REQUEST_TYPE {
4950
*/
5051
class GPSServiceImpl(
5152
private val activity: ComponentActivity,
53+
private val authenticationViewModel: AuthenticationViewModel,
5254
private val userViewModel: UserViewModel,
5355
) : GPSService {
5456
private var _location = MutableStateFlow(Location.DEFAULT_LOCATION)
@@ -196,7 +198,10 @@ class GPSServiceImpl(
196198
*/
197199
private fun uploadUserLocation() {
198200
Log.d(TAG_UPLOAD_LOCATION, "Uploading user location")
201+
authenticationViewModel.loadAuthenticationUserData(
202+
onFailure = { Log.d(TAG_UPLOAD_LOCATION, "Authentication data is null") })
199203
userViewModel.loadUser(
204+
authenticationViewModel.authUserData.value!!.uid,
200205
onSuccess = {
201206
val newUser =
202207
userViewModel.user.value?.copy(locationGIS = parseLocationGIS(_location.value))

app/src/main/java/com/android/periodpals/services/PushNotificationsServiceImpl.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import androidx.core.app.NotificationCompat
1616
import androidx.core.app.NotificationManagerCompat
1717
import androidx.core.content.ContextCompat
1818
import com.android.periodpals.R
19+
import com.android.periodpals.model.authentication.AuthenticationViewModel
1920
import com.android.periodpals.model.user.UserViewModel
2021
import com.google.firebase.FirebaseApp
2122
import com.google.firebase.messaging.FirebaseMessaging
@@ -37,6 +38,7 @@ private const val TIMEOUT = 1000L
3738
*/
3839
class PushNotificationsServiceImpl(
3940
private val activity: ComponentActivity,
41+
private val authenticationViewModel: AuthenticationViewModel?,
4042
private val userViewModel: UserViewModel?,
4143
) : FirebaseMessagingService(), PushNotificationsService {
4244

@@ -50,7 +52,7 @@ class PushNotificationsServiceImpl(
5052
handlePermissionResult(it)
5153
}
5254

53-
constructor() : this(ComponentActivity(), null) {
55+
constructor() : this(ComponentActivity(), null, null) {
5456
Log.e(TAG, "went through empty constructor")
5557
}
5658

@@ -229,7 +231,10 @@ class PushNotificationsServiceImpl(
229231
Log.e(TAG, "UserViewModel not available")
230232
return
231233
}
234+
authenticationViewModel?.loadAuthenticationUserData(
235+
onFailure = { Log.d(TAG, "Authentication data is null") })
232236
userViewModel.loadUser(
237+
authenticationViewModel?.authUserData?.value!!.uid,
233238
onSuccess = {
234239
Log.d(TAG, "Uploading token to server")
235240
val newUser = userViewModel.user.value?.copy(fcmToken = token)

0 commit comments

Comments
 (0)