Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refs/heads/feat/profile/powersync #310

Draft
wants to merge 41 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
1ea16e5
feat: add gradle dependencies
coaguila Dec 5, 2024
5f37746
feat: add `asList` to `User.kt` to facilitate use later
coaguila Dec 5, 2024
a25dc58
feat: add `UserModelPowerSync` which should replace `UserModelSupabas…
coaguila Dec 5, 2024
ef6319f
feat: add `asList` to `UserDto.kt` to facilitate use in PowerSync
coaguila Dec 6, 2024
9158b19
feat: implement `upsertUserProfile` to `UserModelPowerSync.kt`
coaguila Dec 6, 2024
8ad3f98
fix: correct `upsertUserProfile` to `UserModelPowerSync.kt`
coaguila Dec 6, 2024
23902a9
feat: add `deleteUserProfile` to `UserModelPowerSync.kt`
coaguila Dec 7, 2024
fb0bc6a
feat: add `loadUserProfile` to `UserModelPowerSync.kt`
coaguila Dec 10, 2024
5fc475d
feat: fix `deleteUserProfile` & `upsertUserProfile` to `UserModelPowe…
coaguila Dec 10, 2024
c2a9ae4
style: ktfmt fortnite
coaguila Dec 10, 2024
146b31d
docs: added some in `UserModelPowerSync
coaguila Dec 10, 2024
4d06fcb
refactor: `loadUserProfile` refactored
coaguila Dec 12, 2024
6e06e41
feat: add `Schema.kt` to represent the local db
coaguila Dec 12, 2024
e541e45
feat: upload to supabase from user model
coaguila Dec 12, 2024
00baf3b
feat: add dependency for powersync innitialization
coaguila Dec 12, 2024
78d8e57
feat: add `Schema.kt` to outline local db
coaguila Dec 12, 2024
57af27b
refactor: make `UserViewModel` take the interface instead of just sup…
coaguila Dec 12, 2024
64c4f82
feat: add powersync into the main activity
coaguila Dec 12, 2024
215f7be
style:ktfmt
coaguila Dec 12, 2024
f4e2d79
Merge branch 'main' of https://github.com/PeriodPals/periodpals into …
coaguila Dec 12, 2024
6f99612
fix
coaguila Dec 12, 2024
86720f4
style: ktfmt
coaguila Dec 12, 2024
ab3c4e8
fix
coaguila Dec 12, 2024
43cbd6c
refactor: `MainActivity` move init of usermodel to main
coaguila Dec 16, 2024
29980cb
refactor: add a connect to loadUserProfile
coaguila Dec 16, 2024
317192f
feat: add syncSupabase to UserModelPowerSync.kt
coaguila Dec 18, 2024
7294cf1
refactor: move all view model initiation to composable function to fa…
coaguila Dec 18, 2024
6b14b8d
feat: add setter for the `PushNotificationsServiceImpl`
coaguila Dec 18, 2024
7183cac
refactor: move `PushNotificiationServiceImpl` out of the composable
coaguila Dec 18, 2024
237a974
Merge branch 'main' of https://github.com/PeriodPals/periodpals into …
coaguila Dec 18, 2024
3cc983d
feat: implement `sync()` in `UserModelPowerSync`
coaguila Dec 19, 2024
4221da7
Merge branch 'main' of github.com:PeriodPals/periodpals into feat/pro…
coaguila Dec 19, 2024
9405afa
refactor: undo unneeded changes
coaguila Dec 19, 2024
57bd02e
Merge branch 'main' of github.com:PeriodPals/periodpals into feat/pro…
coaguila Dec 19, 2024
a4ec706
feat: update `asList` for `User`
coaguila Dec 19, 2024
a726dae
feat: update `asList` for `UserDto`
coaguila Dec 19, 2024
c516ae7
feat: update `users` table in `Schema.kt`
coaguila Dec 19, 2024
e6926ee
feat: implement a new view model only for with powersync + supabase
coaguila Dec 20, 2024
ffad65e
feat: update `UserModelPowerSync` to the current db topography
coaguila Dec 20, 2024
4009b2f
ktfmt: f o r m a t m e
coaguila Dec 20, 2024
a87cd97
fix: buggy at edit release
coaguila Dec 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,10 @@ dependencies {
implementation(libs.kotlinx.serialization.json.v162)
implementation(libs.storage.kt)

implementation("com.powersync:core:1.0.0-BETA9")
implementation("com.powersync:connector-supabase:1.0.0-BETA9")
implementation("com.powersync:compose:1.0.0-BETA11")

implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.material)
Expand Down
35 changes: 27 additions & 8 deletions app/src/main/java/com/android/periodpals/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.viewmodel.compose.viewModel
Expand All @@ -30,8 +31,10 @@ import com.android.periodpals.model.timer.TimerManager
import com.android.periodpals.model.timer.TimerRepositorySupabase
import com.android.periodpals.model.timer.TimerViewModel
import com.android.periodpals.model.user.UserAuthenticationState
import com.android.periodpals.model.user.UserModelPowerSync
import com.android.periodpals.model.user.UserRepositorySupabase
import com.android.periodpals.model.user.UserViewModel
import com.android.periodpals.resources.localSchema
import com.android.periodpals.services.GPSServiceImpl
import com.android.periodpals.services.PushNotificationsService
import com.android.periodpals.services.PushNotificationsServiceImpl
Expand All @@ -51,6 +54,9 @@ import com.android.periodpals.ui.settings.SettingsScreen
import com.android.periodpals.ui.theme.PeriodPalsAppTheme
import com.android.periodpals.ui.timer.TimerScreen
import com.google.android.gms.common.GoogleApiAvailability
import com.powersync.PowerSyncDatabase
import com.powersync.compose.rememberDatabaseDriverFactory
import com.powersync.connector.supabase.SupabaseConnector
import io.getstream.chat.android.client.ChatClient
import io.getstream.chat.android.client.logger.ChatLogLevel
import io.getstream.chat.android.compose.ui.channels.ChannelsScreen
Expand All @@ -59,6 +65,7 @@ import io.getstream.chat.android.models.InitializationState
import io.getstream.chat.android.offline.plugin.factory.StreamOfflinePluginFactory
import io.getstream.chat.android.state.plugin.config.StatePluginConfig
import io.getstream.chat.android.state.plugin.factory.StreamStatePluginFactory
import io.github.jan.supabase.SupabaseClient
import io.github.jan.supabase.auth.Auth
import io.github.jan.supabase.createSupabaseClient
import io.github.jan.supabase.postgrest.Postgrest
Expand Down Expand Up @@ -88,7 +95,7 @@ class MainActivity : ComponentActivity() {
private val authenticationViewModel = AuthenticationViewModel(authModel)

private val userModel = UserRepositorySupabase(supabaseClient)
private val userViewModel = UserViewModel(userModel)
private val userViewModelSupabase = UserViewModel(userModel)

private val userLocationModel = UserLocationModelSupabase(supabaseClient)
private val userLocationViewModel = UserLocationViewModel(userLocationModel)
Expand All @@ -105,7 +112,7 @@ class MainActivity : ComponentActivity() {

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

Expand Down Expand Up @@ -134,10 +141,12 @@ class MainActivity : ComponentActivity() {
// A surface container using the 'background' color from the theme
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
PeriodPalsApp(
this,
supabaseClient,
gpsService,
pushNotificationsService,
authenticationViewModel,
userViewModel,
userViewModelSupabase,
alertViewModel,
timerViewModel,
chatClient,
Expand Down Expand Up @@ -185,6 +194,8 @@ fun userAuthStateLogic(

@Composable
fun PeriodPalsApp(
mainActivity: MainActivity,
supabase: SupabaseClient,
gpsService: GPSServiceImpl,
pushNotificationsService: PushNotificationsService,
authenticationViewModel: AuthenticationViewModel,
Expand All @@ -194,6 +205,13 @@ fun PeriodPalsApp(
chatClient: ChatClient,
chatViewModel: ChatViewModel,
) {
val dbDriverFactory = rememberDatabaseDriverFactory()
val db = remember { PowerSyncDatabase(dbDriverFactory, schema = localSchema) }
val supabaseConnector = remember { SupabaseConnector(supabase, BuildConfig.POWERSYNC_URL) }

val userModelPowerSync = remember { UserModelPowerSync(db, supabaseConnector, supabase) }
val userViewModelPowerSync = remember { UserViewModel(userModelPowerSync) }

val navController = rememberNavController()
val navigationActions = NavigationActions(navController)

Expand Down Expand Up @@ -231,8 +249,7 @@ fun PeriodPalsApp(
authenticationViewModel,
locationViewModel,
gpsService,
navigationActions,
)
navigationActions)
}
composable(Screen.EDIT_ALERT) {
EditAlertScreen(locationViewModel, gpsService, alertViewModel, navigationActions)
Expand Down Expand Up @@ -288,16 +305,18 @@ fun PeriodPalsApp(
navigation(startDestination = Screen.PROFILE, route = Route.PROFILE) {
composable(Screen.PROFILE) {
ProfileScreen(
userViewModel,
userViewModelPowerSync,
authenticationViewModel,
pushNotificationsService,
chatViewModel,
navigationActions,
)
}
composable(Screen.EDIT_PROFILE) { EditProfileScreen(userViewModel, navigationActions) }
composable(Screen.EDIT_PROFILE) {
EditProfileScreen(userViewModelPowerSync, navigationActions)
}
composable(Screen.SETTINGS) {
SettingsScreen(userViewModel, authenticationViewModel, navigationActions)
SettingsScreen(userViewModelPowerSync, authenticationViewModel, navigationActions)
}
}
}
Expand Down
8 changes: 5 additions & 3 deletions app/src/main/java/com/android/periodpals/model/user/User.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ data class User(
val description: String,
val dob: String,
val preferredDistance: Int,
val fcmToken: String? = null,
val fcmToken: String? = null
) {
/**
* Converts the User object to a UserDto object.
Expand All @@ -30,7 +30,9 @@ data class User(
description = this.description,
dob = this.dob,
preferred_distance = this.preferredDistance,
fcm_token = this.fcmToken,
)
fcm_token = this.fcmToken)
}

inline fun asList(): List<Any?> =
listOf(name, imageUrl, description, dob, preferredDistance, fcmToken)
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,7 @@ data class UserDto(
fcmToken = this.fcm_token,
)
}

inline fun asList(): List<Any?> =
listOf(name, imageUrl, description, dob, preferred_distance, fcm_token)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
package com.android.periodpals.model.user

import android.util.Log
import com.powersync.PowerSyncDatabase
import com.powersync.connector.supabase.SupabaseConnector
import io.github.jan.supabase.SupabaseClient
import io.github.jan.supabase.auth.auth
import io.github.jan.supabase.storage.storage
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

/**
* Implementation of UserRepository using PowerSync with Supabase.
*
* @property db PowerSync's database used to locally cache everything that calls syncs to online
* servers.
* @property supabase The Supabase client used for making API calls.
*/
private const val TAG = "UserModelPowerSync"
private const val USERS = "users"

class UserModelPowerSync(
private val db: PowerSyncDatabase,
private val connector: SupabaseConnector,
private val supabase: SupabaseClient
) : UserRepository {

private suspend fun sync() {
try {
connector.uploadData(db)
Log.d(TAG, "sync: Success")
} catch (e: Exception) {
Log.d(TAG, "sync: Failure ${e.message}")
}
}

override suspend fun loadUserProfile(
idUser: String,
onSuccess: (UserDto) -> Unit,
onFailure: (Exception) -> Unit
) {
try {
db.connect(connector)
sync()
val user: UserDto =
db.get(
"SELECT name, imageUrl, description, dob, preferred_distance, fcm_token FROM $USERS WHERE user_id = ?",
listOf(idUser)
) {
UserDto(
name = it.getString(0)!!,
imageUrl = it.getString(1)!!,
description = it.getString(2)!!,
dob = it.getString(3)!!,
preferred_distance = it.getLong(4)!!.toInt(),
fcm_token = it.getString(5)
)
}

Log.d(TAG, "loadUserProfile: Success")
onSuccess(user)
} catch (e: Exception) {
Log.d(TAG, "loadUserProfile: fail to load user profile: ${e.message}")
onFailure(e)
}
}

override suspend fun loadUserProfiles(
onSuccess: (List<UserDto>) -> Unit,
onFailure: (Exception) -> Unit
) {
try {
db.connect(connector)
sync()
val users: List<UserDto> =
db.getAll(
"SELECT name, imageUrl, description, dob, preferred_distance, fcm_token, locationGIS FROM $USERS",
listOf()
) {
UserDto(
name = it.getString(0)!!,
imageUrl = it.getString(1)!!,
description = it.getString(2)!!,
dob = it.getString(3)!!,
preferred_distance = it.getLong(4)!!.toInt(),
fcm_token = it.getString(5)
)
}

Log.d(TAG, "loadUserProfiles: Success")
onSuccess(users)
} catch (e: Exception) {
Log.d(TAG, "loadUserProfiles: fail to load users profiles: ${e.message}")
onFailure(e)
}
}

override suspend fun createUserProfile(
user: User,
onSuccess: () -> Unit,
onFailure: (Exception) -> Unit
) {
try {
db.connect(connector)
db.writeTransaction { tx ->
tx.execute(
"INSERT INTO $USERS (name, imageUrl, description, dob, preferred_distance, fcm_token) VALUES (?, ?, ?, ?, ?, ?);",
user.asList()
)
}
Log.d(TAG, "createUserProfile: Success")
onSuccess()
sync()
} catch (e: Exception) {
Log.d(TAG, "createUserProfile: fail to create user profile: ${e.message}")
onFailure(e)
}
}

override suspend fun upsertUserProfile(
userDto: UserDto,
onSuccess: (UserDto) -> Unit,
onFailure: (Exception) -> Unit
) {
try {
db.connect(connector)
val currUser: String? = supabase.auth.currentUserOrNull()?.id
Log.d(TAG, "b4")
sync()
db.writeTransaction { tx ->
tx.execute(
"""
INSERT INTO $USERS(user_id, name, imageUrl, description, dob, preferred_distance, fcm_token)
VALUES(?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(user_id)
DO
UPDATE SET name = ?, imageUrl = ?, description = ?, dob = ?, preferred_distance = ?, fcm_token = ?
WHERE user_id = ?;
""",
listOf(
currUser,
userDto.name,
userDto.imageUrl,
userDto.description,
userDto.dob,
userDto.preferred_distance,
userDto.fcm_token,
userDto.name,
userDto.imageUrl,
userDto.description,
userDto.dob,
userDto.preferred_distance,
userDto.fcm_token,
currUser
)
)
}
Log.d(TAG, "upsertUserProfile: Success")
sync()
onSuccess(userDto)
} catch (e: Exception) {
Log.d(TAG, "upsertUserProfile: fail to create user profile: ${e.message}")
onFailure(e)
}
}

override suspend fun deleteUserProfile(
idUser: String,
onSuccess: () -> Unit,
onFailure: (Exception) -> Unit
) {
try {
val currUser =
supabase.auth.currentUserOrNull()?.id
?: throw Exception("Supabase does not have a user logged in")

db.writeTransaction { tx ->
tx.execute("DELETE FROM $USERS WHERE user_id = ?", listOf(currUser))
}
Log.d(TAG, "deleteUserProfile: Success")
sync()
onSuccess()
} catch (e: Exception) {
Log.d(TAG, "deleteUserProfile: fail to delete user profile: ${e.message}")
onFailure(e)
}
}

override suspend fun uploadFile(
filePath: String,
bytes: ByteArray,
onSuccess: () -> Unit,
onFailure: (Exception) -> Unit,
) {
try {
withContext(Dispatchers.Main) {
supabase.storage.from("avatars").upload("$filePath.jpg", bytes) { upsert = true }
}
Log.d(TAG, "uploadFile: Success")
onSuccess()
} catch (e: Exception) {
Log.d(TAG, "uploadFile: fail to upload file: ${e.message}")
onFailure(e)
}
}

override suspend fun downloadFile(
filePath: String,
onSuccess: (bytes: ByteArray) -> Unit,
onFailure: (Exception) -> Unit,
) {
try {
withContext(Dispatchers.Main) {
val file = supabase.storage.from("avatars").downloadAuthenticated("$filePath.jpg")
Log.d(TAG, "downloadFile: Success")
onSuccess(file)
}
} catch (e: Exception) {
Log.d(TAG, "downloadFile: fail to download file: ${e.message}")
onFailure(e)
}
}
}
Loading
Loading