Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ dependencies {
implementation(libs.androidx.credentials.play.services.auth)
implementation(libs.googleid)
implementation(libs.play.services.auth)

implementation(libs.kotlinx.coroutines.play.services)

/*google drive api*/
implementation(libs.google.api.client.android)
Expand Down
43 changes: 33 additions & 10 deletions core/src/main/java/com/app/open/piccollab/core/auth/AuthManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import kotlinx.coroutines.tasks.await
import java.security.MessageDigest
import java.util.Date
import java.util.UUID

private const val TAG = "AuthManager"

class AuthManager(private val dataStorePref: DataStorePref) {
class AuthManager(private val context: Context, private val dataStorePref: DataStorePref) {
fun startGoogleAuthentication(context: Context): Flow<UserData?> {
return flow {
try {
Expand Down Expand Up @@ -100,7 +102,6 @@ class AuthManager(private val dataStorePref: DataStorePref) {
Identity.getAuthorizationClient(activity)
.authorize(authorizationRequest)
.addOnSuccessListener { authorizationResult ->

if (authorizationResult.hasResolution()) {
val pendingIntent = authorizationResult.pendingIntent
// Access needs to be granted by the user
Expand Down Expand Up @@ -128,28 +129,50 @@ class AuthManager(private val dataStorePref: DataStorePref) {
TAG,
"getDrivePermission: result authorizationResult: $authorizationResult"
)

RestApiManager.accessToken = authorizationResult.accessToken
CoroutineScope(Dispatchers.IO).launch {
dataStorePref.setAccessToken(
authorizationResult.accessToken ?: ""
)

dataStorePref.setExpiresIn(Date().time + 300_000)
delay(1000)
dataStorePref.getAccessToken().collect { accessToken ->
Log.d(TAG, "getDrivePermission: accessTokenSaved: $accessToken")
}


} //how to call this suspend method
// Access was previously granted, continue with user action
/*saveToDriveAppFolder(authorizationResult);*/
}
}
}
.addOnFailureListener { e -> Log.e(TAG, "Failed to authorize", e) }
}

fun logout(activity: Activity){

suspend fun getNewToken() {
Log.d(TAG, "getNewToken: ")

val requestedScopes = listOf(Scope(DriveScopes.DRIVE_FILE))
val authorizationRequest = AuthorizationRequest.Builder()
.setRequestedScopes(requestedScopes)
.build()

try {
// Use await() to suspend until the Task is complete
val result = Identity.getAuthorizationClient(context)
.authorize(authorizationRequest)
.await()

Log.d(TAG, "getNewToken: result: $result")

dataStorePref.setAccessToken(result.accessToken ?: "")
dataStorePref.setExpiresIn(Date().time + 300_000) // 5 minutes from now

} catch (e: Exception) {
Log.e(TAG, "Error getting new token", e)
// handle errors properly
}
}


fun logout(activity: Activity) {
val signInClient = Identity.getSignInClient(activity)
// This clears the account and forces re-consent next time
signInClient.signOut()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ class DataStorePref(private val context: Context) {
}
}

fun getExpiresIn(): Flow<Long> {
suspend fun getExpiresIn(): Long {
return context.dataStore.data.map { preferences ->
preferences[EXPIRES_IN_KEY] ?: 0L
}
}.first()
}

suspend fun setExpiresIn(expiresIn: Long) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,18 @@ class EventFolderRepository(

suspend fun getOrCreateProjectFolder(): String {
/*getting root folder id from local data store*/
Log.d(TAG, "getOrCreateProjectFolder: getting root folder id from local data store")
val rootFolderIfFromPref = dataStorePref.getDataValue(ROOT_FOLDER_KEY)
if (rootFolderIfFromPref == null) {
if (rootFolderIfFromPref.isNullOrBlank()) {
/*getting root folder if from google drive*/
Log.d(TAG, "getOrCreateProjectFolder: getting root folder if from google drive")
val rootFolderId = driveManager.rootFolderId()
if (rootFolderId != null) {
Log.d(TAG, "getOrCreateProjectFolder: from drive")
return rootFolderId
} else {
/*creating new root folder to google drive*/
Log.d(TAG, "getOrCreateProjectFolder: creating new root folder to google drive")
val rootFolderItem = NewEventItem(
eventName = DEFAULT_PROJECT_FOLDER_NAME,
eventDescription = DEFAULT_PROJECT_FOLDER_DESCRIPTION
Expand Down
9 changes: 7 additions & 2 deletions core/src/main/java/com/app/open/piccollab/core/di/Provider.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.app.open.piccollab.core.di

import android.content.Context
import com.app.open.piccollab.core.auth.AuthManager
import com.app.open.piccollab.core.db.datastore.DataStorePref
import com.app.open.piccollab.core.db.room.database.UserDatabase
import com.app.open.piccollab.core.network.module.drive.DriveManager
Expand Down Expand Up @@ -32,8 +33,12 @@ class Provider {

@Singleton
@Provides
fun providesDriveManager(@ApplicationContext context: Context, dataStorePref: DataStorePref) =
DriveManager(context, dataStorePref)
fun providesDriveManager(
@ApplicationContext context: Context,
dataStorePref: DataStorePref,
authManager: AuthManager
) =
DriveManager(context, dataStorePref, authManager)


}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package com.app.open.piccollab.core.network.module

import android.content.Context
import com.app.open.piccollab.core.auth.AuthManager
import com.app.open.piccollab.core.db.datastore.DataStorePref
import com.app.open.piccollab.core.network.module.apiservices.DriveApiService
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
Expand Down Expand Up @@ -38,7 +40,10 @@ class NetworkModule {

@Singleton
@Provides
fun providesAuthManager(dataStorePref: DataStorePref): AuthManager{
return AuthManager(dataStorePref)
fun providesAuthManager(
@ApplicationContext context: Context,
dataStorePref: DataStorePref
): AuthManager {
return AuthManager(context, dataStorePref)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package com.app.open.piccollab.core.network.module.drive

import android.content.Context
import android.util.Log
import com.app.open.piccollab.core.auth.AuthManager
import com.app.open.piccollab.core.db.datastore.DataStorePref
import com.app.open.piccollab.core.db.datastore.ROOT_FOLDER_KEY
import com.app.open.piccollab.core.db.room.entities.EventFolder
import com.app.open.piccollab.core.db.room.repositories.DEFAULT_PROJECT_FOLDER_NAME
import com.app.open.piccollab.core.models.event.NewEventItem
Expand All @@ -17,7 +17,6 @@ import com.google.auth.oauth2.GoogleCredentials
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex
Expand All @@ -27,7 +26,11 @@ import java.util.Date
private const val TAG = "DriveManager"


class DriveManager(private val context: Context, private val dataStorePref: DataStorePref) {
class DriveManager(
private val context: Context,
private val dataStorePref: DataStorePref,
private val authManager: AuthManager
) {
val tokenMutex = Mutex()

@Volatile
Expand All @@ -46,73 +49,113 @@ class DriveManager(private val context: Context, private val dataStorePref: Data
coroutineScope.launch {
dataStorePref.getAccessToken().collect { token ->
tokenMutex.withLock {
Log.d(TAG, "token: $token")
_token = token
}
}
}
}

fun createFolder(eventItem: NewEventItem, projectFolderId: String? = null): String {
suspend fun createFolder(eventItem: NewEventItem, projectFolderId: String? = null): String {
Log.d(
TAG,
"createFolder() called with: eventItem = $eventItem, projectFolderId = $projectFolderId"
)
val file = File()
file.mimeType = "application/vnd.google-apps.folder"
file.name = eventItem.eventName
file.description = eventItem.eventDescription
file.parents = listOf(projectFolderId)
try {
val outFile = getDriveService().files().create(file).execute()
Log.d(TAG, "createFolder() returned: id: ${outFile.id}")
return outFile.id
val outFile = getDriveService()?.files()?.create(file)?.execute()
Log.d(TAG, "createFolder() returned: id: ${outFile?.id}")
return outFile?.id ?: ""
} catch (e: Exception) {
Log.w(TAG, "createFolder: ", e)
return ""
}
}


private fun getDriveService(): Drive {
private suspend fun getDriveService(): Drive? {
Log.d(TAG, "getDriveService() called")
val accessToken = AccessToken(token, Date(Date().time + 300_000))
val googleCredential = GoogleCredentials.create(accessToken)

val requestInitializer = HttpCredentialsAdapter(googleCredential)
val driveService: Drive = Drive.Builder(
NetHttpTransport(),
GsonFactory.getDefaultInstance(),
requestInitializer
).setApplicationName(context.packageName).build()
val driveService: Drive = try {
/*checking accessToken expiry*/
val expiryTime = dataStorePref.getExpiresIn()
val currentTime = Date().time

Log.d(TAG, "getDriveService: expiryTime: $expiryTime")
Log.d(TAG, "getDriveService: currentTime: $currentTime")
if (currentTime > expiryTime) {
Log.d(TAG, "getDriveService: getting new token")
authManager.getNewToken()
Log.d(TAG, "getDriveService: new token received")
}


val accessToken = AccessToken(token, null)
val googleCredential = GoogleCredentials.create(accessToken)

val requestInitializer = HttpCredentialsAdapter(googleCredential)
Drive.Builder(
NetHttpTransport(), GsonFactory.getDefaultInstance(), requestInitializer
).setApplicationName(context.packageName).build()
} catch (e: Exception) {
Log.d(TAG, "getDriveService: ", e)
return null
}
return driveService
}

/*
fun cancelDriveManagerCoroutine() {
coroutineScope.cancel()
}

fun rootFolderId(): String? {
val fileList = getDriveService().files().list().setQ(
"mimeType='application/vnd.google-apps.folder'"
)
.setFields("files(id, name, createdTime, mimeType)")
.execute().files
Log.d(TAG, "rootFolderId: files: $fileList")
val rootFolder = fileList.find { file -> file.name == DEFAULT_PROJECT_FOLDER_NAME }
val rootFolderId = rootFolder?.id
Log.d(TAG, "rootFolderId() returned: $rootFolderId")
return rootFolderId
*/

suspend fun rootFolderId(): String? {
Log.d(TAG, "rootFolderId() called")
val fileList = try {
getDriveService()?.files()?.list()?.setQ(
"mimeType='application/vnd.google-apps.folder'"
)?.setFields("files(id, name, createdTime, mimeType)")?.execute()?.files
} catch (e: Exception) {
Log.w(TAG, "rootFolderId: ", e)
return null
}
if (fileList != null) {
Log.d(TAG, "rootFolderId: files: $fileList")
val rootFolder = fileList.find { file -> file.name == DEFAULT_PROJECT_FOLDER_NAME }
val rootFolderId = rootFolder?.id
Log.d(TAG, "rootFolderId() returned: $rootFolderId")
return rootFolderId
} else {
return null
}
}

fun getEventFolderFromDrive(rootProjectId :String): List<EventFolder> {
suspend fun getEventFolderFromDrive(rootProjectId: String): List<EventFolder> {
Log.d(TAG, "getEventFolderFromDrive: ")
val fileList = getDriveService().files().list().setQ(
"'$rootProjectId' in parents and mimeType='application/vnd.google-apps.folder'"
)
.setFields("files(id, name, createdTime, mimeType, description)")
.execute().files
val eventFolderList = mutableListOf<EventFolder>()
fileList.forEach { file ->
eventFolderList.add(EventFolder(file.id, file.name, file.description))
val driveService = getDriveService()
if (driveService != null) {

val fileList = try {
driveService.files().list().setQ(
"'$rootProjectId' in parents and mimeType='application/vnd.google-apps.folder'"
).setFields("files(id, name, createdTime, mimeType, description)").execute().files
} catch (e: Exception) {
Log.w(TAG, "getEventFolderFromDrive: ", e)
return emptyList()
}
val eventFolderList = mutableListOf<EventFolder>()
fileList.forEach { file ->
eventFolderList.add(EventFolder(file.id, file.name, file.description))
}
Log.d(TAG, "getEventFolderFromDrive: event folder list $eventFolderList")
return eventFolderList
} else {
return emptyList()
}
Log.d(TAG, "getEventFolderFromDrive: event folder list $eventFolderList")
return eventFolderList
}

}
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ coreKtx = "1.10.1"
junit = "4.13.2"
junitVersion = "1.1.5"
espressoCore = "3.5.1"
kotlinxCoroutinesPlayServices = "1.10.2"
kotlinxSerializationJson = "1.9.0"
lifecycleRuntimeKtx = "2.6.1"
activityCompose = "1.8.0"
Expand Down Expand Up @@ -70,6 +71,7 @@ androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-man
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
kotlinx-coroutines-play-services = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-play-services", version.ref = "kotlinxCoroutinesPlayServices" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "loggingInterceptor" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
Expand Down