diff --git a/core/build.gradle.kts b/core/build.gradle.kts index bd63b10..568c854 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -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) diff --git a/core/src/main/java/com/app/open/piccollab/core/auth/AuthManager.kt b/core/src/main/java/com/app/open/piccollab/core/auth/AuthManager.kt index 79ff127..7928afb 100644 --- a/core/src/main/java/com/app/open/piccollab/core/auth/AuthManager.kt +++ b/core/src/main/java/com/app/open/piccollab/core/auth/AuthManager.kt @@ -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 { return flow { try { @@ -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 @@ -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() diff --git a/core/src/main/java/com/app/open/piccollab/core/db/datastore/DataStorePref.kt b/core/src/main/java/com/app/open/piccollab/core/db/datastore/DataStorePref.kt index bc26705..59e37f4 100644 --- a/core/src/main/java/com/app/open/piccollab/core/db/datastore/DataStorePref.kt +++ b/core/src/main/java/com/app/open/piccollab/core/db/datastore/DataStorePref.kt @@ -35,10 +35,10 @@ class DataStorePref(private val context: Context) { } } - fun getExpiresIn(): Flow { + suspend fun getExpiresIn(): Long { return context.dataStore.data.map { preferences -> preferences[EXPIRES_IN_KEY] ?: 0L - } + }.first() } suspend fun setExpiresIn(expiresIn: Long) { diff --git a/core/src/main/java/com/app/open/piccollab/core/db/room/repositories/EventFolderRepository.kt b/core/src/main/java/com/app/open/piccollab/core/db/room/repositories/EventFolderRepository.kt index 9dfc01c..9d1497c 100644 --- a/core/src/main/java/com/app/open/piccollab/core/db/room/repositories/EventFolderRepository.kt +++ b/core/src/main/java/com/app/open/piccollab/core/db/room/repositories/EventFolderRepository.kt @@ -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 diff --git a/core/src/main/java/com/app/open/piccollab/core/di/Provider.kt b/core/src/main/java/com/app/open/piccollab/core/di/Provider.kt index 4867210..06c4da4 100644 --- a/core/src/main/java/com/app/open/piccollab/core/di/Provider.kt +++ b/core/src/main/java/com/app/open/piccollab/core/di/Provider.kt @@ -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 @@ -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) } \ No newline at end of file diff --git a/core/src/main/java/com/app/open/piccollab/core/network/module/NetworkModule.kt b/core/src/main/java/com/app/open/piccollab/core/network/module/NetworkModule.kt index 4f6b75f..85f1fd0 100644 --- a/core/src/main/java/com/app/open/piccollab/core/network/module/NetworkModule.kt +++ b/core/src/main/java/com/app/open/piccollab/core/network/module/NetworkModule.kt @@ -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 @@ -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) } } \ No newline at end of file diff --git a/core/src/main/java/com/app/open/piccollab/core/network/module/drive/DriveManager.kt b/core/src/main/java/com/app/open/piccollab/core/network/module/drive/DriveManager.kt index 1db7143..804142a 100644 --- a/core/src/main/java/com/app/open/piccollab/core/network/module/drive/DriveManager.kt +++ b/core/src/main/java/com/app/open/piccollab/core/network/module/drive/DriveManager.kt @@ -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 @@ -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 @@ -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 @@ -46,22 +49,27 @@ 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 "" @@ -69,50 +77,85 @@ class DriveManager(private val context: Context, private val dataStorePref: Data } - 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 { + suspend fun getEventFolderFromDrive(rootProjectId: String): List { 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() - 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() + 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 } } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f9b2c8e..5aa9b91 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" @@ -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" }