From 1ea16e5eee0b4c38d86552b7a9bf72845b65cd46 Mon Sep 17 00:00:00 2001 From: Alonso Date: Thu, 5 Dec 2024 21:13:07 +0100 Subject: [PATCH 01/37] feat: add gradle dependencies --- app/build.gradle.kts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1495679ba..bd959f788 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -179,6 +179,9 @@ dependencies { implementation(libs.ktor.client.android.v300rc1) implementation(libs.kotlinx.serialization.json.v162) + implementation("com.powersync:core:1.0.0-BETA9") + implementation("com.powersync:connector-supabase:1.0.0-BETA9") + implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) implementation(libs.material) From 5f377468360921e51dcfa02bff74dd1ceaf7e533 Mon Sep 17 00:00:00 2001 From: Alonso Date: Thu, 5 Dec 2024 23:58:28 +0100 Subject: [PATCH 02/37] feat: add `asList` to `User.kt` to facilitate use later --- app/src/main/java/com/android/periodpals/model/user/User.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/com/android/periodpals/model/user/User.kt b/app/src/main/java/com/android/periodpals/model/user/User.kt index 5d3f7a021..3923c6639 100644 --- a/app/src/main/java/com/android/periodpals/model/user/User.kt +++ b/app/src/main/java/com/android/periodpals/model/user/User.kt @@ -13,4 +13,6 @@ data class User(val name: String, val imageUrl: String, val description: String, return UserDto( name = this.name, imageUrl = this.imageUrl, description = this.description, dob = this.dob) } + + inline fun asList(): List = listOf(name, imageUrl, description, dob) } From a25dc58d67f4b9266bda83b14eed454471f427a6 Mon Sep 17 00:00:00 2001 From: Alonso Date: Fri, 6 Dec 2024 00:00:05 +0100 Subject: [PATCH 03/37] feat: add `UserModelPowerSync` which should replace `UserModelSupabase` to enable a local cache of profile --- .../model/user/UserModelPowerSync.kt | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt diff --git a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt new file mode 100644 index 000000000..e69b1e9eb --- /dev/null +++ b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt @@ -0,0 +1,56 @@ +package com.android.periodpals.model.user + +import android.util.Log +import com.powersync.PowerSyncDatabase +import com.powersync.connector.supabase.SupabaseConnector + +private const val TAG = "UserModelPowerSync" +private const val USERS = "users" + +class UserModelPowerSync( + private val db: PowerSyncDatabase +) : UserRepository{ + override suspend fun loadUserProfile( + onSuccess: (UserDto) -> Unit, + onFailure: (Exception) -> Unit + ) { + TODO("Not yet implemented") + } + + override suspend fun createUserProfile( + user: User, + onSuccess: () -> Unit, + onFailure: (Exception) -> Unit + ) { + try { + db.writeTransaction { tx -> + tx.execute( + " INSERT INTO $USERS (name, imageUrl, description, dob) VALUES (?, ?, ?, ?);", + user.asList() + ) + } + Log.d(TAG, "createUserProfile: Success") + onSuccess() + } 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 + ) { + TODO("Not yet implemented") + } + + override suspend fun deleteUserProfile( + idUser: String, + onSuccess: () -> Unit, + onFailure: (Exception) -> Unit + ) { + TODO("Not yet implemented") + } + +} \ No newline at end of file From ef6319f776d42e398505807be36df1e0a513e389 Mon Sep 17 00:00:00 2001 From: Alonso Date: Fri, 6 Dec 2024 16:38:21 +0100 Subject: [PATCH 04/37] feat: add `asList` to `UserDto.kt` to facilitate use in PowerSync --- app/src/main/java/com/android/periodpals/model/user/UserDto.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/com/android/periodpals/model/user/UserDto.kt b/app/src/main/java/com/android/periodpals/model/user/UserDto.kt index d786d3ff9..8076b32a5 100644 --- a/app/src/main/java/com/android/periodpals/model/user/UserDto.kt +++ b/app/src/main/java/com/android/periodpals/model/user/UserDto.kt @@ -21,4 +21,6 @@ data class UserDto( return User( name = this.name, imageUrl = this.imageUrl, description = this.description, dob = this.dob) } + + inline fun asList(): List = listOf(name, imageUrl, description, dob) } From 9158b193dae154007d074a4c6e9afd50c0394e3d Mon Sep 17 00:00:00 2001 From: Alonso Date: Fri, 6 Dec 2024 16:43:03 +0100 Subject: [PATCH 05/37] feat: implement `upsertUserProfile` to `UserModelPowerSync.kt` --- .../periodpals/model/user/UserModelPowerSync.kt | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt index e69b1e9eb..56a2a8b67 100644 --- a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt +++ b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt @@ -10,6 +10,7 @@ private const val USERS = "users" class UserModelPowerSync( private val db: PowerSyncDatabase ) : UserRepository{ + override suspend fun loadUserProfile( onSuccess: (UserDto) -> Unit, onFailure: (Exception) -> Unit @@ -25,7 +26,7 @@ class UserModelPowerSync( try { db.writeTransaction { tx -> tx.execute( - " INSERT INTO $USERS (name, imageUrl, description, dob) VALUES (?, ?, ?, ?);", + "INSERT INTO $USERS (name, imageUrl, description, dob) VALUES (?, ?, ?, ?);", user.asList() ) } @@ -42,6 +43,17 @@ class UserModelPowerSync( onSuccess: (UserDto) -> Unit, onFailure: (Exception) -> Unit ) { + try { + db.writeTransaction { + "UPSERT INTO $USERS (name, imageUrl, description, dob) VALUES (?, ?, ?, ?);", + userDto.asList() + } + Log.d(TAG, "upsertUserProfile: Success") + onSuccess(userDto) + } catch (e: Exception) { + Log.d(TAG, "upsertUserProfile: fail to create user profile: ${e.message}") + onFailure(e) + } TODO("Not yet implemented") } From 8ad3f9806de36a2c673cee6cc0a8f637c83f9cd3 Mon Sep 17 00:00:00 2001 From: Alonso Date: Fri, 6 Dec 2024 16:44:03 +0100 Subject: [PATCH 06/37] fix: correct `upsertUserProfile` to `UserModelPowerSync.kt` --- .../android/periodpals/model/user/UserModelPowerSync.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt index 56a2a8b67..55cdfddca 100644 --- a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt +++ b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt @@ -44,9 +44,11 @@ class UserModelPowerSync( onFailure: (Exception) -> Unit ) { try { - db.writeTransaction { - "UPSERT INTO $USERS (name, imageUrl, description, dob) VALUES (?, ?, ?, ?);", - userDto.asList() + db.writeTransaction { tx -> + tx.execute( + "UPSERT INTO $USERS (name, imageUrl, description, dob) VALUES (?, ?, ?, ?);", + userDto.asList() + ) } Log.d(TAG, "upsertUserProfile: Success") onSuccess(userDto) From 23902a9c514625d4bd8b6fafb5d9550f28892acd Mon Sep 17 00:00:00 2001 From: Alonso Date: Sat, 7 Dec 2024 18:19:11 +0100 Subject: [PATCH 07/37] feat: add `deleteUserProfile` to `UserModelPowerSync.kt` --- .../periodpals/model/user/UserModelPowerSync.kt | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt index 55cdfddca..68c075a36 100644 --- a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt +++ b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt @@ -64,7 +64,19 @@ class UserModelPowerSync( onSuccess: () -> Unit, onFailure: (Exception) -> Unit ) { - TODO("Not yet implemented") + try { + db.writeTransaction { tx -> + tx.execute( + "DELETE FROM $USERS WHERE id=?", + listOf(idUser) + ) + } + Log.d(TAG, "deleteUserProfile: Success") + onSuccess() + } catch (e: Exception) { + Log.d(TAG, "deleteUserProfile: fail to delete user profile: ${e.message}") + onFailure(e) + } } } \ No newline at end of file From fb0bc6ada92e3248c1e042e4a018b9542b3c8068 Mon Sep 17 00:00:00 2001 From: Alonso Date: Tue, 10 Dec 2024 20:53:13 +0100 Subject: [PATCH 08/37] feat: add `loadUserProfile` to `UserModelPowerSync.kt` --- .../model/user/UserModelPowerSync.kt | 49 +++++++++++++++++-- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt index 68c075a36..86807b5a1 100644 --- a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt +++ b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt @@ -3,19 +3,53 @@ 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.auth.user.UserInfo private const val TAG = "UserModelPowerSync" private const val USERS = "users" class UserModelPowerSync( - private val db: PowerSyncDatabase + private val db: PowerSyncDatabase, + private val supabase: SupabaseClient ) : UserRepository{ override suspend fun loadUserProfile( onSuccess: (UserDto) -> Unit, onFailure: (Exception) -> Unit ) { - TODO("Not yet implemented") + try { + val currUser: String? = supabase.auth.currentUserOrNull()?.id + var user: UserDto? = null + + if (currUser == null) { + throw Exception("Supabase does not have a user logged in") + } + + db.writeTransaction { tx -> + user = tx.getOptional ( + "SELECT name, imageUrl, description, dob from $USERS where user_id = ?", + listOf(currUser) + ) { + UserDto( + name = it.getString(0)!!, + imageUrl = it.getString(1)!!, + description = it.getString(2)!!, + dob = it.getString(3)!! + ) + } + } + if (user == null) { + throw Exception("PowerSync failure did not fetch correctly") + } + + 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 createUserProfile( @@ -44,9 +78,16 @@ class UserModelPowerSync( onFailure: (Exception) -> Unit ) { try { + val currUser: String? = supabase.auth.currentUserOrNull()?.id + db.writeTransaction { tx -> tx.execute( - "UPSERT INTO $USERS (name, imageUrl, description, dob) VALUES (?, ?, ?, ?);", + """ + INSERT INTO $USERS (name, imageUrl, description, dob) + VALUES (?, ?, ?, ?) + ON CONFLICT (id) + """, + // "UPSERT INTO $USERS (name, imageUrl, description, dob) VALUES (?, ?, ?, ?);", userDto.asList() ) } @@ -56,7 +97,6 @@ class UserModelPowerSync( Log.d(TAG, "upsertUserProfile: fail to create user profile: ${e.message}") onFailure(e) } - TODO("Not yet implemented") } override suspend fun deleteUserProfile( @@ -64,6 +104,7 @@ class UserModelPowerSync( onSuccess: () -> Unit, onFailure: (Exception) -> Unit ) { + // TODO: NEEDS REFACTORING try { db.writeTransaction { tx -> tx.execute( From 5fc475d9b2119f9ebdf5750c49ebf06d6d714575 Mon Sep 17 00:00:00 2001 From: Alonso Date: Tue, 10 Dec 2024 21:04:33 +0100 Subject: [PATCH 09/37] feat: fix `deleteUserProfile` & `upsertUserProfile` to `UserModelPowerSync.kt` --- .../model/user/UserModelPowerSync.kt | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt index 86807b5a1..4f00c0868 100644 --- a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt +++ b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt @@ -20,13 +20,9 @@ class UserModelPowerSync( onFailure: (Exception) -> Unit ) { try { - val currUser: String? = supabase.auth.currentUserOrNull()?.id + val currUser = supabase.auth.currentUserOrNull()?.id ?: throw Exception("Supabase does not have a user logged in") var user: UserDto? = null - if (currUser == null) { - throw Exception("Supabase does not have a user logged in") - } - db.writeTransaction { tx -> user = tx.getOptional ( "SELECT name, imageUrl, description, dob from $USERS where user_id = ?", @@ -83,12 +79,15 @@ class UserModelPowerSync( db.writeTransaction { tx -> tx.execute( """ - INSERT INTO $USERS (name, imageUrl, description, dob) - VALUES (?, ?, ?, ?) - ON CONFLICT (id) + INSERT INTO $USERS (user_id, name, imageUrl, description, dob) + VALUES (?, ?, ?, ?, ?) + ON CONFLICT (user_id) + DO UPDATE SET name = ?, imageUrl = ?, description = ?, dob = ?; """, - // "UPSERT INTO $USERS (name, imageUrl, description, dob) VALUES (?, ?, ?, ?);", - userDto.asList() + listOf( + currUser, userDto.name, userDto.imageUrl, userDto.description, userDto.dob, + userDto.name, userDto.imageUrl, userDto.description, userDto.dob + ) ) } Log.d(TAG, "upsertUserProfile: Success") @@ -104,12 +103,13 @@ class UserModelPowerSync( onSuccess: () -> Unit, onFailure: (Exception) -> Unit ) { - // TODO: NEEDS REFACTORING 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 id=?", - listOf(idUser) + "DELETE FROM $USERS WHERE user_id=?", + listOf(currUser) ) } Log.d(TAG, "deleteUserProfile: Success") From c2a9ae4890972a8e00e8e87f2f8749937fd3b507 Mon Sep 17 00:00:00 2001 From: Alonso Date: Tue, 10 Dec 2024 21:08:51 +0100 Subject: [PATCH 10/37] style: ktfmt fortnite --- .../model/user/UserModelPowerSync.kt | 189 +++++++++--------- 1 file changed, 94 insertions(+), 95 deletions(-) diff --git a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt index 4f00c0868..9be312175 100644 --- a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt +++ b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt @@ -2,122 +2,121 @@ 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.auth.user.UserInfo private const val TAG = "UserModelPowerSync" private const val USERS = "users" -class UserModelPowerSync( - private val db: PowerSyncDatabase, - private val supabase: SupabaseClient -) : UserRepository{ +class UserModelPowerSync(private val db: PowerSyncDatabase, private val supabase: SupabaseClient) : + UserRepository { - override suspend fun loadUserProfile( - onSuccess: (UserDto) -> Unit, - onFailure: (Exception) -> Unit - ) { - try { - val currUser = supabase.auth.currentUserOrNull()?.id ?: throw Exception("Supabase does not have a user logged in") - var user: UserDto? = null + override suspend fun loadUserProfile( + onSuccess: (UserDto) -> Unit, + onFailure: (Exception) -> Unit + ) { + try { + val currUser = + supabase.auth.currentUserOrNull()?.id + ?: throw Exception("Supabase does not have a user logged in") + var user: UserDto? = null - db.writeTransaction { tx -> - user = tx.getOptional ( - "SELECT name, imageUrl, description, dob from $USERS where user_id = ?", - listOf(currUser) - ) { - UserDto( - name = it.getString(0)!!, - imageUrl = it.getString(1)!!, - description = it.getString(2)!!, - dob = it.getString(3)!! - ) + db.writeTransaction { tx -> + user = + tx.getOptional( + "SELECT name, imageUrl, description, dob FROM $USERS WHERE user_id = ?", + listOf(currUser)) { + UserDto( + name = it.getString(0)!!, + imageUrl = it.getString(1)!!, + description = it.getString(2)!!, + dob = it.getString(3)!!) } - } - if (user == null) { - throw Exception("PowerSync failure did not fetch correctly") - } + } + if (user == null) { + throw Exception("PowerSync failure did not fetch correctly") + } - Log.d(TAG, "loadUserProfile: Success") - onSuccess(user!!) - } catch (e: Exception) { - Log.d(TAG, "loadUserProfile: fail to load user profile: ${e.message}") - onFailure(e) - } + 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 createUserProfile( - user: User, - onSuccess: () -> Unit, - onFailure: (Exception) -> Unit - ) { - try { - db.writeTransaction { tx -> - tx.execute( - "INSERT INTO $USERS (name, imageUrl, description, dob) VALUES (?, ?, ?, ?);", - user.asList() - ) - } - Log.d(TAG, "createUserProfile: Success") - onSuccess() - } catch (e: Exception) { - Log.d(TAG, "createUserProfile: fail to create user profile: ${e.message}") - onFailure(e) - } + override suspend fun createUserProfile( + user: User, + onSuccess: () -> Unit, + onFailure: (Exception) -> Unit + ) { + try { + db.writeTransaction { tx -> + tx.execute( + "INSERT INTO $USERS (name, imageUrl, description, dob) VALUES (?, ?, ?, ?);", + user.asList()) + } + Log.d(TAG, "createUserProfile: Success") + onSuccess() + } 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 { - val currUser: String? = supabase.auth.currentUserOrNull()?.id + override suspend fun upsertUserProfile( + userDto: UserDto, + onSuccess: (UserDto) -> Unit, + onFailure: (Exception) -> Unit + ) { + try { + val currUser: String? = supabase.auth.currentUserOrNull()?.id - db.writeTransaction { tx -> - tx.execute( - """ + db.writeTransaction { tx -> + tx.execute( + """ INSERT INTO $USERS (user_id, name, imageUrl, description, dob) VALUES (?, ?, ?, ?, ?) ON CONFLICT (user_id) DO UPDATE SET name = ?, imageUrl = ?, description = ?, dob = ?; """, - listOf( - currUser, userDto.name, userDto.imageUrl, userDto.description, userDto.dob, - userDto.name, userDto.imageUrl, userDto.description, userDto.dob - ) - ) - } - Log.d(TAG, "upsertUserProfile: Success") - onSuccess(userDto) - } catch (e: Exception) { - Log.d(TAG, "upsertUserProfile: fail to create user profile: ${e.message}") - onFailure(e) - } + listOf( + currUser, + userDto.name, + userDto.imageUrl, + userDto.description, + userDto.dob, + userDto.name, + userDto.imageUrl, + userDto.description, + userDto.dob)) + } + Log.d(TAG, "upsertUserProfile: Success") + 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") + 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") - onSuccess() - } catch (e: Exception) { - Log.d(TAG, "deleteUserProfile: fail to delete user profile: ${e.message}") - onFailure(e) - } + db.writeTransaction { tx -> + tx.execute("DELETE FROM $USERS WHERE user_id = ?", listOf(currUser)) + } + Log.d(TAG, "deleteUserProfile: Success") + onSuccess() + } catch (e: Exception) { + Log.d(TAG, "deleteUserProfile: fail to delete user profile: ${e.message}") + onFailure(e) } - -} \ No newline at end of file + } +} From 146b31ddd3e63e8be9593091851b523263463158 Mon Sep 17 00:00:00 2001 From: Alonso Date: Tue, 10 Dec 2024 21:20:28 +0100 Subject: [PATCH 11/37] docs: added some in `UserModelPowerSync ` --- .../com/android/periodpals/model/user/UserModelPowerSync.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt index 9be312175..a00225427 100644 --- a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt +++ b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt @@ -5,6 +5,12 @@ import com.powersync.PowerSyncDatabase import io.github.jan.supabase.SupabaseClient import io.github.jan.supabase.auth.auth +/** + * 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" From 4d06fcb49b7061ea8e7cc12747bea54efdc2e8a5 Mon Sep 17 00:00:00 2001 From: Alonso Date: Thu, 12 Dec 2024 12:08:34 +0100 Subject: [PATCH 12/37] refactor: `loadUserProfile` refactored ` --- .../model/user/UserModelPowerSync.kt | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt index a00225427..e7a20c71e 100644 --- a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt +++ b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt @@ -25,26 +25,25 @@ class UserModelPowerSync(private val db: PowerSyncDatabase, private val supabase val currUser = supabase.auth.currentUserOrNull()?.id ?: throw Exception("Supabase does not have a user logged in") - var user: UserDto? = null - db.writeTransaction { tx -> - user = - tx.getOptional( - "SELECT name, imageUrl, description, dob FROM $USERS WHERE user_id = ?", - listOf(currUser)) { - UserDto( - name = it.getString(0)!!, - imageUrl = it.getString(1)!!, - description = it.getString(2)!!, - dob = it.getString(3)!!) - } - } + val user: UserDto? = + db.writeTransaction { tx -> + tx.getOptional( + "SELECT name, imageUrl, description, dob FROM $USERS WHERE user_id = ?", + listOf(currUser)) { + UserDto( + name = it.getString(0)!!, + imageUrl = it.getString(1)!!, + description = it.getString(2)!!, + dob = it.getString(3)!!) + } + } if (user == null) { throw Exception("PowerSync failure did not fetch correctly") } Log.d(TAG, "loadUserProfile: Success") - onSuccess(user!!) + onSuccess(user) } catch (e: Exception) { Log.d(TAG, "loadUserProfile: fail to load user profile: ${e.message}") onFailure(e) From 6e06e410558768f45763668b9e711194dde3b28e Mon Sep 17 00:00:00 2001 From: Alonso Date: Thu, 12 Dec 2024 13:56:23 +0100 Subject: [PATCH 13/37] feat: add `Schema.kt` to represent the local db ` --- .../android/periodpals/resources/Schema.kt | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 app/src/main/java/com/android/periodpals/resources/Schema.kt diff --git a/app/src/main/java/com/android/periodpals/resources/Schema.kt b/app/src/main/java/com/android/periodpals/resources/Schema.kt new file mode 100644 index 000000000..d2b57b7bb --- /dev/null +++ b/app/src/main/java/com/android/periodpals/resources/Schema.kt @@ -0,0 +1,22 @@ +package com.android.periodpals.resources + +import com.powersync.db.schema.Column +import com.powersync.db.schema.Table + +const val USERS = "users" + +val users = Table( + USERS, + listOf( + Column.text("user_id"), + Column.text("name"), + Column.text("email"), + Column.text("imageUrl"), + Column.text("description"), + Column.text("dob"), + Column.text("id"), + Column.text("fcm_token"), + Column.integer("erred_distance"), + Column.text("locationGIS") + ) +) \ No newline at end of file From e541e45c224002645dbe24a32f9fe38814a8d2fa Mon Sep 17 00:00:00 2001 From: Alonso Date: Thu, 12 Dec 2024 13:58:30 +0100 Subject: [PATCH 14/37] feat: upload to supabase from user model ` --- .../android/periodpals/model/user/UserModelPowerSync.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt index e7a20c71e..a2e60ac9f 100644 --- a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt +++ b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt @@ -2,6 +2,7 @@ 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 @@ -14,9 +15,11 @@ import io.github.jan.supabase.auth.auth private const val TAG = "UserModelPowerSync" private const val USERS = "users" -class UserModelPowerSync(private val db: PowerSyncDatabase, private val supabase: SupabaseClient) : +class UserModelPowerSync(private val db: PowerSyncDatabase, private val connector: SupabaseConnector, private val supabase: SupabaseClient) : UserRepository { + suspend fun syncDatabase() = connector.uploadData(db) + override suspend fun loadUserProfile( onSuccess: (UserDto) -> Unit, onFailure: (Exception) -> Unit @@ -63,6 +66,7 @@ class UserModelPowerSync(private val db: PowerSyncDatabase, private val supabase } Log.d(TAG, "createUserProfile: Success") onSuccess() + connector.uploadData(db) } catch (e: Exception) { Log.d(TAG, "createUserProfile: fail to create user profile: ${e.message}") onFailure(e) @@ -98,6 +102,7 @@ class UserModelPowerSync(private val db: PowerSyncDatabase, private val supabase } Log.d(TAG, "upsertUserProfile: Success") onSuccess(userDto) + connector.uploadData(db) } catch (e: Exception) { Log.d(TAG, "upsertUserProfile: fail to create user profile: ${e.message}") onFailure(e) @@ -119,6 +124,7 @@ class UserModelPowerSync(private val db: PowerSyncDatabase, private val supabase } Log.d(TAG, "deleteUserProfile: Success") onSuccess() + connector.uploadData(db) } catch (e: Exception) { Log.d(TAG, "deleteUserProfile: fail to delete user profile: ${e.message}") onFailure(e) From 00baf3b95be7acfc41b8e0729b76fda8ad8f383a Mon Sep 17 00:00:00 2001 From: Alonso Date: Thu, 12 Dec 2024 19:06:01 +0100 Subject: [PATCH 15/37] feat: add dependency for powersync innitialization ` --- app/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index bd959f788..8890f7925 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -181,6 +181,7 @@ dependencies { 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) From 78d8e57a6ac1350d1b98d9ddd94a822b25861e16 Mon Sep 17 00:00:00 2001 From: Alonso Date: Thu, 12 Dec 2024 19:06:55 +0100 Subject: [PATCH 16/37] feat: add `Schema.kt` to outline local db --- .../main/java/com/android/periodpals/resources/Schema.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/java/com/android/periodpals/resources/Schema.kt b/app/src/main/java/com/android/periodpals/resources/Schema.kt index d2b57b7bb..a5c3c0d3e 100644 --- a/app/src/main/java/com/android/periodpals/resources/Schema.kt +++ b/app/src/main/java/com/android/periodpals/resources/Schema.kt @@ -1,6 +1,7 @@ package com.android.periodpals.resources import com.powersync.db.schema.Column +import com.powersync.db.schema.Schema import com.powersync.db.schema.Table const val USERS = "users" @@ -19,4 +20,10 @@ val users = Table( Column.integer("erred_distance"), Column.text("locationGIS") ) +) + +val localSchema: Schema = Schema( + listOf( + users + ) ) \ No newline at end of file From 57af27b51e350a98acfc7a66e2b20bd09e8933da Mon Sep 17 00:00:00 2001 From: Alonso Date: Thu, 12 Dec 2024 19:07:28 +0100 Subject: [PATCH 17/37] refactor: make `UserViewModel` take the interface instead of just supabase --- .../java/com/android/periodpals/model/user/UserViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/android/periodpals/model/user/UserViewModel.kt b/app/src/main/java/com/android/periodpals/model/user/UserViewModel.kt index d8f3f6eb8..e5e3d2b29 100644 --- a/app/src/main/java/com/android/periodpals/model/user/UserViewModel.kt +++ b/app/src/main/java/com/android/periodpals/model/user/UserViewModel.kt @@ -47,7 +47,7 @@ private val profileImageValidators = * * @property userRepository The repository used for loading and saving user profiles. */ -class UserViewModel(private val userRepository: UserRepositorySupabase) : ViewModel() { +class UserViewModel(private val userRepository: UserRepository) : ViewModel() { companion object { const val NAME_STATE_NAME = "name" const val DESCRIPTION_STATE_NAME = "description" From 64c4f829a4310ed544ed41c48c2c2d5fd747f348 Mon Sep 17 00:00:00 2001 From: Alonso Date: Thu, 12 Dec 2024 19:08:08 +0100 Subject: [PATCH 18/37] feat: add powersync into the main activity --- .../com/android/periodpals/MainActivity.kt | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/android/periodpals/MainActivity.kt b/app/src/main/java/com/android/periodpals/MainActivity.kt index ac38f0319..783fe1f41 100644 --- a/app/src/main/java/com/android/periodpals/MainActivity.kt +++ b/app/src/main/java/com/android/periodpals/MainActivity.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.compose.NavHost @@ -19,8 +20,10 @@ import com.android.periodpals.model.alert.AlertViewModel import com.android.periodpals.model.authentication.AuthenticationModelSupabase import com.android.periodpals.model.authentication.AuthenticationViewModel import com.android.periodpals.model.location.LocationViewModel +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 @@ -40,6 +43,11 @@ 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.DatabaseDriverFactory +import com.powersync.PowerSyncDatabase +import com.powersync.compose.rememberDatabaseDriverFactory +import com.powersync.connector.supabase.SupabaseConnector +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 @@ -60,6 +68,11 @@ class MainActivity : ComponentActivity() { install(Auth) install(Postgrest) } + private val supabaseConnector = + SupabaseConnector( + powerSyncEndpoint = BuildConfig.POWERSYNC_URL, + supabaseClient = supabaseClient + ) private val authModel = AuthenticationModelSupabase(supabaseClient) private val authenticationViewModel = AuthenticationViewModel(authModel) @@ -93,7 +106,9 @@ class MainActivity : ComponentActivity() { gpsService, pushNotificationsService, authenticationViewModel, - userViewModel, + supabaseConnector, + supabaseClient, + //userViewModel, alertViewModel) } } @@ -121,9 +136,16 @@ fun PeriodPalsApp( gpsService: GPSServiceImpl, pushNotificationsService: PushNotificationsService, authenticationViewModel: AuthenticationViewModel, - userViewModel: UserViewModel, + connector: SupabaseConnector, + supabase: SupabaseClient, + //userViewModel: UserViewModel, alertViewModel: AlertViewModel ) { + val driverFactory = rememberDatabaseDriverFactory() + val db = remember{ PowerSyncDatabase( driverFactory, schema = localSchema)} + val userModel = UserModelPowerSync(db, connector, supabase) + val userViewModel = UserViewModel(userModel) + val navController = rememberNavController() val navigationActions = NavigationActions(navController) From 215f7beaf1d797b0b62cb1284c9ada73b1178e8a Mon Sep 17 00:00:00 2001 From: Alonso Date: Thu, 12 Dec 2024 20:00:22 +0100 Subject: [PATCH 19/37] style:ktfmt --- .../com/android/periodpals/MainActivity.kt | 11 +++--- .../model/user/UserModelPowerSync.kt | 34 ++++++++++-------- .../android/periodpals/resources/Schema.kt | 35 ++++++++----------- 3 files changed, 38 insertions(+), 42 deletions(-) diff --git a/app/src/main/java/com/android/periodpals/MainActivity.kt b/app/src/main/java/com/android/periodpals/MainActivity.kt index 783fe1f41..6bbc0cd4d 100644 --- a/app/src/main/java/com/android/periodpals/MainActivity.kt +++ b/app/src/main/java/com/android/periodpals/MainActivity.kt @@ -43,7 +43,6 @@ 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.DatabaseDriverFactory import com.powersync.PowerSyncDatabase import com.powersync.compose.rememberDatabaseDriverFactory import com.powersync.connector.supabase.SupabaseConnector @@ -70,9 +69,7 @@ class MainActivity : ComponentActivity() { } private val supabaseConnector = SupabaseConnector( - powerSyncEndpoint = BuildConfig.POWERSYNC_URL, - supabaseClient = supabaseClient - ) + powerSyncEndpoint = BuildConfig.POWERSYNC_URL, supabaseClient = supabaseClient) private val authModel = AuthenticationModelSupabase(supabaseClient) private val authenticationViewModel = AuthenticationViewModel(authModel) @@ -108,7 +105,7 @@ class MainActivity : ComponentActivity() { authenticationViewModel, supabaseConnector, supabaseClient, - //userViewModel, + // userViewModel, alertViewModel) } } @@ -138,11 +135,11 @@ fun PeriodPalsApp( authenticationViewModel: AuthenticationViewModel, connector: SupabaseConnector, supabase: SupabaseClient, - //userViewModel: UserViewModel, + // userViewModel: UserViewModel, alertViewModel: AlertViewModel ) { val driverFactory = rememberDatabaseDriverFactory() - val db = remember{ PowerSyncDatabase( driverFactory, schema = localSchema)} + val db = remember { PowerSyncDatabase(driverFactory, schema = localSchema) } val userModel = UserModelPowerSync(db, connector, supabase) val userViewModel = UserViewModel(userModel) diff --git a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt index a2e60ac9f..9294e35e4 100644 --- a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt +++ b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt @@ -9,14 +9,18 @@ import io.github.jan.supabase.auth.auth /** * Implementation of UserRepository using PowerSync with Supabase. * - * @property db PowerSync's database used to locally cache everything that calls syncs to online servers. + * @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 { +class UserModelPowerSync( + private val db: PowerSyncDatabase, + private val connector: SupabaseConnector, + private val supabase: SupabaseClient +) : UserRepository { suspend fun syncDatabase() = connector.uploadData(db) @@ -29,18 +33,18 @@ class UserModelPowerSync(private val db: PowerSyncDatabase, private val connecto supabase.auth.currentUserOrNull()?.id ?: throw Exception("Supabase does not have a user logged in") - val user: UserDto? = - db.writeTransaction { tx -> - tx.getOptional( - "SELECT name, imageUrl, description, dob FROM $USERS WHERE user_id = ?", - listOf(currUser)) { - UserDto( - name = it.getString(0)!!, - imageUrl = it.getString(1)!!, - description = it.getString(2)!!, - dob = it.getString(3)!!) - } - } + val user: UserDto? = + db.writeTransaction { tx -> + tx.getOptional( + "SELECT name, imageUrl, description, dob FROM $USERS WHERE user_id = ?", + listOf(currUser)) { + UserDto( + name = it.getString(0)!!, + imageUrl = it.getString(1)!!, + description = it.getString(2)!!, + dob = it.getString(3)!!) + } + } if (user == null) { throw Exception("PowerSync failure did not fetch correctly") } diff --git a/app/src/main/java/com/android/periodpals/resources/Schema.kt b/app/src/main/java/com/android/periodpals/resources/Schema.kt index a5c3c0d3e..43a78ed6f 100644 --- a/app/src/main/java/com/android/periodpals/resources/Schema.kt +++ b/app/src/main/java/com/android/periodpals/resources/Schema.kt @@ -6,24 +6,19 @@ import com.powersync.db.schema.Table const val USERS = "users" -val users = Table( - USERS, - listOf( - Column.text("user_id"), - Column.text("name"), - Column.text("email"), - Column.text("imageUrl"), - Column.text("description"), - Column.text("dob"), - Column.text("id"), - Column.text("fcm_token"), - Column.integer("erred_distance"), - Column.text("locationGIS") - ) -) +val users = + Table( + USERS, + listOf( + Column.text("user_id"), + Column.text("name"), + Column.text("email"), + Column.text("imageUrl"), + Column.text("description"), + Column.text("dob"), + Column.text("id"), + Column.text("fcm_token"), + Column.integer("erred_distance"), + Column.text("locationGIS"))) -val localSchema: Schema = Schema( - listOf( - users - ) -) \ No newline at end of file +val localSchema: Schema = Schema(listOf(users)) From 6f996120cb0a84dd2560393a56e27418dfc6d6fd Mon Sep 17 00:00:00 2001 From: Alonso Date: Thu, 12 Dec 2024 20:49:53 +0100 Subject: [PATCH 20/37] fix --- .../com/android/periodpals/MainActivity.kt | 3 + .../com/android/periodpals/model/user/User.kt | 4 +- .../android/periodpals/model/user/UserDto.kt | 4 +- .../model/user/UserModelPowerSync.kt | 68 ++++++++++++++++--- 4 files changed, 68 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/android/periodpals/MainActivity.kt b/app/src/main/java/com/android/periodpals/MainActivity.kt index cd8638309..e87a75048 100644 --- a/app/src/main/java/com/android/periodpals/MainActivity.kt +++ b/app/src/main/java/com/android/periodpals/MainActivity.kt @@ -21,6 +21,9 @@ import com.android.periodpals.model.authentication.AuthenticationModelSupabase import com.android.periodpals.model.authentication.AuthenticationViewModel import com.android.periodpals.model.chat.ChatViewModel import com.android.periodpals.model.location.LocationViewModel +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.UserModelPowerSync import com.android.periodpals.model.user.UserRepositorySupabase import com.android.periodpals.model.user.UserViewModel diff --git a/app/src/main/java/com/android/periodpals/model/user/User.kt b/app/src/main/java/com/android/periodpals/model/user/User.kt index f956b6818..52908b807 100644 --- a/app/src/main/java/com/android/periodpals/model/user/User.kt +++ b/app/src/main/java/com/android/periodpals/model/user/User.kt @@ -3,6 +3,8 @@ package com.android.periodpals.model.user import com.android.periodpals.model.location.Location import com.android.periodpals.model.location.LocationGIS import com.android.periodpals.model.location.parseLocationGIS +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json /** * Data class representing a user. @@ -38,5 +40,5 @@ data class User( ) } - inline fun asList(): List = listOf(name, imageUrl, description, dob) + inline fun asList(): List = listOf(name, imageUrl, description, dob, fcmToken, Json.encodeToString(locationGIS)) } diff --git a/app/src/main/java/com/android/periodpals/model/user/UserDto.kt b/app/src/main/java/com/android/periodpals/model/user/UserDto.kt index 5e4d3824f..e3aa9aa66 100644 --- a/app/src/main/java/com/android/periodpals/model/user/UserDto.kt +++ b/app/src/main/java/com/android/periodpals/model/user/UserDto.kt @@ -2,6 +2,8 @@ package com.android.periodpals.model.user import com.android.periodpals.model.location.LocationGIS import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json /** * Data Transfer Object (DTO) for user data. @@ -38,5 +40,5 @@ data class UserDto( ) } - inline fun asList(): List = listOf(name, imageUrl, description, dob) + inline fun asList(): List = listOf(name, imageUrl, description, dob, fcm_token, Json.encodeToString(locationGIS)) } diff --git a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt index 9294e35e4..c766b1de4 100644 --- a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt +++ b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt @@ -1,10 +1,16 @@ package com.android.periodpals.model.user import android.util.Log +import com.android.periodpals.model.location.LocationGIS 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 +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json /** * Implementation of UserRepository using PowerSync with Supabase. @@ -36,13 +42,16 @@ class UserModelPowerSync( val user: UserDto? = db.writeTransaction { tx -> tx.getOptional( - "SELECT name, imageUrl, description, dob FROM $USERS WHERE user_id = ?", + "SELECT name, imageUrl, description, dob, fcm_token, locationGIS FROM $USERS WHERE user_id = ?", listOf(currUser)) { UserDto( name = it.getString(0)!!, imageUrl = it.getString(1)!!, description = it.getString(2)!!, - dob = it.getString(3)!!) + dob = it.getString(3)!!, + fcm_token = it.getString(4)!!, + locationGIS = Json.decodeFromString(it.getString(5)!!) + ) } } if (user == null) { @@ -65,7 +74,7 @@ class UserModelPowerSync( try { db.writeTransaction { tx -> tx.execute( - "INSERT INTO $USERS (name, imageUrl, description, dob) VALUES (?, ?, ?, ?);", + "INSERT INTO $USERS (name, imageUrl, description, dob, fcm_token, locationGIS) VALUES (?, ?, ?, ?, ?, ?);", user.asList()) } Log.d(TAG, "createUserProfile: Success") @@ -88,10 +97,10 @@ class UserModelPowerSync( db.writeTransaction { tx -> tx.execute( """ - INSERT INTO $USERS (user_id, name, imageUrl, description, dob) + INSERT INTO $USERS (name, imageUrl, description, dob, fcm_token, locationGIS) VALUES (?, ?, ?, ?, ?) ON CONFLICT (user_id) - DO UPDATE SET name = ?, imageUrl = ?, description = ?, dob = ?; + DO UPDATE SET name = ?, imageUrl = ?, description = ?, dob = ?, fcm_token = ?, locationGIS = ?; """, listOf( currUser, @@ -99,14 +108,20 @@ class UserModelPowerSync( userDto.imageUrl, userDto.description, userDto.dob, + userDto.fcm_token, + Json.encodeToString(userDto.locationGIS), userDto.name, userDto.imageUrl, userDto.description, - userDto.dob)) + userDto.dob, + userDto.fcm_token, + Json.encodeToString(userDto.locationGIS) + ) + ) } Log.d(TAG, "upsertUserProfile: Success") - onSuccess(userDto) connector.uploadData(db) + onSuccess(userDto) } catch (e: Exception) { Log.d(TAG, "upsertUserProfile: fail to create user profile: ${e.message}") onFailure(e) @@ -127,11 +142,46 @@ class UserModelPowerSync( tx.execute("DELETE FROM $USERS WHERE user_id = ?", listOf(currUser)) } Log.d(TAG, "deleteUserProfile: Success") - onSuccess() connector.uploadData(db) + 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) + } + } +} \ No newline at end of file From 86720f4d021a6bc8837616abf58b39bca9304061 Mon Sep 17 00:00:00 2001 From: Alonso Date: Thu, 12 Dec 2024 20:59:41 +0100 Subject: [PATCH 21/37] style: ktfmt --- .../com/android/periodpals/model/user/User.kt | 3 +- .../android/periodpals/model/user/UserDto.kt | 3 +- .../model/user/UserModelPowerSync.kt | 71 +++++++++---------- 3 files changed, 38 insertions(+), 39 deletions(-) diff --git a/app/src/main/java/com/android/periodpals/model/user/User.kt b/app/src/main/java/com/android/periodpals/model/user/User.kt index 52908b807..83b048b18 100644 --- a/app/src/main/java/com/android/periodpals/model/user/User.kt +++ b/app/src/main/java/com/android/periodpals/model/user/User.kt @@ -40,5 +40,6 @@ data class User( ) } - inline fun asList(): List = listOf(name, imageUrl, description, dob, fcmToken, Json.encodeToString(locationGIS)) + inline fun asList(): List = + listOf(name, imageUrl, description, dob, fcmToken, Json.encodeToString(locationGIS)) } diff --git a/app/src/main/java/com/android/periodpals/model/user/UserDto.kt b/app/src/main/java/com/android/periodpals/model/user/UserDto.kt index e3aa9aa66..04f19645f 100644 --- a/app/src/main/java/com/android/periodpals/model/user/UserDto.kt +++ b/app/src/main/java/com/android/periodpals/model/user/UserDto.kt @@ -40,5 +40,6 @@ data class UserDto( ) } - inline fun asList(): List = listOf(name, imageUrl, description, dob, fcm_token, Json.encodeToString(locationGIS)) + inline fun asList(): List = + listOf(name, imageUrl, description, dob, fcm_token, Json.encodeToString(locationGIS)) } diff --git a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt index c766b1de4..fdf0d4f8e 100644 --- a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt +++ b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt @@ -50,8 +50,7 @@ class UserModelPowerSync( description = it.getString(2)!!, dob = it.getString(3)!!, fcm_token = it.getString(4)!!, - locationGIS = Json.decodeFromString(it.getString(5)!!) - ) + locationGIS = Json.decodeFromString(it.getString(5)!!)) } } if (user == null) { @@ -115,9 +114,7 @@ class UserModelPowerSync( userDto.description, userDto.dob, userDto.fcm_token, - Json.encodeToString(userDto.locationGIS) - ) - ) + Json.encodeToString(userDto.locationGIS))) } Log.d(TAG, "upsertUserProfile: Success") connector.uploadData(db) @@ -150,38 +147,38 @@ class UserModelPowerSync( } } - 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 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) - } + 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) } -} \ No newline at end of file + } +} From ab3c4e878af4032c31b547efae4696f71340da2a Mon Sep 17 00:00:00 2001 From: Alonso Date: Thu, 12 Dec 2024 22:55:13 +0100 Subject: [PATCH 22/37] fix --- app/src/main/java/com/android/periodpals/resources/Schema.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/java/com/android/periodpals/resources/Schema.kt b/app/src/main/java/com/android/periodpals/resources/Schema.kt index 43a78ed6f..cc5dabb77 100644 --- a/app/src/main/java/com/android/periodpals/resources/Schema.kt +++ b/app/src/main/java/com/android/periodpals/resources/Schema.kt @@ -16,9 +16,7 @@ val users = Column.text("imageUrl"), Column.text("description"), Column.text("dob"), - Column.text("id"), Column.text("fcm_token"), - Column.integer("erred_distance"), Column.text("locationGIS"))) val localSchema: Schema = Schema(listOf(users)) From 43cbd6c5b86f2460d7faa1160bfb55e05d16c7a4 Mon Sep 17 00:00:00 2001 From: Alonso Date: Mon, 16 Dec 2024 11:11:54 +0100 Subject: [PATCH 23/37] refactor: `MainActivity` move init of usermodel to main --- .../com/android/periodpals/MainActivity.kt | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/android/periodpals/MainActivity.kt b/app/src/main/java/com/android/periodpals/MainActivity.kt index e87a75048..f7dd715a5 100644 --- a/app/src/main/java/com/android/periodpals/MainActivity.kt +++ b/app/src/main/java/com/android/periodpals/MainActivity.kt @@ -9,6 +9,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.compose.NavHost @@ -48,6 +49,7 @@ 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.DatabaseDriverFactory import com.powersync.PowerSyncDatabase import com.powersync.compose.rememberDatabaseDriverFactory import com.powersync.connector.supabase.SupabaseConnector @@ -79,11 +81,13 @@ class MainActivity : ComponentActivity() { private val supabaseConnector = SupabaseConnector( powerSyncEndpoint = BuildConfig.POWERSYNC_URL, supabaseClient = supabaseClient) + private val driverFactory = DatabaseDriverFactory(this) + private val db:PowerSyncDatabase = PowerSyncDatabase(driverFactory, schema = localSchema) private val authModel = AuthenticationModelSupabase(supabaseClient) private val authenticationViewModel = AuthenticationViewModel(authModel) - private val userModel = UserRepositorySupabase(supabaseClient) + private val userModel = UserModelPowerSync(db, supabaseConnector, supabaseClient) private val userViewModel = UserViewModel(userModel) private val alertModel = AlertModelSupabase(supabaseClient) @@ -94,6 +98,7 @@ class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + gpsService = GPSServiceImpl(this, userViewModel) pushNotificationsService = PushNotificationsServiceImpl(this, userViewModel) @@ -116,9 +121,7 @@ class MainActivity : ComponentActivity() { gpsService, pushNotificationsService, authenticationViewModel, - supabaseConnector, - supabaseClient, - // userViewModel, + userViewModel, alertViewModel, timerViewModel, chatViewModel, @@ -149,17 +152,11 @@ fun PeriodPalsApp( gpsService: GPSServiceImpl, pushNotificationsService: PushNotificationsService, authenticationViewModel: AuthenticationViewModel, - connector: SupabaseConnector, - supabase: SupabaseClient, - // userViewModel: UserViewModel, + userViewModel: UserViewModel, alertViewModel: AlertViewModel, timerViewModel: TimerViewModel, chatViewModel: ChatViewModel, ) { - val driverFactory = rememberDatabaseDriverFactory() - val db = remember { PowerSyncDatabase(driverFactory, schema = localSchema) } - val userModel = UserModelPowerSync(db, connector, supabase) - val userViewModel = UserViewModel(userModel) val navController = rememberNavController() val navigationActions = NavigationActions(navController) From 29980cb112aeb723de577da4fb5597df8e24dbe1 Mon Sep 17 00:00:00 2001 From: Alonso Date: Mon, 16 Dec 2024 11:12:32 +0100 Subject: [PATCH 24/37] refactor: add a connect to loadUserProfile --- .../com/android/periodpals/model/user/UserModelPowerSync.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt index fdf0d4f8e..9de2e640a 100644 --- a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt +++ b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt @@ -35,6 +35,7 @@ class UserModelPowerSync( onFailure: (Exception) -> Unit ) { try { + db.connect(connector) // find a better place to place this val currUser = supabase.auth.currentUserOrNull()?.id ?: throw Exception("Supabase does not have a user logged in") @@ -49,7 +50,7 @@ class UserModelPowerSync( imageUrl = it.getString(1)!!, description = it.getString(2)!!, dob = it.getString(3)!!, - fcm_token = it.getString(4)!!, + fcm_token = it.getString(4), locationGIS = Json.decodeFromString(it.getString(5)!!)) } } From 317192fb43b2f991b0ca298cd094ab5cb4846537 Mon Sep 17 00:00:00 2001 From: Alonso Date: Wed, 18 Dec 2024 15:25:57 +0100 Subject: [PATCH 25/37] feat: add syncSupabase to UserModelPowerSync.kt --- .../model/user/UserModelPowerSync.kt | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt index 9de2e640a..20a38a2e6 100644 --- a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt +++ b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt @@ -28,35 +28,36 @@ class UserModelPowerSync( private val supabase: SupabaseClient ) : UserRepository { - suspend fun syncDatabase() = connector.uploadData(db) + suspend fun syncSupabase() { + try { + connector.uploadData(db) + Log.d(TAG, "syncSupabase: Success") + } catch (e: Exception) { + Log.d(TAG, "syncSupabase: Failure ${e.message}") + } + } override suspend fun loadUserProfile( onSuccess: (UserDto) -> Unit, onFailure: (Exception) -> Unit ) { try { - db.connect(connector) // find a better place to place this val currUser = supabase.auth.currentUserOrNull()?.id ?: throw Exception("Supabase does not have a user logged in") - val user: UserDto? = - db.writeTransaction { tx -> - tx.getOptional( - "SELECT name, imageUrl, description, dob, fcm_token, locationGIS FROM $USERS WHERE user_id = ?", - listOf(currUser)) { - UserDto( - name = it.getString(0)!!, - imageUrl = it.getString(1)!!, - description = it.getString(2)!!, - dob = it.getString(3)!!, - fcm_token = it.getString(4), - locationGIS = Json.decodeFromString(it.getString(5)!!)) - } - } - if (user == null) { - throw Exception("PowerSync failure did not fetch correctly") - } + val user: UserDto = + db.get( + "SELECT name, imageUrl, description, dob, fcm_token, locationGIS FROM $USERS WHERE user_id = ?", + listOf(currUser)) { + UserDto( + name = it.getString(0)!!, + imageUrl = it.getString(1)!!, + description = it.getString(2)!!, + dob = it.getString(3)!!, + fcm_token = it.getString(4), + locationGIS = Json.decodeFromString(it.getString(5)!!)) + } Log.d(TAG, "loadUserProfile: Success") onSuccess(user) From 7294cf10308bc00e7bfd48094b4ea83f01d3f228 Mon Sep 17 00:00:00 2001 From: Alonso Date: Wed, 18 Dec 2024 15:26:53 +0100 Subject: [PATCH 26/37] refactor: move all view model initiation to composable function to facilitate powersync db init --- .../com/android/periodpals/MainActivity.kt | 116 +++++++++--------- 1 file changed, 59 insertions(+), 57 deletions(-) diff --git a/app/src/main/java/com/android/periodpals/MainActivity.kt b/app/src/main/java/com/android/periodpals/MainActivity.kt index f7dd715a5..5b1a28ee1 100644 --- a/app/src/main/java/com/android/periodpals/MainActivity.kt +++ b/app/src/main/java/com/android/periodpals/MainActivity.kt @@ -2,6 +2,7 @@ package com.android.periodpals import android.content.Context import android.os.Bundle +import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.fillMaxSize @@ -9,7 +10,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.compose.NavHost @@ -26,6 +26,7 @@ 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.UserModelPowerSync +import com.android.periodpals.model.user.UserRepository import com.android.periodpals.model.user.UserRepositorySupabase import com.android.periodpals.model.user.UserViewModel import com.android.periodpals.resources.localSchema @@ -53,57 +54,28 @@ import com.powersync.DatabaseDriverFactory import com.powersync.PowerSyncDatabase import com.powersync.compose.rememberDatabaseDriverFactory import com.powersync.connector.supabase.SupabaseConnector -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 import io.github.jan.supabase.storage.Storage +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext import org.osmdroid.config.Configuration private const val TAG = "MainActivity" class MainActivity : ComponentActivity() { - private lateinit var gpsService: GPSServiceImpl - private lateinit var pushNotificationsService: PushNotificationsServiceImpl - private lateinit var chatViewModel: ChatViewModel - private lateinit var timerManager: TimerManager + lateinit var gpsService: GPSServiceImpl - private val supabaseClient = - createSupabaseClient( - supabaseUrl = BuildConfig.SUPABASE_URL, - supabaseKey = BuildConfig.SUPABASE_KEY, - ) { - install(Auth) - install(Postgrest) - install(Storage) - } - private val supabaseConnector = - SupabaseConnector( - powerSyncEndpoint = BuildConfig.POWERSYNC_URL, supabaseClient = supabaseClient) - private val driverFactory = DatabaseDriverFactory(this) - private val db:PowerSyncDatabase = PowerSyncDatabase(driverFactory, schema = localSchema) - - private val authModel = AuthenticationModelSupabase(supabaseClient) - private val authenticationViewModel = AuthenticationViewModel(authModel) - - private val userModel = UserModelPowerSync(db, supabaseConnector, supabaseClient) - private val userViewModel = UserViewModel(userModel) - - private val alertModel = AlertModelSupabase(supabaseClient) - private val alertViewModel = AlertViewModel(alertModel) - - private val timerModel = TimerRepositorySupabase(supabaseClient) - private lateinit var timerViewModel: TimerViewModel + fun setGPSService(service: GPSServiceImpl) { + this.gpsService = service + } - override fun onCreate(savedInstanceState: Bundle?) { + override fun onCreate(savedInstanceState: Bundle + ?) { super.onCreate(savedInstanceState) - - - gpsService = GPSServiceImpl(this, userViewModel) - pushNotificationsService = PushNotificationsServiceImpl(this, userViewModel) - timerManager = TimerManager(this) - timerViewModel = TimerViewModel(timerModel, timerManager) // Initialize osmdroid configuration getSharedPreferences(this) Configuration.getInstance().load(this, getSharedPreferences("osmdroid", Context.MODE_PRIVATE)) @@ -111,21 +83,11 @@ class MainActivity : ComponentActivity() { // Check if Google Play Services are available GoogleApiAvailability.getInstance().makeGooglePlayServicesAvailable(this) - chatViewModel = ChatViewModel() - setContent { PeriodPalsAppTheme { // A surface container using the 'background' color from the theme Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) { - PeriodPalsApp( - gpsService, - pushNotificationsService, - authenticationViewModel, - userViewModel, - alertViewModel, - timerViewModel, - chatViewModel, - ) + PeriodPalsApp(this) } } } @@ -149,20 +111,60 @@ class MainActivity : ComponentActivity() { @Composable fun PeriodPalsApp( - gpsService: GPSServiceImpl, - pushNotificationsService: PushNotificationsService, - authenticationViewModel: AuthenticationViewModel, - userViewModel: UserViewModel, - alertViewModel: AlertViewModel, - timerViewModel: TimerViewModel, - chatViewModel: ChatViewModel, + main: MainActivity ) { + // Supabase Client init with the necessary extentions installed + val supabaseClient = remember { + createSupabaseClient( + supabaseUrl = BuildConfig.SUPABASE_URL, + supabaseKey = BuildConfig.SUPABASE_KEY, + ) { + install(Auth) + install(Postgrest) + install(Storage) + } + } + + // PowerSync x Supabase Local-first db + val supabaseConnector = remember { + SupabaseConnector( + powerSyncEndpoint = BuildConfig.POWERSYNC_URL, supabaseClient = supabaseClient) + } + val dbDriver = rememberDatabaseDriverFactory() + val db = remember { + PowerSyncDatabase(dbDriver, schema = localSchema) + } + + // View Models + val authModel = remember { AuthenticationModelSupabase(supabaseClient)} + val authenticationViewModel = remember { AuthenticationViewModel(authModel) } + + val userModel = remember { UserModelPowerSync(db, supabaseConnector, supabaseClient) } + val userViewModel = remember { UserViewModel(userModel) } + + val alertModel = remember { AlertModelSupabase(supabaseClient) } + val alertViewModel = remember { AlertViewModel(alertModel) } + + val timerModel = remember { TimerRepositorySupabase(supabaseClient) } + val timerManager = remember { TimerManager(main) } + val timerViewModel = remember { TimerViewModel(timerModel, timerManager) } + + val pushNotificationsService = remember { PushNotificationsServiceImpl(main, userViewModel) } + + val gpsService = remember { GPSServiceImpl(main, userViewModel) } + + val chatViewModel = remember { ChatViewModel() } val navController = rememberNavController() val navigationActions = NavigationActions(navController) val locationViewModel: LocationViewModel = viewModel(factory = LocationViewModel.Factory) + //Clean up + main.setGPSService(gpsService) + runBlocking { withContext(Dispatchers.IO) { db.connect(supabaseConnector) } } + + NavHost(navController = navController, startDestination = Route.AUTH) { // Authentication navigation(startDestination = Screen.SIGN_IN, route = Route.AUTH) { From 6b14b8dba5c73e450bce5a03f65ef20a2388cb17 Mon Sep 17 00:00:00 2001 From: Alonso Date: Wed, 18 Dec 2024 17:35:31 +0100 Subject: [PATCH 27/37] feat: add setter for the `PushNotificationsServiceImpl` --- .../services/PushNotificationsServiceImpl.kt | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/android/periodpals/services/PushNotificationsServiceImpl.kt b/app/src/main/java/com/android/periodpals/services/PushNotificationsServiceImpl.kt index d5db36379..577bf3e3a 100644 --- a/app/src/main/java/com/android/periodpals/services/PushNotificationsServiceImpl.kt +++ b/app/src/main/java/com/android/periodpals/services/PushNotificationsServiceImpl.kt @@ -36,10 +36,11 @@ private const val TIMEOUT = 1000L * @property activity The activity context used for requesting permissions. */ class PushNotificationsServiceImpl( - private val activity: ComponentActivity, - private val userViewModel: UserViewModel?, + private val activity: ComponentActivity ) : FirebaseMessagingService(), PushNotificationsService { + private var setter = 0 + private lateinit var userViewModel: UserViewModel private var firebase: FirebaseMessaging private var _pushPermissionsGranted = MutableStateFlow(false) @@ -50,15 +51,23 @@ class PushNotificationsServiceImpl( handlePermissionResult(it) } - constructor() : this(ComponentActivity(), null) { - Log.e(TAG, "went through empty constructor") - } - init { // to be executed right after primary constructor FirebaseApp.initializeApp(activity) this.firebase = FirebaseMessaging.getInstance() } + /** + * Setter for the `UserViewModel` in `PushNotificationServiceImpl` + * + * @param userViewModel New user + */ + fun setUserViewModel(userViewModel: UserViewModel) { + if (setter == 0) { + this.userViewModel = userViewModel + setter ++ + } + } + /** * Called when a new token for the default Firebase project is generated. This is invoked after * app install when a token is first generated, and again if the token changes. From 7183cac0e8486948e1240d1b51c48218e26d5d05 Mon Sep 17 00:00:00 2001 From: Alonso Date: Wed, 18 Dec 2024 17:36:43 +0100 Subject: [PATCH 28/37] refactor: move `PushNotificiationServiceImpl` out of the composable --- app/src/main/java/com/android/periodpals/MainActivity.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/android/periodpals/MainActivity.kt b/app/src/main/java/com/android/periodpals/MainActivity.kt index 5b1a28ee1..cc1984a43 100644 --- a/app/src/main/java/com/android/periodpals/MainActivity.kt +++ b/app/src/main/java/com/android/periodpals/MainActivity.kt @@ -68,6 +68,7 @@ private const val TAG = "MainActivity" class MainActivity : ComponentActivity() { lateinit var gpsService: GPSServiceImpl + private val pushNotificationsService = PushNotificationsServiceImpl(this) fun setGPSService(service: GPSServiceImpl) { this.gpsService = service @@ -87,7 +88,7 @@ class MainActivity : ComponentActivity() { PeriodPalsAppTheme { // A surface container using the 'background' color from the theme Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) { - PeriodPalsApp(this) + PeriodPalsApp(this, pushNotificationsService) } } } @@ -111,7 +112,8 @@ class MainActivity : ComponentActivity() { @Composable fun PeriodPalsApp( - main: MainActivity + main: MainActivity, + pushNotificationsService: PushNotificationsServiceImpl ) { // Supabase Client init with the necessary extentions installed val supabaseClient = remember { @@ -149,7 +151,7 @@ fun PeriodPalsApp( val timerManager = remember { TimerManager(main) } val timerViewModel = remember { TimerViewModel(timerModel, timerManager) } - val pushNotificationsService = remember { PushNotificationsServiceImpl(main, userViewModel) } + pushNotificationsService.setUserViewModel(userViewModel) val gpsService = remember { GPSServiceImpl(main, userViewModel) } From 3cc983de5ee2e63eb7c54fc1443e52e6903e6282 Mon Sep 17 00:00:00 2001 From: Alonso Date: Thu, 19 Dec 2024 13:41:21 +0100 Subject: [PATCH 29/37] feat: implement `sync()` in `UserModelPowerSync` --- .../model/user/UserModelPowerSync.kt | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt index 20a38a2e6..59681f293 100644 --- a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt +++ b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt @@ -28,12 +28,12 @@ class UserModelPowerSync( private val supabase: SupabaseClient ) : UserRepository { - suspend fun syncSupabase() { + private suspend fun sync() { try { connector.uploadData(db) - Log.d(TAG, "syncSupabase: Success") + Log.d(TAG, "sync: Success") } catch (e: Exception) { - Log.d(TAG, "syncSupabase: Failure ${e.message}") + Log.d(TAG, "sync: Failure ${e.message}") } } @@ -42,20 +42,22 @@ class UserModelPowerSync( onFailure: (Exception) -> Unit ) { try { + sync() val currUser = supabase.auth.currentUserOrNull()?.id ?: throw Exception("Supabase does not have a user logged in") val user: UserDto = db.get( - "SELECT name, imageUrl, description, dob, fcm_token, locationGIS FROM $USERS WHERE user_id = ?", + "SELECT name, imageUrl, description, dob, preferred_distance, fcm_token, locationGIS FROM $USERS WHERE user_id = ?", listOf(currUser)) { UserDto( name = it.getString(0)!!, imageUrl = it.getString(1)!!, description = it.getString(2)!!, dob = it.getString(3)!!, - fcm_token = it.getString(4), + preferred_distance = it.getLong(4)!!.toInt(), + fcm_token = it.getString(5), locationGIS = Json.decodeFromString(it.getString(5)!!)) } @@ -80,7 +82,7 @@ class UserModelPowerSync( } Log.d(TAG, "createUserProfile: Success") onSuccess() - connector.uploadData(db) + sync() } catch (e: Exception) { Log.d(TAG, "createUserProfile: fail to create user profile: ${e.message}") onFailure(e) @@ -119,7 +121,7 @@ class UserModelPowerSync( Json.encodeToString(userDto.locationGIS))) } Log.d(TAG, "upsertUserProfile: Success") - connector.uploadData(db) + sync() onSuccess(userDto) } catch (e: Exception) { Log.d(TAG, "upsertUserProfile: fail to create user profile: ${e.message}") @@ -141,7 +143,7 @@ class UserModelPowerSync( tx.execute("DELETE FROM $USERS WHERE user_id = ?", listOf(currUser)) } Log.d(TAG, "deleteUserProfile: Success") - connector.uploadData(db) + sync() onSuccess() } catch (e: Exception) { Log.d(TAG, "deleteUserProfile: fail to delete user profile: ${e.message}") From 9405afadef360a9fb0316bbc4174d93cf0f8d538 Mon Sep 17 00:00:00 2001 From: Alonso Date: Thu, 19 Dec 2024 20:37:59 +0100 Subject: [PATCH 30/37] refactor: undo unneeded changes --- .../com/android/periodpals/MainActivity.kt | 178 ++++++------------ 1 file changed, 62 insertions(+), 116 deletions(-) diff --git a/app/src/main/java/com/android/periodpals/MainActivity.kt b/app/src/main/java/com/android/periodpals/MainActivity.kt index 71537c048..d39734e27 100644 --- a/app/src/main/java/com/android/periodpals/MainActivity.kt +++ b/app/src/main/java/com/android/periodpals/MainActivity.kt @@ -11,7 +11,6 @@ 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 @@ -29,11 +28,8 @@ 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.UserRepository 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 @@ -61,17 +57,10 @@ 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 com.powersync.DatabaseDriverFactory -import com.powersync.PowerSyncDatabase -import com.powersync.compose.rememberDatabaseDriverFactory -import com.powersync.connector.supabase.SupabaseConnector import io.github.jan.supabase.auth.Auth import io.github.jan.supabase.createSupabaseClient import io.github.jan.supabase.postgrest.Postgrest import io.github.jan.supabase.storage.Storage -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withContext import org.osmdroid.config.Configuration private const val TAG = "MainActivity" @@ -83,9 +72,15 @@ class MainActivity : ComponentActivity() { private lateinit var chatViewModel: ChatViewModel private lateinit var timerManager: TimerManager - fun setGPSService(service: GPSServiceImpl) { - this.gpsService = service - } + private val supabaseClient = + createSupabaseClient( + supabaseUrl = BuildConfig.SUPABASE_URL, + supabaseKey = BuildConfig.SUPABASE_KEY, + ) { + install(Auth) + install(Postgrest) + install(Storage) + } private val authModel = AuthenticationModelSupabase(supabaseClient) private val authenticationViewModel = AuthenticationViewModel(authModel) @@ -101,13 +96,11 @@ class MainActivity : ComponentActivity() { private lateinit var timerViewModel: TimerViewModel override fun onCreate(savedInstanceState: Bundle?) { - override fun onCreate(savedInstanceState: Bundle - ?) { super.onCreate(savedInstanceState) gpsService = GPSServiceImpl(this, authenticationViewModel, userViewModel) pushNotificationsService = - PushNotificationsServiceImpl(this, authenticationViewModel, userViewModel) + PushNotificationsServiceImpl(this, authenticationViewModel, userViewModel) timerManager = TimerManager(this) timerViewModel = TimerViewModel(timerModel, timerManager) @@ -119,18 +112,18 @@ class MainActivity : ComponentActivity() { // Set up the OfflinePlugin for offline storage val offlinePluginFactory = - StreamOfflinePluginFactory( - appContext = applicationContext, - ) + StreamOfflinePluginFactory( + appContext = applicationContext, + ) val statePluginFactory = - StreamStatePluginFactory(config = StatePluginConfig(), appContext = this) + StreamStatePluginFactory(config = StatePluginConfig(), appContext = this) // Set up the chat client for API calls and with the plugin for offline storage val chatClient = - ChatClient.Builder(BuildConfig.STREAM_SDK_KEY, applicationContext) - .withPlugins(offlinePluginFactory, statePluginFactory) - .logLevel(ChatLogLevel.ALL) // Set to NOTHING in prod - .build() + ChatClient.Builder(BuildConfig.STREAM_SDK_KEY, applicationContext) + .withPlugins(offlinePluginFactory, statePluginFactory) + .logLevel(ChatLogLevel.ALL) // Set to NOTHING in prod + .build() chatViewModel = ChatViewModel(chatClient) @@ -139,16 +132,15 @@ class MainActivity : ComponentActivity() { // A surface container using the 'background' color from the theme Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) { PeriodPalsApp( - gpsService, - pushNotificationsService, - authenticationViewModel, - userViewModel, - alertViewModel, - timerViewModel, - chatClient, - chatViewModel, + gpsService, + pushNotificationsService, + authenticationViewModel, + userViewModel, + alertViewModel, + timerViewModel, + chatClient, + chatViewModel, ) - PeriodPalsApp(this, pushNotificationsService) } } } @@ -180,8 +172,8 @@ class MainActivity : ComponentActivity() { * @param navigationActions The actions used to navigate between screens. */ fun userAuthStateLogic( - authenticationViewModel: AuthenticationViewModel, - navigationActions: NavigationActions + authenticationViewModel: AuthenticationViewModel, + navigationActions: NavigationActions ) { when (authenticationViewModel.userAuthenticationState.value) { is UserAuthenticationState.SuccessIsLoggedIn -> navigationActions.navigateTo(Screen.PROFILE) @@ -191,59 +183,15 @@ fun userAuthStateLogic( @Composable fun PeriodPalsApp( - main: MainActivity, - pushNotificationsService: PushNotificationsServiceImpl - gpsService: GPSServiceImpl, - pushNotificationsService: PushNotificationsService, - authenticationViewModel: AuthenticationViewModel, - userViewModel: UserViewModel, - alertViewModel: AlertViewModel, - timerViewModel: TimerViewModel, - chatClient: ChatClient, - chatViewModel: ChatViewModel + gpsService: GPSServiceImpl, + pushNotificationsService: PushNotificationsService, + authenticationViewModel: AuthenticationViewModel, + userViewModel: UserViewModel, + alertViewModel: AlertViewModel, + timerViewModel: TimerViewModel, + chatClient: ChatClient, + chatViewModel: ChatViewModel ) { - // Supabase Client init with the necessary extentions installed - val supabaseClient = remember { - createSupabaseClient( - supabaseUrl = BuildConfig.SUPABASE_URL, - supabaseKey = BuildConfig.SUPABASE_KEY, - ) { - install(Auth) - install(Postgrest) - install(Storage) - } - } - - // PowerSync x Supabase Local-first db - val supabaseConnector = remember { - SupabaseConnector( - powerSyncEndpoint = BuildConfig.POWERSYNC_URL, supabaseClient = supabaseClient) - } - val dbDriver = rememberDatabaseDriverFactory() - val db = remember { - PowerSyncDatabase(dbDriver, schema = localSchema) - } - - // View Models - val authModel = remember { AuthenticationModelSupabase(supabaseClient)} - val authenticationViewModel = remember { AuthenticationViewModel(authModel) } - - val userModel = remember { UserModelPowerSync(db, supabaseConnector, supabaseClient) } - val userViewModel = remember { UserViewModel(userModel) } - - val alertModel = remember { AlertModelSupabase(supabaseClient) } - val alertViewModel = remember { AlertViewModel(alertModel) } - - val timerModel = remember { TimerRepositorySupabase(supabaseClient) } - val timerManager = remember { TimerManager(main) } - val timerViewModel = remember { TimerViewModel(timerModel, timerManager) } - - pushNotificationsService.setUserViewModel(userViewModel) - - val gpsService = remember { GPSServiceImpl(main, userViewModel) } - - val chatViewModel = remember { ChatViewModel() } - val navController = rememberNavController() val navigationActions = NavigationActions(navController) @@ -251,11 +199,6 @@ fun PeriodPalsApp( userAuthStateLogic(authenticationViewModel, navigationActions) - //Clean up - main.setGPSService(gpsService) - runBlocking { withContext(Dispatchers.IO) { db.connect(supabaseConnector) } } - - NavHost(navController = navController, startDestination = Route.AUTH) { // Authentication navigation(startDestination = Screen.SIGN_IN, route = Route.AUTH) { @@ -268,12 +211,12 @@ fun PeriodPalsApp( navigation(startDestination = Screen.ALERT, route = Route.ALERT) { composable(Screen.ALERT) { CreateAlertScreen( - locationViewModel, - gpsService, - alertViewModel, - authenticationViewModel, - userViewModel, - navigationActions, + locationViewModel, + gpsService, + alertViewModel, + authenticationViewModel, + userViewModel, + navigationActions, ) } } @@ -282,11 +225,12 @@ fun PeriodPalsApp( navigation(startDestination = Screen.ALERT_LIST, route = Route.ALERT_LIST) { composable(Screen.ALERT_LIST) { AlertListsScreen( - alertViewModel, - authenticationViewModel, - locationViewModel, - gpsService, - navigationActions) + alertViewModel, + authenticationViewModel, + locationViewModel, + gpsService, + navigationActions + ) } composable(Screen.EDIT_ALERT) { EditAlertScreen(locationViewModel, gpsService, alertViewModel, navigationActions) @@ -305,17 +249,19 @@ fun PeriodPalsApp( Log.d(TAG, "Client initialization completed") Log.d(TAG, "Client connection state $clientConnectionState") ChannelsScreen( - title = CHANNEL_SCREEN_TITLE, - isShowingHeader = true, - onChannelClick = { - /** TODO: implement channels here */ - }, - onBackPressed = { navigationActions.navigateTo(Screen.ALERT_LIST) }, + title = CHANNEL_SCREEN_TITLE, + isShowingHeader = true, + onChannelClick = { + /** TODO: implement channels here */ + }, + onBackPressed = { navigationActions.navigateTo(Screen.ALERT_LIST) }, ) } + InitializationState.INITIALIZING -> { Log.d(TAG, "Client initializing") } + InitializationState.NOT_INITIALIZED -> { Log.d(TAG, "Client not initialized yet.") } @@ -342,11 +288,11 @@ fun PeriodPalsApp( navigation(startDestination = Screen.PROFILE, route = Route.PROFILE) { composable(Screen.PROFILE) { ProfileScreen( - userViewModel, - authenticationViewModel, - pushNotificationsService, - chatViewModel, - navigationActions, + userViewModel, + authenticationViewModel, + pushNotificationsService, + chatViewModel, + navigationActions, ) } composable(Screen.EDIT_PROFILE) { EditProfileScreen(userViewModel, navigationActions) } @@ -355,4 +301,4 @@ fun PeriodPalsApp( } } } -} +} \ No newline at end of file From a4ec7062e7bbb8f4cc54261694f836b37ecec989 Mon Sep 17 00:00:00 2001 From: Alonso Date: Fri, 20 Dec 2024 00:47:05 +0100 Subject: [PATCH 31/37] feat: update `asList` for `User` --- .../com/android/periodpals/model/user/User.kt | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/android/periodpals/model/user/User.kt b/app/src/main/java/com/android/periodpals/model/user/User.kt index 1d5923adb..5c27b9ed5 100644 --- a/app/src/main/java/com/android/periodpals/model/user/User.kt +++ b/app/src/main/java/com/android/periodpals/model/user/User.kt @@ -1,9 +1,5 @@ package com.android.periodpals.model.user -import com.android.periodpals.model.location.Location -import com.android.periodpals.model.location.LocationGIS -import com.android.periodpals.model.location.parseLocationGIS - /** * Data class representing a user. * @@ -15,12 +11,12 @@ import com.android.periodpals.model.location.parseLocationGIS * @property fcmToken The Firebase Cloud Messaging token for the user (optional). */ data class User( - val name: String, - val imageUrl: String, - val description: String, - val dob: String, - val preferredDistance: Int, - val fcmToken: String? = null + val name: String, + val imageUrl: String, + val description: String, + val dob: String, + val preferredDistance: Int, + val fcmToken: String? = null ) { /** * Converts the User object to a UserDto object. @@ -29,15 +25,15 @@ data class User( */ fun asUserDto(): UserDto { return UserDto( - name = this.name, - imageUrl = this.imageUrl, - description = this.description, - dob = this.dob, - preferred_distance = this.preferredDistance, - fcm_token = this.fcmToken + name = this.name, + imageUrl = this.imageUrl, + description = this.description, + dob = this.dob, + preferred_distance = this.preferredDistance, + fcm_token = this.fcmToken ) } inline fun asList(): List = - listOf(name, imageUrl, description, dob, fcmToken) + listOf(name, imageUrl, description, dob, preferredDistance, fcmToken) } From a726dae0b2cfede10bb9359e15174d81a8521576 Mon Sep 17 00:00:00 2001 From: Alonso Date: Fri, 20 Dec 2024 00:47:14 +0100 Subject: [PATCH 32/37] feat: update `asList` for `UserDto` --- .../android/periodpals/model/user/UserDto.kt | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/android/periodpals/model/user/UserDto.kt b/app/src/main/java/com/android/periodpals/model/user/UserDto.kt index d9aa4ca00..0f3ce379e 100644 --- a/app/src/main/java/com/android/periodpals/model/user/UserDto.kt +++ b/app/src/main/java/com/android/periodpals/model/user/UserDto.kt @@ -1,8 +1,6 @@ package com.android.periodpals.model.user import kotlinx.serialization.Serializable -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json /** * Data Transfer Object (DTO) for user data. @@ -16,12 +14,12 @@ import kotlinx.serialization.json.Json */ @Serializable data class UserDto( - val name: String, - val imageUrl: String, - val description: String, - val dob: String, - val preferred_distance: Int, - val fcm_token: String? = null, + val name: String, + val imageUrl: String, + val description: String, + val dob: String, + val preferred_distance: Int, + val fcm_token: String? = null, ) { /** * Converts this UserDto to a User object. @@ -30,15 +28,15 @@ data class UserDto( */ fun asUser(): User { return User( - name = this.name, - imageUrl = this.imageUrl, - description = this.description, - dob = this.dob, - preferredDistance = this.preferred_distance, - fcmToken = this.fcm_token, + name = this.name, + imageUrl = this.imageUrl, + description = this.description, + dob = this.dob, + preferredDistance = this.preferred_distance, + fcmToken = this.fcm_token, ) } inline fun asList(): List = - listOf(name, imageUrl, description, dob, fcm_token, Json.encodeToString(locationGIS)) + listOf(name, imageUrl, description, dob, preferred_distance, fcm_token) } From c516ae7d35f9610aec4f037fca5391d2bbb647a7 Mon Sep 17 00:00:00 2001 From: Alonso Date: Fri, 20 Dec 2024 00:50:59 +0100 Subject: [PATCH 33/37] feat: update `users` table in `Schema.kt` --- .../android/periodpals/resources/Schema.kt | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/android/periodpals/resources/Schema.kt b/app/src/main/java/com/android/periodpals/resources/Schema.kt index cc5dabb77..5f279613a 100644 --- a/app/src/main/java/com/android/periodpals/resources/Schema.kt +++ b/app/src/main/java/com/android/periodpals/resources/Schema.kt @@ -7,16 +7,18 @@ import com.powersync.db.schema.Table const val USERS = "users" val users = - Table( - USERS, - listOf( - Column.text("user_id"), - Column.text("name"), - Column.text("email"), - Column.text("imageUrl"), - Column.text("description"), - Column.text("dob"), - Column.text("fcm_token"), - Column.text("locationGIS"))) + Table( + USERS, + listOf( + Column.text("user_id"), + Column.text("name"), + Column.text("email"), + Column.text("imageUrl"), + Column.text("description"), + Column.text("dob"), + Column.integer("preferred_distance"), + Column.text("fcm_token") + ) + ) val localSchema: Schema = Schema(listOf(users)) From e6926eedd76429e61e21256167bfe4edd86f40f8 Mon Sep 17 00:00:00 2001 From: Alonso Date: Fri, 20 Dec 2024 01:49:22 +0100 Subject: [PATCH 34/37] feat: implement a new view model only for with powersync + supabase --- .../com/android/periodpals/MainActivity.kt | 102 +++++++++++------- 1 file changed, 65 insertions(+), 37 deletions(-) diff --git a/app/src/main/java/com/android/periodpals/MainActivity.kt b/app/src/main/java/com/android/periodpals/MainActivity.kt index 144877ba8..82263e36a 100644 --- a/app/src/main/java/com/android/periodpals/MainActivity.kt +++ b/app/src/main/java/com/android/periodpals/MainActivity.kt @@ -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 @@ -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 @@ -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 @@ -59,10 +65,12 @@ 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 import io.github.jan.supabase.storage.Storage +import kotlinx.coroutines.runBlocking import org.osmdroid.config.Configuration private const val TAG = "MainActivity" @@ -88,7 +96,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) @@ -105,7 +113,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) @@ -134,10 +142,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, @@ -174,8 +184,8 @@ class MainActivity : ComponentActivity() { * @param navigationActions The actions used to navigate between screens. */ fun userAuthStateLogic( - authenticationViewModel: AuthenticationViewModel, - navigationActions: NavigationActions, + authenticationViewModel: AuthenticationViewModel, + navigationActions: NavigationActions, ) { when (authenticationViewModel.userAuthenticationState.value) { is UserAuthenticationState.SuccessIsLoggedIn -> navigationActions.navigateTo(Screen.PROFILE) @@ -185,15 +195,25 @@ fun userAuthStateLogic( @Composable fun PeriodPalsApp( - gpsService: GPSServiceImpl, - pushNotificationsService: PushNotificationsService, - authenticationViewModel: AuthenticationViewModel, - userViewModel: UserViewModel, - alertViewModel: AlertViewModel, - timerViewModel: TimerViewModel, - chatClient: ChatClient, - chatViewModel: ChatViewModel, + mainActivity: MainActivity, + supabase: SupabaseClient, + gpsService: GPSServiceImpl, + pushNotificationsService: PushNotificationsService, + authenticationViewModel: AuthenticationViewModel, + userViewModel: UserViewModel, + alertViewModel: AlertViewModel, + timerViewModel: TimerViewModel, + chatClient: ChatClient, + chatViewModel: ChatViewModel, ) { + val dbDriverFactory = rememberDatabaseDriverFactory() + val db = remember { PowerSyncDatabase(dbDriverFactory, schema = localSchema) } + val supabaseConnector = remember { SupabaseConnector(supabase, BuildConfig.POWERSYNC_URL) } + runBlocking { db.connect(supabaseConnector) } + + val userModelPowerSync = remember { UserModelPowerSync(db, supabaseConnector, supabase) } + val userViewModelPowerSync = remember { UserViewModel(userModelPowerSync) } + val navController = rememberNavController() val navigationActions = NavigationActions(navController) @@ -213,12 +233,12 @@ fun PeriodPalsApp( navigation(startDestination = Screen.ALERT, route = Route.ALERT) { composable(Screen.ALERT) { CreateAlertScreen( - locationViewModel, - gpsService, - alertViewModel, - authenticationViewModel, - userViewModel, - navigationActions, + locationViewModel, + gpsService, + alertViewModel, + authenticationViewModel, + userViewModel, + navigationActions, ) } } @@ -227,11 +247,12 @@ fun PeriodPalsApp( navigation(startDestination = Screen.ALERT_LIST, route = Route.ALERT_LIST) { composable(Screen.ALERT_LIST) { AlertListsScreen( - alertViewModel, - authenticationViewModel, - locationViewModel, - gpsService, - navigationActions) + alertViewModel, + authenticationViewModel, + locationViewModel, + gpsService, + navigationActions + ) } composable(Screen.EDIT_ALERT) { EditAlertScreen(locationViewModel, gpsService, alertViewModel, navigationActions) @@ -250,17 +271,19 @@ fun PeriodPalsApp( Log.d(TAG, "Client initialization completed") Log.d(TAG, "Client connection state $clientConnectionState") ChannelsScreen( - title = CHANNEL_SCREEN_TITLE, - isShowingHeader = true, - onChannelClick = { - /** TODO: implement channels here */ - }, - onBackPressed = { navigationActions.navigateTo(Screen.ALERT_LIST) }, + title = CHANNEL_SCREEN_TITLE, + isShowingHeader = true, + onChannelClick = { + /** TODO: implement channels here */ + }, + onBackPressed = { navigationActions.navigateTo(Screen.ALERT_LIST) }, ) } + InitializationState.INITIALIZING -> { Log.d(TAG, "Client initializing") } + InitializationState.NOT_INITIALIZED -> { Log.d(TAG, "Client not initialized yet.") } @@ -287,16 +310,21 @@ fun PeriodPalsApp( navigation(startDestination = Screen.PROFILE, route = Route.PROFILE) { composable(Screen.PROFILE) { ProfileScreen( - userViewModel, - authenticationViewModel, - pushNotificationsService, - chatViewModel, - navigationActions, + userViewModelPowerSync, + authenticationViewModel, + pushNotificationsService, + chatViewModel, + navigationActions, + ) + } + composable(Screen.EDIT_PROFILE) { + EditProfileScreen( + userViewModelPowerSync, + navigationActions ) } - composable(Screen.EDIT_PROFILE) { EditProfileScreen(userViewModel, navigationActions) } composable(Screen.SETTINGS) { - SettingsScreen(userViewModel, authenticationViewModel, navigationActions) + SettingsScreen(userViewModelPowerSync, authenticationViewModel, navigationActions) } } } From ffad65e116479dda59d017aba742b17f359115e0 Mon Sep 17 00:00:00 2001 From: Alonso Date: Fri, 20 Dec 2024 02:07:19 +0100 Subject: [PATCH 35/37] feat: update `UserModelPowerSync` to the current db topography --- .../model/user/UserModelPowerSync.kt | 152 +++++++++++------- 1 file changed, 90 insertions(+), 62 deletions(-) diff --git a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt index 59681f293..a3719517f 100644 --- a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt +++ b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt @@ -1,7 +1,6 @@ package com.android.periodpals.model.user import android.util.Log -import com.android.periodpals.model.location.LocationGIS import com.powersync.PowerSyncDatabase import com.powersync.connector.supabase.SupabaseConnector import io.github.jan.supabase.SupabaseClient @@ -9,8 +8,6 @@ import io.github.jan.supabase.auth.auth import io.github.jan.supabase.storage.storage import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json /** * Implementation of UserRepository using PowerSync with Supabase. @@ -23,9 +20,9 @@ private const val TAG = "UserModelPowerSync" private const val USERS = "users" class UserModelPowerSync( - private val db: PowerSyncDatabase, - private val connector: SupabaseConnector, - private val supabase: SupabaseClient + private val db: PowerSyncDatabase, + private val connector: SupabaseConnector, + private val supabase: SupabaseClient ) : UserRepository { private suspend fun sync() { @@ -38,28 +35,26 @@ class UserModelPowerSync( } override suspend fun loadUserProfile( - onSuccess: (UserDto) -> Unit, - onFailure: (Exception) -> Unit + idUser: String, + onSuccess: (UserDto) -> Unit, + onFailure: (Exception) -> Unit ) { try { sync() - val currUser = - supabase.auth.currentUserOrNull()?.id - ?: throw Exception("Supabase does not have a user logged in") - val user: UserDto = - db.get( - "SELECT name, imageUrl, description, dob, preferred_distance, fcm_token, locationGIS FROM $USERS WHERE user_id = ?", - listOf(currUser)) { - 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), - locationGIS = Json.decodeFromString(it.getString(5)!!)) - } + db.get( + "SELECT name, imageUrl, description, dob, preferred_distance, fcm_token, locationGIS 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) @@ -69,16 +64,47 @@ class UserModelPowerSync( } } + override suspend fun loadUserProfiles( + onSuccess: (List) -> Unit, + onFailure: (Exception) -> Unit + ) { + try { + sync() + val users: List = + 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 + user: User, + onSuccess: () -> Unit, + onFailure: (Exception) -> Unit ) { try { db.writeTransaction { tx -> tx.execute( - "INSERT INTO $USERS (name, imageUrl, description, dob, fcm_token, locationGIS) VALUES (?, ?, ?, ?, ?, ?);", - user.asList()) + "INSERT INTO $USERS (name, imageUrl, description, dob, preferred_distance, fcm_token) VALUES (?, ?, ?, ?, ?, ?);", + user.asList() + ) } Log.d(TAG, "createUserProfile: Success") onSuccess() @@ -90,35 +116,37 @@ class UserModelPowerSync( } override suspend fun upsertUserProfile( - userDto: UserDto, - onSuccess: (UserDto) -> Unit, - onFailure: (Exception) -> Unit + userDto: UserDto, + onSuccess: (UserDto) -> Unit, + onFailure: (Exception) -> Unit ) { try { val currUser: String? = supabase.auth.currentUserOrNull()?.id db.writeTransaction { tx -> tx.execute( - """ - INSERT INTO $USERS (name, imageUrl, description, dob, fcm_token, locationGIS) - VALUES (?, ?, ?, ?, ?) + """ + 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 = ?, fcm_token = ?, locationGIS = ?; + DO UPDATE SET name = ?, imageUrl = ?, description = ?, dob = ?, preferred_distance = ?, fcm_token = ?; """, - listOf( - currUser, - userDto.name, - userDto.imageUrl, - userDto.description, - userDto.dob, - userDto.fcm_token, - Json.encodeToString(userDto.locationGIS), - userDto.name, - userDto.imageUrl, - userDto.description, - userDto.dob, - userDto.fcm_token, - Json.encodeToString(userDto.locationGIS))) + 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 + ) + ) } Log.d(TAG, "upsertUserProfile: Success") sync() @@ -130,14 +158,14 @@ class UserModelPowerSync( } override suspend fun deleteUserProfile( - idUser: String, - onSuccess: () -> Unit, - onFailure: (Exception) -> Unit + idUser: String, + onSuccess: () -> Unit, + onFailure: (Exception) -> Unit ) { try { val currUser = - supabase.auth.currentUserOrNull()?.id - ?: throw Exception("Supabase does not have a user logged in") + 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)) @@ -152,10 +180,10 @@ class UserModelPowerSync( } override suspend fun uploadFile( - filePath: String, - bytes: ByteArray, - onSuccess: () -> Unit, - onFailure: (Exception) -> Unit, + filePath: String, + bytes: ByteArray, + onSuccess: () -> Unit, + onFailure: (Exception) -> Unit, ) { try { withContext(Dispatchers.Main) { @@ -170,9 +198,9 @@ class UserModelPowerSync( } override suspend fun downloadFile( - filePath: String, - onSuccess: (bytes: ByteArray) -> Unit, - onFailure: (Exception) -> Unit, + filePath: String, + onSuccess: (bytes: ByteArray) -> Unit, + onFailure: (Exception) -> Unit, ) { try { withContext(Dispatchers.Main) { From 4009b2f25443b9cf690f2d2630fcd026f6186e21 Mon Sep 17 00:00:00 2001 From: Alonso Date: Fri, 20 Dec 2024 02:08:40 +0100 Subject: [PATCH 36/37] ktfmt: f o r m a t m e --- .../com/android/periodpals/MainActivity.kt | 124 ++++++++-------- .../com/android/periodpals/model/user/User.kt | 27 ++-- .../android/periodpals/model/user/UserDto.kt | 26 ++-- .../model/user/UserModelPowerSync.kt | 138 +++++++++--------- .../android/periodpals/resources/Schema.kt | 24 ++- 5 files changed, 161 insertions(+), 178 deletions(-) diff --git a/app/src/main/java/com/android/periodpals/MainActivity.kt b/app/src/main/java/com/android/periodpals/MainActivity.kt index 82263e36a..4d4dec8e5 100644 --- a/app/src/main/java/com/android/periodpals/MainActivity.kt +++ b/app/src/main/java/com/android/periodpals/MainActivity.kt @@ -83,14 +83,14 @@ class MainActivity : ComponentActivity() { private lateinit var timerManager: TimerManager private val supabaseClient = - createSupabaseClient( - supabaseUrl = BuildConfig.SUPABASE_URL, - supabaseKey = BuildConfig.SUPABASE_KEY, - ) { - install(Auth) - install(Postgrest) - install(Storage) - } + createSupabaseClient( + supabaseUrl = BuildConfig.SUPABASE_URL, + supabaseKey = BuildConfig.SUPABASE_KEY, + ) { + install(Auth) + install(Postgrest) + install(Storage) + } private val authModel = AuthenticationModelSupabase(supabaseClient) private val authenticationViewModel = AuthenticationViewModel(authModel) @@ -113,7 +113,7 @@ class MainActivity : ComponentActivity() { gpsService = GPSServiceImpl(this, authenticationViewModel, userLocationViewModel) pushNotificationsService = - PushNotificationsServiceImpl(this, authenticationViewModel, userViewModelSupabase) + PushNotificationsServiceImpl(this, authenticationViewModel, userViewModelSupabase) timerManager = TimerManager(this) timerViewModel = TimerViewModel(timerModel, timerManager) @@ -126,14 +126,14 @@ class MainActivity : ComponentActivity() { // Set up the OfflinePlugin for offline storage val offlinePluginFactory = StreamOfflinePluginFactory(appContext = applicationContext) val statePluginFactory = - StreamStatePluginFactory(config = StatePluginConfig(), appContext = this) + StreamStatePluginFactory(config = StatePluginConfig(), appContext = this) // Set up the chat client for API calls and with the plugin for offline storage val chatClient = - ChatClient.Builder(BuildConfig.STREAM_SDK_KEY, applicationContext) - .withPlugins(offlinePluginFactory, statePluginFactory) - .logLevel(ChatLogLevel.ALL) // Set to NOTHING in prod - .build() + ChatClient.Builder(BuildConfig.STREAM_SDK_KEY, applicationContext) + .withPlugins(offlinePluginFactory, statePluginFactory) + .logLevel(ChatLogLevel.ALL) // Set to NOTHING in prod + .build() chatViewModel = ChatViewModel(chatClient) @@ -142,16 +142,16 @@ 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, - userViewModelSupabase, - alertViewModel, - timerViewModel, - chatClient, - chatViewModel, + this, + supabaseClient, + gpsService, + pushNotificationsService, + authenticationViewModel, + userViewModelSupabase, + alertViewModel, + timerViewModel, + chatClient, + chatViewModel, ) } } @@ -184,8 +184,8 @@ class MainActivity : ComponentActivity() { * @param navigationActions The actions used to navigate between screens. */ fun userAuthStateLogic( - authenticationViewModel: AuthenticationViewModel, - navigationActions: NavigationActions, + authenticationViewModel: AuthenticationViewModel, + navigationActions: NavigationActions, ) { when (authenticationViewModel.userAuthenticationState.value) { is UserAuthenticationState.SuccessIsLoggedIn -> navigationActions.navigateTo(Screen.PROFILE) @@ -195,16 +195,16 @@ fun userAuthStateLogic( @Composable fun PeriodPalsApp( - mainActivity: MainActivity, - supabase: SupabaseClient, - gpsService: GPSServiceImpl, - pushNotificationsService: PushNotificationsService, - authenticationViewModel: AuthenticationViewModel, - userViewModel: UserViewModel, - alertViewModel: AlertViewModel, - timerViewModel: TimerViewModel, - chatClient: ChatClient, - chatViewModel: ChatViewModel, + mainActivity: MainActivity, + supabase: SupabaseClient, + gpsService: GPSServiceImpl, + pushNotificationsService: PushNotificationsService, + authenticationViewModel: AuthenticationViewModel, + userViewModel: UserViewModel, + alertViewModel: AlertViewModel, + timerViewModel: TimerViewModel, + chatClient: ChatClient, + chatViewModel: ChatViewModel, ) { val dbDriverFactory = rememberDatabaseDriverFactory() val db = remember { PowerSyncDatabase(dbDriverFactory, schema = localSchema) } @@ -233,12 +233,12 @@ fun PeriodPalsApp( navigation(startDestination = Screen.ALERT, route = Route.ALERT) { composable(Screen.ALERT) { CreateAlertScreen( - locationViewModel, - gpsService, - alertViewModel, - authenticationViewModel, - userViewModel, - navigationActions, + locationViewModel, + gpsService, + alertViewModel, + authenticationViewModel, + userViewModel, + navigationActions, ) } } @@ -247,12 +247,11 @@ fun PeriodPalsApp( navigation(startDestination = Screen.ALERT_LIST, route = Route.ALERT_LIST) { composable(Screen.ALERT_LIST) { AlertListsScreen( - alertViewModel, - authenticationViewModel, - locationViewModel, - gpsService, - navigationActions - ) + alertViewModel, + authenticationViewModel, + locationViewModel, + gpsService, + navigationActions) } composable(Screen.EDIT_ALERT) { EditAlertScreen(locationViewModel, gpsService, alertViewModel, navigationActions) @@ -271,19 +270,17 @@ fun PeriodPalsApp( Log.d(TAG, "Client initialization completed") Log.d(TAG, "Client connection state $clientConnectionState") ChannelsScreen( - title = CHANNEL_SCREEN_TITLE, - isShowingHeader = true, - onChannelClick = { - /** TODO: implement channels here */ - }, - onBackPressed = { navigationActions.navigateTo(Screen.ALERT_LIST) }, + title = CHANNEL_SCREEN_TITLE, + isShowingHeader = true, + onChannelClick = { + /** TODO: implement channels here */ + }, + onBackPressed = { navigationActions.navigateTo(Screen.ALERT_LIST) }, ) } - InitializationState.INITIALIZING -> { Log.d(TAG, "Client initializing") } - InitializationState.NOT_INITIALIZED -> { Log.d(TAG, "Client not initialized yet.") } @@ -310,18 +307,15 @@ fun PeriodPalsApp( navigation(startDestination = Screen.PROFILE, route = Route.PROFILE) { composable(Screen.PROFILE) { ProfileScreen( - userViewModelPowerSync, - authenticationViewModel, - pushNotificationsService, - chatViewModel, - navigationActions, + userViewModelPowerSync, + authenticationViewModel, + pushNotificationsService, + chatViewModel, + navigationActions, ) } composable(Screen.EDIT_PROFILE) { - EditProfileScreen( - userViewModelPowerSync, - navigationActions - ) + EditProfileScreen(userViewModelPowerSync, navigationActions) } composable(Screen.SETTINGS) { SettingsScreen(userViewModelPowerSync, authenticationViewModel, navigationActions) diff --git a/app/src/main/java/com/android/periodpals/model/user/User.kt b/app/src/main/java/com/android/periodpals/model/user/User.kt index 5c27b9ed5..e30489a45 100644 --- a/app/src/main/java/com/android/periodpals/model/user/User.kt +++ b/app/src/main/java/com/android/periodpals/model/user/User.kt @@ -11,12 +11,12 @@ package com.android.periodpals.model.user * @property fcmToken The Firebase Cloud Messaging token for the user (optional). */ data class User( - val name: String, - val imageUrl: String, - val description: String, - val dob: String, - val preferredDistance: Int, - val fcmToken: String? = null + val name: String, + val imageUrl: String, + val description: String, + val dob: String, + val preferredDistance: Int, + val fcmToken: String? = null ) { /** * Converts the User object to a UserDto object. @@ -25,15 +25,14 @@ data class User( */ fun asUserDto(): UserDto { return UserDto( - name = this.name, - imageUrl = this.imageUrl, - description = this.description, - dob = this.dob, - preferred_distance = this.preferredDistance, - fcm_token = this.fcmToken - ) + name = this.name, + imageUrl = this.imageUrl, + description = this.description, + dob = this.dob, + preferred_distance = this.preferredDistance, + fcm_token = this.fcmToken) } inline fun asList(): List = - listOf(name, imageUrl, description, dob, preferredDistance, fcmToken) + listOf(name, imageUrl, description, dob, preferredDistance, fcmToken) } diff --git a/app/src/main/java/com/android/periodpals/model/user/UserDto.kt b/app/src/main/java/com/android/periodpals/model/user/UserDto.kt index 0f3ce379e..9dadc4dd5 100644 --- a/app/src/main/java/com/android/periodpals/model/user/UserDto.kt +++ b/app/src/main/java/com/android/periodpals/model/user/UserDto.kt @@ -14,12 +14,12 @@ import kotlinx.serialization.Serializable */ @Serializable data class UserDto( - val name: String, - val imageUrl: String, - val description: String, - val dob: String, - val preferred_distance: Int, - val fcm_token: String? = null, + val name: String, + val imageUrl: String, + val description: String, + val dob: String, + val preferred_distance: Int, + val fcm_token: String? = null, ) { /** * Converts this UserDto to a User object. @@ -28,15 +28,15 @@ data class UserDto( */ fun asUser(): User { return User( - name = this.name, - imageUrl = this.imageUrl, - description = this.description, - dob = this.dob, - preferredDistance = this.preferred_distance, - fcmToken = this.fcm_token, + name = this.name, + imageUrl = this.imageUrl, + description = this.description, + dob = this.dob, + preferredDistance = this.preferred_distance, + fcmToken = this.fcm_token, ) } inline fun asList(): List = - listOf(name, imageUrl, description, dob, preferred_distance, fcm_token) + listOf(name, imageUrl, description, dob, preferred_distance, fcm_token) } diff --git a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt index a3719517f..1707ad891 100644 --- a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt +++ b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt @@ -20,9 +20,9 @@ private const val TAG = "UserModelPowerSync" private const val USERS = "users" class UserModelPowerSync( - private val db: PowerSyncDatabase, - private val connector: SupabaseConnector, - private val supabase: SupabaseClient + private val db: PowerSyncDatabase, + private val connector: SupabaseConnector, + private val supabase: SupabaseClient ) : UserRepository { private suspend fun sync() { @@ -35,26 +35,24 @@ class UserModelPowerSync( } override suspend fun loadUserProfile( - idUser: String, - onSuccess: (UserDto) -> Unit, - onFailure: (Exception) -> Unit + idUser: String, + onSuccess: (UserDto) -> Unit, + onFailure: (Exception) -> Unit ) { try { sync() val user: UserDto = - db.get( - "SELECT name, imageUrl, description, dob, preferred_distance, fcm_token, locationGIS 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) - ) - } + db.get( + "SELECT name, imageUrl, description, dob, preferred_distance, fcm_token, locationGIS 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) @@ -65,25 +63,23 @@ class UserModelPowerSync( } override suspend fun loadUserProfiles( - onSuccess: (List) -> Unit, - onFailure: (Exception) -> Unit + onSuccess: (List) -> Unit, + onFailure: (Exception) -> Unit ) { try { sync() val users: List = - 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) - ) - } + 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) @@ -91,20 +87,18 @@ class UserModelPowerSync( Log.d(TAG, "loadUserProfiles: fail to load users profiles: ${e.message}") onFailure(e) } - } override suspend fun createUserProfile( - user: User, - onSuccess: () -> Unit, - onFailure: (Exception) -> Unit + user: User, + onSuccess: () -> Unit, + onFailure: (Exception) -> Unit ) { try { db.writeTransaction { tx -> tx.execute( - "INSERT INTO $USERS (name, imageUrl, description, dob, preferred_distance, fcm_token) VALUES (?, ?, ?, ?, ?, ?);", - user.asList() - ) + "INSERT INTO $USERS (name, imageUrl, description, dob, preferred_distance, fcm_token) VALUES (?, ?, ?, ?, ?, ?);", + user.asList()) } Log.d(TAG, "createUserProfile: Success") onSuccess() @@ -116,37 +110,35 @@ class UserModelPowerSync( } override suspend fun upsertUserProfile( - userDto: UserDto, - onSuccess: (UserDto) -> Unit, - onFailure: (Exception) -> Unit + userDto: UserDto, + onSuccess: (UserDto) -> Unit, + onFailure: (Exception) -> Unit ) { try { val currUser: String? = supabase.auth.currentUserOrNull()?.id 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 = ?; """, - 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 - ) - ) + 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)) } Log.d(TAG, "upsertUserProfile: Success") sync() @@ -158,14 +150,14 @@ class UserModelPowerSync( } override suspend fun deleteUserProfile( - idUser: String, - onSuccess: () -> Unit, - onFailure: (Exception) -> Unit + idUser: String, + onSuccess: () -> Unit, + onFailure: (Exception) -> Unit ) { try { val currUser = - supabase.auth.currentUserOrNull()?.id - ?: throw Exception("Supabase does not have a user logged in") + 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)) @@ -180,10 +172,10 @@ class UserModelPowerSync( } override suspend fun uploadFile( - filePath: String, - bytes: ByteArray, - onSuccess: () -> Unit, - onFailure: (Exception) -> Unit, + filePath: String, + bytes: ByteArray, + onSuccess: () -> Unit, + onFailure: (Exception) -> Unit, ) { try { withContext(Dispatchers.Main) { @@ -198,9 +190,9 @@ class UserModelPowerSync( } override suspend fun downloadFile( - filePath: String, - onSuccess: (bytes: ByteArray) -> Unit, - onFailure: (Exception) -> Unit, + filePath: String, + onSuccess: (bytes: ByteArray) -> Unit, + onFailure: (Exception) -> Unit, ) { try { withContext(Dispatchers.Main) { diff --git a/app/src/main/java/com/android/periodpals/resources/Schema.kt b/app/src/main/java/com/android/periodpals/resources/Schema.kt index 5f279613a..d82965324 100644 --- a/app/src/main/java/com/android/periodpals/resources/Schema.kt +++ b/app/src/main/java/com/android/periodpals/resources/Schema.kt @@ -7,18 +7,16 @@ import com.powersync.db.schema.Table const val USERS = "users" val users = - Table( - USERS, - listOf( - Column.text("user_id"), - Column.text("name"), - Column.text("email"), - Column.text("imageUrl"), - Column.text("description"), - Column.text("dob"), - Column.integer("preferred_distance"), - Column.text("fcm_token") - ) - ) + Table( + USERS, + listOf( + Column.text("user_id"), + Column.text("name"), + Column.text("email"), + Column.text("imageUrl"), + Column.text("description"), + Column.text("dob"), + Column.integer("preferred_distance"), + Column.text("fcm_token"))) val localSchema: Schema = Schema(listOf(users)) From a87cd97df08a1567039a4b36b097a056eb2515e9 Mon Sep 17 00:00:00 2001 From: Alonso Date: Fri, 20 Dec 2024 05:59:37 +0100 Subject: [PATCH 37/37] fix: buggy at edit release --- .../com/android/periodpals/MainActivity.kt | 2 - .../model/user/UserModelPowerSync.kt | 155 ++++++------ .../periodpals/model/user/UserViewModel.kt | 238 +++++++++--------- 3 files changed, 205 insertions(+), 190 deletions(-) diff --git a/app/src/main/java/com/android/periodpals/MainActivity.kt b/app/src/main/java/com/android/periodpals/MainActivity.kt index 4d4dec8e5..110be3582 100644 --- a/app/src/main/java/com/android/periodpals/MainActivity.kt +++ b/app/src/main/java/com/android/periodpals/MainActivity.kt @@ -70,7 +70,6 @@ import io.github.jan.supabase.auth.Auth import io.github.jan.supabase.createSupabaseClient import io.github.jan.supabase.postgrest.Postgrest import io.github.jan.supabase.storage.Storage -import kotlinx.coroutines.runBlocking import org.osmdroid.config.Configuration private const val TAG = "MainActivity" @@ -209,7 +208,6 @@ fun PeriodPalsApp( val dbDriverFactory = rememberDatabaseDriverFactory() val db = remember { PowerSyncDatabase(dbDriverFactory, schema = localSchema) } val supabaseConnector = remember { SupabaseConnector(supabase, BuildConfig.POWERSYNC_URL) } - runBlocking { db.connect(supabaseConnector) } val userModelPowerSync = remember { UserModelPowerSync(db, supabaseConnector, supabase) } val userViewModelPowerSync = remember { UserViewModel(userModelPowerSync) } diff --git a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt index 1707ad891..46b28be75 100644 --- a/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt +++ b/app/src/main/java/com/android/periodpals/model/user/UserModelPowerSync.kt @@ -20,9 +20,9 @@ private const val TAG = "UserModelPowerSync" private const val USERS = "users" class UserModelPowerSync( - private val db: PowerSyncDatabase, - private val connector: SupabaseConnector, - private val supabase: SupabaseClient + private val db: PowerSyncDatabase, + private val connector: SupabaseConnector, + private val supabase: SupabaseClient ) : UserRepository { private suspend fun sync() { @@ -35,24 +35,27 @@ class UserModelPowerSync( } override suspend fun loadUserProfile( - idUser: String, - onSuccess: (UserDto) -> Unit, - onFailure: (Exception) -> Unit + 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, locationGIS 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)) - } + 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) @@ -63,23 +66,26 @@ class UserModelPowerSync( } override suspend fun loadUserProfiles( - onSuccess: (List) -> Unit, - onFailure: (Exception) -> Unit + onSuccess: (List) -> Unit, + onFailure: (Exception) -> Unit ) { try { + db.connect(connector) sync() val users: List = - 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)) - } + 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) @@ -90,15 +96,17 @@ class UserModelPowerSync( } override suspend fun createUserProfile( - user: User, - onSuccess: () -> Unit, - onFailure: (Exception) -> Unit + 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()) + "INSERT INTO $USERS (name, imageUrl, description, dob, preferred_distance, fcm_token) VALUES (?, ?, ?, ?, ?, ?);", + user.asList() + ) } Log.d(TAG, "createUserProfile: Success") onSuccess() @@ -110,35 +118,42 @@ class UserModelPowerSync( } override suspend fun upsertUserProfile( - userDto: UserDto, - onSuccess: (UserDto) -> Unit, - onFailure: (Exception) -> Unit + 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 = ?; + """ + 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)) + 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() @@ -150,14 +165,14 @@ class UserModelPowerSync( } override suspend fun deleteUserProfile( - idUser: String, - onSuccess: () -> Unit, - onFailure: (Exception) -> Unit + idUser: String, + onSuccess: () -> Unit, + onFailure: (Exception) -> Unit ) { try { val currUser = - supabase.auth.currentUserOrNull()?.id - ?: throw Exception("Supabase does not have a user logged in") + 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)) @@ -172,10 +187,10 @@ class UserModelPowerSync( } override suspend fun uploadFile( - filePath: String, - bytes: ByteArray, - onSuccess: () -> Unit, - onFailure: (Exception) -> Unit, + filePath: String, + bytes: ByteArray, + onSuccess: () -> Unit, + onFailure: (Exception) -> Unit, ) { try { withContext(Dispatchers.Main) { @@ -190,9 +205,9 @@ class UserModelPowerSync( } override suspend fun downloadFile( - filePath: String, - onSuccess: (bytes: ByteArray) -> Unit, - onFailure: (Exception) -> Unit, + filePath: String, + onSuccess: (bytes: ByteArray) -> Unit, + onFailure: (Exception) -> Unit, ) { try { withContext(Dispatchers.Main) { diff --git a/app/src/main/java/com/android/periodpals/model/user/UserViewModel.kt b/app/src/main/java/com/android/periodpals/model/user/UserViewModel.kt index cb7633415..26d73f855 100644 --- a/app/src/main/java/com/android/periodpals/model/user/UserViewModel.kt +++ b/app/src/main/java/com/android/periodpals/model/user/UserViewModel.kt @@ -8,11 +8,11 @@ import androidx.lifecycle.viewModelScope import com.dsc.form_builder.FormState import com.dsc.form_builder.TextFieldState import com.dsc.form_builder.Validators +import kotlinx.coroutines.launch import java.text.DateFormat import java.time.LocalDate import java.time.format.DateTimeFormatter import java.util.Locale -import kotlinx.coroutines.launch private const val TAG = "UserViewModel" @@ -24,28 +24,28 @@ private const val ERROR_INVALID_NAME = "Please enter a name" private const val ERROR_NAME_TOO_LONG = "Name must be less than $MAX_NAME_LENGTH characters" private const val ERROR_INVALID_DESCRIPTION = "Please enter a description" private const val ERROR_DESCRIPTION_TOO_LONG = - "Description must be less than $MAX_DESCRIPTION_LENGTH characters" + "Description must be less than $MAX_DESCRIPTION_LENGTH characters" private const val ERROR_INVALID_DOB = "Please enter a valid date" private const val ERROR_TOO_YOUNG = "You must be at least $MIN_AGE years old" private val nameValidators = - listOf( - Validators.Required(message = ERROR_INVALID_NAME), - Validators.Max(message = ERROR_NAME_TOO_LONG, limit = MAX_NAME_LENGTH), - ) + listOf( + Validators.Required(message = ERROR_INVALID_NAME), + Validators.Max(message = ERROR_NAME_TOO_LONG, limit = MAX_NAME_LENGTH), + ) private val descriptionValidators = - listOf( - Validators.Required(message = ERROR_INVALID_DESCRIPTION), - Validators.Max(message = ERROR_DESCRIPTION_TOO_LONG, limit = MAX_DESCRIPTION_LENGTH), - ) + listOf( + Validators.Required(message = ERROR_INVALID_DESCRIPTION), + Validators.Max(message = ERROR_DESCRIPTION_TOO_LONG, limit = MAX_DESCRIPTION_LENGTH), + ) private val dobValidators = - listOf( - Validators.Required(message = ERROR_INVALID_DOB), - Validators.Custom(message = ERROR_INVALID_DOB, function = { validateDate(it as String) }), - Validators.Custom(message = ERROR_TOO_YOUNG, function = { isOldEnough(it as String) }), - ) + listOf( + Validators.Required(message = ERROR_INVALID_DOB), + Validators.Custom(message = ERROR_INVALID_DOB, function = { validateDate(it as String) }), + Validators.Custom(message = ERROR_TOO_YOUNG, function = { isOldEnough(it as String) }), + ) private val profileImageValidators = - emptyList() // TODO: add validators when profile image is implemented + emptyList() // TODO: add validators when profile image is implemented /** * ViewModel for managing user data. @@ -68,15 +68,17 @@ class UserViewModel(private val userRepository: UserRepository) : ViewModel() { val avatar: State = _avatar val formState = - FormState( - fields = - listOf( - TextFieldState(name = NAME_STATE_NAME, validators = nameValidators), - TextFieldState(name = DESCRIPTION_STATE_NAME, validators = descriptionValidators), - TextFieldState(name = DOB_STATE_NAME, validators = dobValidators), - TextFieldState( - name = PROFILE_IMAGE_STATE_NAME, validators = profileImageValidators), - )) + FormState( + fields = + listOf( + TextFieldState(name = NAME_STATE_NAME, validators = nameValidators), + TextFieldState(name = DESCRIPTION_STATE_NAME, validators = descriptionValidators), + TextFieldState(name = DOB_STATE_NAME, validators = dobValidators), + TextFieldState( + name = PROFILE_IMAGE_STATE_NAME, validators = profileImageValidators + ), + ) + ) /** * Loads the user profile and updates the user state. @@ -87,25 +89,25 @@ class UserViewModel(private val userRepository: UserRepository) : ViewModel() { * 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}") - }, + 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() - onSuccess() - }, - onFailure = { e: Exception -> - Log.d(TAG, "loadUserProfile: fail to load user profile: ${e.message}") - _user.value = null - onFailure(e) - }, + idUser, + onSuccess = { userDto -> + Log.d(TAG, "loadUserProfile: Successful") + _user.value = userDto.asUser() + onSuccess() + }, + onFailure = { e: Exception -> + Log.d(TAG, "loadUserProfile: fail to load user profile: ${e.message}") + //_user.value = null + onFailure(e) + }, ) } } @@ -118,23 +120,23 @@ class UserViewModel(private val userRepository: UserRepository) : ViewModel() { * profiles. */ fun loadUsers( - onSuccess: () -> Unit = { Log.d(TAG, "loadUsers success callback") }, - onFailure: (Exception) -> Unit = { e: Exception -> - Log.d(TAG, "loadUsers failure callback: ${e.message}") - }, + 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) - }, + 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) + }, ) } } @@ -147,25 +149,25 @@ class UserViewModel(private val userRepository: UserRepository) : ViewModel() { * @param onFailure Callback function to be called when there is an error saving the user profile. */ fun saveUser( - user: User, - onSuccess: () -> Unit = { Log.d(TAG, "saveUser success callback") }, - onFailure: (Exception) -> Unit = { e: Exception -> - Log.d(TAG, "saveUser failure callback: ${e.message}") - }, + user: User, + onSuccess: () -> Unit = { Log.d(TAG, "saveUser success callback") }, + onFailure: (Exception) -> Unit = { e: Exception -> + Log.d(TAG, "saveUser failure callback: ${e.message}") + }, ) { viewModelScope.launch { userRepository.upsertUserProfile( - user.asUserDto(), - onSuccess = { - Log.d(TAG, "saveUser: Success") - _user.value = it.asUser() - onSuccess() - }, - onFailure = { e: Exception -> - Log.d(TAG, "saveUser: fail to save user: ${e.message}") - _user.value = null - onFailure(e) - }, + user.asUserDto(), + onSuccess = { + Log.d(TAG, "saveUser: Success") + _user.value = it.asUser() + onSuccess() + }, + onFailure = { e: Exception -> + Log.d(TAG, "saveUser: fail to save user: ${e.message}") + //_user.value = null + onFailure(e) + }, ) } } @@ -179,24 +181,24 @@ class UserViewModel(private val userRepository: UserRepository) : ViewModel() { * profile. */ fun deleteUser( - idUser: String, - onSuccess: () -> Unit = { Log.d(TAG, "deleteAccount success callback") }, - onFailure: (Exception) -> Unit = { e: Exception -> - Log.d(TAG, "deleteAccount failure callback: ${e.message}") - }, + idUser: String, + onSuccess: () -> Unit = { Log.d(TAG, "deleteAccount success callback") }, + onFailure: (Exception) -> Unit = { e: Exception -> + Log.d(TAG, "deleteAccount failure callback: ${e.message}") + }, ) { viewModelScope.launch { userRepository.deleteUserProfile( - idUser, - onSuccess = { - Log.d(TAG, "deleteAccount: Success") - _user.value = null - onSuccess() - }, - onFailure = { e: Exception -> - Log.d(TAG, "deleteAccount : fail to delete user: ${e.message}") - onFailure(e) - }, + idUser, + onSuccess = { + Log.d(TAG, "deleteAccount: Success") + _user.value = null + onSuccess() + }, + onFailure = { e: Exception -> + Log.d(TAG, "deleteAccount : fail to delete user: ${e.message}") + onFailure(e) + }, ) } } @@ -210,25 +212,25 @@ class UserViewModel(private val userRepository: UserRepository) : ViewModel() { * @param onFailure Callback function to be called when there is an exception. */ fun uploadFile( - filePath: String, - bytes: ByteArray, - onSuccess: () -> Unit = { Log.d(TAG, "uploadFile success callback") }, - onFailure: (Exception) -> Unit = { e: Exception -> - Log.d(TAG, "uploadFile failure callback: ${e.message}") - }, + filePath: String, + bytes: ByteArray, + onSuccess: () -> Unit = { Log.d(TAG, "uploadFile success callback") }, + onFailure: (Exception) -> Unit = { e: Exception -> + Log.d(TAG, "uploadFile failure callback: ${e.message}") + }, ) { viewModelScope.launch { userRepository.uploadFile( - filePath, - bytes, - onSuccess = { - Log.d(TAG, "uploadFile: Success") - onSuccess() - }, - onFailure = { e: Exception -> - Log.d(TAG, "uploadFile: fail to upload file: ${e.message}") - onFailure(e) - }, + filePath, + bytes, + onSuccess = { + Log.d(TAG, "uploadFile: Success") + onSuccess() + }, + onFailure = { e: Exception -> + Log.d(TAG, "uploadFile: fail to upload file: ${e.message}") + onFailure(e) + }, ) } } @@ -241,24 +243,24 @@ class UserViewModel(private val userRepository: UserRepository) : ViewModel() { * @param onFailure Callback function to be called when there is an exception. */ fun downloadFile( - filePath: String, - onSuccess: () -> Unit = { Log.d(TAG, "downloadFile success callback") }, - onFailure: (Exception) -> Unit = { e: Exception -> - Log.d(TAG, "downloadFile failure callback: ${e.message}") - }, + filePath: String, + onSuccess: () -> Unit = { Log.d(TAG, "downloadFile success callback") }, + onFailure: (Exception) -> Unit = { e: Exception -> + Log.d(TAG, "downloadFile failure callback: ${e.message}") + }, ) { viewModelScope.launch { userRepository.downloadFile( - filePath, - onSuccess = { bytes -> - Log.d(TAG, "downloadFile: Success") - _avatar.value = bytes - onSuccess() - }, - onFailure = { e: Exception -> - Log.d(TAG, "downloadFile: fail to download file: ${e.message}") - onFailure(e) - }, + filePath, + onSuccess = { bytes -> + Log.d(TAG, "downloadFile: Success") + _avatar.value = bytes + onSuccess() + }, + onFailure = { e: Exception -> + Log.d(TAG, "downloadFile: fail to download file: ${e.message}") + onFailure(e) + }, ) } } @@ -290,7 +292,7 @@ fun validateDate(date: String): Boolean { fun isOldEnough(date: String): Boolean { return try { LocalDate.parse(date, DateTimeFormatter.ofPattern("dd/MM/yyyy")) - .isBefore(LocalDate.now().minusYears(MIN_AGE)) + .isBefore(LocalDate.now().minusYears(MIN_AGE)) } catch (e: Exception) { false }