-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add Google Drive support #13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
bc4f187
a68664d
2061490
861c4ea
19ab7a7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,221 @@ | ||||||||||
| package io.github.smiling_pixel.client | ||||||||||
|
|
||||||||||
| import android.content.Context | ||||||||||
| import com.google.android.gms.auth.api.signin.GoogleSignIn | ||||||||||
| import com.google.android.gms.auth.api.signin.GoogleSignInOptions | ||||||||||
| import com.google.android.gms.common.api.ApiException | ||||||||||
| import com.google.android.gms.common.api.Scope | ||||||||||
| import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential | ||||||||||
| import com.google.api.client.extensions.android.http.AndroidHttp | ||||||||||
| import com.google.api.client.http.ByteArrayContent | ||||||||||
| import com.google.api.client.http.javanet.NetHttpTransport | ||||||||||
| import com.google.api.client.json.gson.GsonFactory | ||||||||||
| import com.google.api.services.drive.Drive | ||||||||||
| import com.google.api.services.drive.DriveScopes | ||||||||||
| import com.google.api.services.drive.model.File | ||||||||||
| import io.github.smiling_pixel.preference.AndroidContextProvider | ||||||||||
| import kotlinx.coroutines.Dispatchers | ||||||||||
| import kotlinx.coroutines.withContext | ||||||||||
| import java.io.ByteArrayOutputStream | ||||||||||
| import java.util.Collections | ||||||||||
|
|
||||||||||
| class GoogleDriveClient : CloudDriveClient { | ||||||||||
|
|
||||||||||
| private val context: Context | ||||||||||
| get() = AndroidContextProvider.context | ||||||||||
|
|
||||||||||
| private val jsonFactory = GsonFactory.getDefaultInstance() | ||||||||||
| private val appName = "MarkDay Diary" | ||||||||||
| private val MIME_TYPE_FOLDER = "application/vnd.google-apps.folder" | ||||||||||
|
|
||||||||||
| private var driveService: Drive? = null | ||||||||||
|
|
||||||||||
| private fun getService(): Drive { | ||||||||||
| return driveService ?: throw IllegalStateException("Google Drive not authorized") | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // Checking auth state and initializing service if possible | ||||||||||
| private fun checkAndInitService(): Boolean { | ||||||||||
| if (driveService != null) return true | ||||||||||
|
|
||||||||||
| val account = GoogleSignIn.getLastSignedInAccount(context) | ||||||||||
| val driveScope = Scope(DriveScopes.DRIVE_FILE) | ||||||||||
|
|
||||||||||
| if (account != null && GoogleSignIn.hasPermissions(account, driveScope)) { | ||||||||||
| val email = account.email | ||||||||||
| if (email != null) { | ||||||||||
| initService(email) | ||||||||||
| return true | ||||||||||
| } | ||||||||||
| } | ||||||||||
| return false | ||||||||||
| } | ||||||||||
|
|
||||||||||
| private fun initService(email: String) { | ||||||||||
| val credential = GoogleAccountCredential.usingOAuth2( | ||||||||||
| context, Collections.singleton(DriveScopes.DRIVE_FILE) | ||||||||||
| ) | ||||||||||
| credential.selectedAccountName = email | ||||||||||
|
|
||||||||||
| driveService = Drive.Builder( | ||||||||||
| AndroidHttp.newCompatibleTransport(), | ||||||||||
| jsonFactory, | ||||||||||
| credential | ||||||||||
| ).setApplicationName(appName).build() | ||||||||||
| } | ||||||||||
|
|
||||||||||
| override suspend fun isAuthorized(): Boolean = withContext(Dispatchers.IO) { | ||||||||||
| checkAndInitService() | ||||||||||
| } | ||||||||||
|
|
||||||||||
| override suspend fun authorize(): Boolean = withContext(Dispatchers.Main) { | ||||||||||
| if (withContext(Dispatchers.IO) { checkAndInitService() }) return@withContext true | ||||||||||
|
|
||||||||||
| val driveScope = Scope(DriveScopes.DRIVE_FILE) | ||||||||||
| val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) | ||||||||||
| .requestEmail() | ||||||||||
| .requestScopes(driveScope) | ||||||||||
| .build() | ||||||||||
|
|
||||||||||
| val client = GoogleSignIn.getClient(context, gso) | ||||||||||
| val signInIntent = client.signInIntent | ||||||||||
|
|
||||||||||
| val result = GoogleSignInHelper.launchSignIn(signInIntent) | ||||||||||
|
|
||||||||||
| if (result != null && result.resultCode == android.app.Activity.RESULT_OK) { | ||||||||||
| val task = GoogleSignIn.getSignedInAccountFromIntent(result.data) | ||||||||||
|
||||||||||
| val task = GoogleSignIn.getSignedInAccountFromIntent(result.data) | |
| val task = GoogleSignIn.getSignedInAccountFromIntent(result.data) |
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is inconsistent indentation on lines 87-88 and 96-97. These lines use an extra space before the keywords, while surrounding code has proper indentation. The indentation should be aligned consistently with the rest of the code block.
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is inconsistent indentation on lines 108-110. These lines use an extra space before the keywords, while surrounding code has proper indentation. The indentation should be aligned consistently with the rest of the code block.
| driveService = null | |
| deferred.complete(Unit) | |
| driveService = null | |
| deferred.complete(Unit) |
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -0,0 +1,30 @@ | ||||
| package io.github.smiling_pixel.client | ||||
|
|
||||
| import android.app.Activity | ||||
|
||||
| import android.app.Activity |
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The import androidx.activity.result.IntentSenderRequest is unused in this file. Consider removing it to keep the imports clean.
| import androidx.activity.result.IntentSenderRequest |
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The GoogleSignInHelper object has a potential race condition. If multiple coroutines call launchSignIn simultaneously, the second call will overwrite authDeferred before the first completes, causing the first caller to never receive its result. Consider using a thread-safe queue or mutex to handle concurrent authorization requests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The import
com.google.api.client.http.javanet.NetHttpTransportis unused in the Android implementation. This import is only relevant for JVM-based implementations. Consider removing it to keep the imports clean.