-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #50 from PeriodPals/feat/profile/view-model
Feat/profile/view model
- Loading branch information
Showing
18 changed files
with
398 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,14 @@ | ||
# PeriodPals | ||
|
||
Our app is an inclusive cycle tracking tool designed to **support menstruating individuals** thanks to **innovative community support tools**, along with period tracking and symptom logging. | ||
These new community features constitute a key innovation: users can discreetly request menstrual products from nearby users through geolocation-based notifications, ensuring privacy and encouraging real-time support. This feature not only helps users in need but also fosters a supportive community, making our app a comprehensive solution for menstrual health management. | ||
Our app is an inclusive cycle tracking tool designed to **support menstruating individuals** thanks | ||
to **innovative community support tools**, along with period tracking and symptom logging. | ||
These new community features constitute a key innovation: users can discreetly request menstrual | ||
products from nearby users through geolocation-based notifications, ensuring privacy and encouraging | ||
real-time support. This feature not only helps users in need but also fosters a supportive | ||
community, making our app a comprehensive solution for menstrual health management. | ||
|
||
## Links | ||
|
||
[Figma](https://www.figma.com/team_invite/redeem/MnyBeEvw4fKH4aV5aVBpPb) | ||
[Google Drive](https://docs.google.com/document/d/1-qGE7yrF2O_BGeR_vdvgo5ePdevHa0nPuL4w-9gv3MQ/edit?usp=sharing) | ||
[Architecture Diagram on Excalidraw](https://excalidraw.com/#json=lnlBs2IsbkRrnxmz0vlL1,VML2k7MzPW-Jo9j7Nq6rmQ) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
11 changes: 11 additions & 0 deletions
11
app/src/main/java/com/android/periodpals/model/user/User.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package com.android.periodpals.model.user | ||
|
||
/** | ||
* Data class representing a user. | ||
* | ||
* @property name The display name of the user. | ||
* @property imageUrl The URL of the user's profile image. | ||
* @property description A brief description of the user. | ||
* @property dob The date of birth of the user. | ||
*/ | ||
data class User(val name: String, val imageUrl: String, val description: String, val dob: String) |
24 changes: 24 additions & 0 deletions
24
app/src/main/java/com/android/periodpals/model/user/UserDto.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package com.android.periodpals.model.user | ||
|
||
import kotlinx.serialization.Serializable | ||
|
||
/** | ||
* Data Transfer Object (DTO) for user data. | ||
* | ||
* @property displayName The display name of the user. | ||
* @property imageUrl The URL of the user's profile image. | ||
* @property description A brief description of the user. | ||
* @property age The age of the user. | ||
*/ | ||
@Serializable | ||
data class UserDto( | ||
val name: String, | ||
val imageUrl: String, | ||
val description: String, | ||
val dob: String | ||
) { | ||
inline fun asUser(): User { | ||
return User( | ||
name = this.name, imageUrl = this.imageUrl, description = this.description, dob = this.dob) | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
app/src/main/java/com/android/periodpals/model/user/UserModel.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package com.android.periodpals.model.user | ||
|
||
/** Interface for user repository. Defines methods for loading and saving user profiles. */ | ||
interface UserRepository { | ||
/** | ||
* Loads the user profile for the given user ID. | ||
* | ||
* @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) | ||
|
||
/** | ||
* Creates or updates the user profile. | ||
* | ||
* @param user The user profile to be created or updated. | ||
* @param onSuccess callback block to be called on success | ||
* @param onFailure callback block to be called when exception is caught | ||
*/ | ||
suspend fun createUserProfile(user: User, onSuccess: () -> Unit, onFailure: (Exception) -> Unit) | ||
} |
60 changes: 60 additions & 0 deletions
60
app/src/main/java/com/android/periodpals/model/user/UserModelSupabase.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package com.android.periodpals.model.user | ||
|
||
import android.util.Log | ||
import io.github.jan.supabase.SupabaseClient | ||
import io.github.jan.supabase.postgrest.postgrest | ||
import kotlinx.coroutines.Dispatchers | ||
import kotlinx.coroutines.withContext | ||
|
||
/** | ||
* Implementation of UserRepository using Supabase. | ||
* | ||
* @property supabase The Supabase client used for making API calls. | ||
*/ | ||
private const val TAG = "UserRepositorySupabase" | ||
private const val USERS = "users" | ||
|
||
class UserRepositorySupabase(private val supabase: SupabaseClient) : UserRepository { | ||
|
||
override suspend fun loadUserProfile( | ||
onSuccess: (UserDto) -> Unit, | ||
onFailure: (Exception) -> Unit | ||
) { | ||
try { | ||
val result = | ||
withContext(Dispatchers.IO) { | ||
supabase.postgrest[USERS] | ||
.select {} | ||
.decodeSingle<UserDto>() // RLS rules only allows user to check their own line | ||
} | ||
Log.d(TAG, "loadUserProfile: Success") | ||
onSuccess(result) | ||
} catch (e: Exception) { | ||
Log.d(TAG, "loadUserProfile: fail to load user profile: ${e.message}") | ||
onFailure(e) | ||
} | ||
} | ||
|
||
override suspend fun createUserProfile( | ||
user: User, | ||
onSuccess: () -> Unit, | ||
onFailure: (Exception) -> Unit | ||
) { | ||
try { | ||
withContext(Dispatchers.IO) { | ||
val userDto = | ||
UserDto( | ||
name = user.name, | ||
imageUrl = user.imageUrl, | ||
description = user.description, | ||
dob = user.dob) | ||
supabase.postgrest[USERS].insert(userDto) | ||
} | ||
Log.d(TAG, "createUserProfile: Success") | ||
onSuccess() | ||
} catch (e: java.lang.Exception) { | ||
Log.d(TAG, "createUserProfile: fail to create user profile: ${e.message}") | ||
onFailure(e) | ||
} | ||
} | ||
} |
56 changes: 56 additions & 0 deletions
56
app/src/main/java/com/android/periodpals/model/user/UserViewModel.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package com.android.periodpals.model.user | ||
|
||
import android.util.Log | ||
import androidx.lifecycle.ViewModel | ||
import androidx.lifecycle.viewModelScope | ||
import kotlinx.coroutines.flow.MutableStateFlow | ||
import kotlinx.coroutines.flow.StateFlow | ||
import kotlinx.coroutines.launch | ||
|
||
/** | ||
* ViewModel for managing user data. | ||
* | ||
* @property userRepository The repository used for loading and saving user profiles. | ||
*/ | ||
private const val TAG = "UserViewModel" | ||
|
||
class UserViewModel(private val userRepository: UserRepositorySupabase) : ViewModel() { | ||
|
||
private val _user = MutableStateFlow<User?>(null) | ||
val user: StateFlow<User?> = _user | ||
|
||
/** Loads the user profile and updates the user state. */ | ||
fun loadUser() { | ||
viewModelScope.launch { | ||
userRepository.loadUserProfile( | ||
onSuccess = { userDto -> | ||
Log.d(TAG, "loadUserProfile: Succesful") | ||
_user.value = userDto.asUser() | ||
}, | ||
onFailure = { | ||
Log.d(TAG, "loadUserProfile: fail to load user profile: ${it.message}") | ||
_user.value = null | ||
}) | ||
} | ||
} | ||
|
||
/** | ||
* Saves the user profile. | ||
* | ||
* @param user The user profile to be saved. | ||
*/ | ||
fun saveUser(user: User) { | ||
viewModelScope.launch { | ||
userRepository.createUserProfile( | ||
user, | ||
onSuccess = { | ||
Log.d(TAG, "saveUser: Success") | ||
_user.value = user | ||
}, | ||
onFailure = { | ||
Log.d(TAG, "saveUser: fail to save user: ${it.message}") | ||
_user.value = null | ||
}) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> | ||
<background android:drawable="@color/ic_launcher_background"/> | ||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/> | ||
<background android:drawable="@color/ic_launcher_background" /> | ||
<foreground android:drawable="@mipmap/ic_launcher_foreground" /> | ||
</adaptive-icon> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> | ||
<background android:drawable="@color/ic_launcher_background"/> | ||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/> | ||
<background android:drawable="@color/ic_launcher_background" /> | ||
<foreground android:drawable="@mipmap/ic_launcher_foreground" /> | ||
</adaptive-icon> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 16 additions & 0 deletions
16
app/src/test/java/com/android/periodpals/model/user/UserDtoTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package com.android.periodpals.model.user | ||
|
||
import org.junit.Assert.assertEquals | ||
import org.junit.Test | ||
|
||
class UserDtoTest { | ||
|
||
val input = UserDto("test_name", "test_url", "test_desc", "test_dob") | ||
|
||
val output = User("test_name", "test_url", "test_desc", "test_dob") | ||
|
||
@Test | ||
fun asUserIsCorrect() { | ||
assertEquals(output, input.asUser()) | ||
} | ||
} |
102 changes: 102 additions & 0 deletions
102
app/src/test/java/com/android/periodpals/model/user/UserModelSupabaseTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
package com.android.periodpals.model.user | ||
|
||
import io.github.jan.supabase.createSupabaseClient | ||
import io.github.jan.supabase.postgrest.Postgrest | ||
import io.ktor.client.engine.mock.MockEngine | ||
import io.ktor.client.engine.mock.respond | ||
import io.ktor.client.engine.mock.respondBadRequest | ||
import io.mockk.mockk | ||
import kotlinx.coroutines.runBlocking | ||
import org.junit.Assert.assertEquals | ||
import org.junit.Assert.fail | ||
import org.junit.Before | ||
import org.junit.Test | ||
|
||
class UserRepositorySupabaseTest { | ||
|
||
private lateinit var userRepositorySupabase: UserRepositorySupabase | ||
|
||
companion object { | ||
val name = "test_name" | ||
val imageUrl = "test_image" | ||
val description = "test_description" | ||
val dob = "test_dob" | ||
} | ||
|
||
private val defaultUserDto: UserDto = UserDto(name, imageUrl, description, dob) | ||
private val defaultUser: User = User(name, imageUrl, description, dob) | ||
|
||
private val supabaseClientSuccess = | ||
createSupabaseClient("", "") { | ||
httpEngine = MockEngine { _ -> | ||
respond( | ||
content = | ||
"[" + | ||
"{\"name\":\"${name}\"," + | ||
"\"imageUrl\":\"${imageUrl}\"," + | ||
"\"description\":\"${description}\"" + | ||
",\"dob\":\"${dob}\"}" + | ||
"]") | ||
} | ||
install(Postgrest) | ||
} | ||
private val supabaseClientFailure = | ||
createSupabaseClient("", "") { | ||
httpEngine = MockEngine { _ -> respondBadRequest() } | ||
install(Postgrest) | ||
} | ||
|
||
@Before | ||
fun setUp() { | ||
userRepositorySupabase = mockk<UserRepositorySupabase>() | ||
} | ||
|
||
@Test | ||
fun loadUserProfileIsSuccessful() { | ||
var result: UserDto? = null | ||
|
||
runBlocking { | ||
val userRepositorySupabase = UserRepositorySupabase(supabaseClientSuccess) | ||
userRepositorySupabase.loadUserProfile({ result = it }, { fail("should not call onFailure") }) | ||
assertEquals(defaultUserDto, result) | ||
} | ||
} | ||
|
||
@Test | ||
fun loadUserProfileHasFailed() { | ||
var onFailureCalled = false | ||
|
||
runBlocking { | ||
val userRepositorySupabase = UserRepositorySupabase(supabaseClientFailure) | ||
userRepositorySupabase.loadUserProfile( | ||
{ fail("should not call onSuccess") }, | ||
{ onFailureCalled = true }, | ||
) | ||
assert(onFailureCalled) | ||
} | ||
} | ||
|
||
@Test | ||
fun createUserProfileIsSuccessful() { | ||
var result = false | ||
|
||
runBlocking { | ||
val userRepositorySupabase = UserRepositorySupabase(supabaseClientSuccess) | ||
userRepositorySupabase.createUserProfile( | ||
defaultUser, { result = true }, { fail("should not call onFailure") }) | ||
assert(result) | ||
} | ||
} | ||
|
||
@Test | ||
fun createUserProfileHasFailed() { | ||
var result = false | ||
|
||
runBlocking { | ||
val userRepositorySupabase = UserRepositorySupabase(supabaseClientFailure) | ||
userRepositorySupabase.createUserProfile( | ||
defaultUser, { fail("should not call onSuccess") }, { result = true }) | ||
assert(result) | ||
} | ||
} | ||
} |
Oops, something went wrong.