From 5d89b6ab1a1be2fbedd8b66650bdb90e5e9d23c6 Mon Sep 17 00:00:00 2001 From: Nicholas Harrison Date: Tue, 30 Mar 2021 21:00:13 -0600 Subject: [PATCH 1/8] Pieces for Google Drive, authentication working --- app/build.gradle | 21 +- app/src/main/AndroidManifest.xml | 5 + .../com/orgzly/android/di/AppComponent.kt | 4 +- .../android/repos/GoogleDriveClient.java | 269 ++++++++++++ .../orgzly/android/repos/GoogleDriveRepo.java | 69 ++++ .../com/orgzly/android/repos/RepoFactory.kt | 3 + .../java/com/orgzly/android/repos/RepoType.kt | 20 +- .../googledrive/GoogleDriveRepoActivity.kt | 388 ++++++++++++++++++ .../orgzly/android/ui/repos/ReposActivity.kt | 28 ++ .../main/res/layout/activity_repo_dropbox.xml | 2 +- .../res/layout/activity_repo_google_drive.xml | 75 ++++ app/src/main/res/layout/activity_repos.xml | 11 +- app/src/main/res/menu/repos_actions.xml | 4 + app/src/main/res/values/strings.xml | 12 +- 14 files changed, 897 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/com/orgzly/android/repos/GoogleDriveClient.java create mode 100644 app/src/main/java/com/orgzly/android/repos/GoogleDriveRepo.java create mode 100644 app/src/main/java/com/orgzly/android/ui/repo/googledrive/GoogleDriveRepoActivity.kt create mode 100644 app/src/main/res/layout/activity_repo_google_drive.xml diff --git a/app/build.gradle b/app/build.gradle index df3b6765f..5600e7c6a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -70,6 +70,8 @@ android { premium { buildConfigField "boolean", "IS_DROPBOX_ENABLED", "true" + buildConfigField "boolean", "IS_GOOGLE_DRIVE_ENABLED", "true" + buildConfigField "String", "VERSION_NAME_SUFFIX", '""' dimension "store" @@ -77,11 +79,13 @@ android { fdroid { /* - * Disable Dropbox. + * Disable Dropbox and Google Drive. * Properties file which contains the required API key is not included with the code. */ buildConfigField "boolean", "IS_DROPBOX_ENABLED", "false" + buildConfigField "boolean", "IS_GOOGLE_DRIVE_ENABLED", "false" + buildConfigField "String", "VERSION_NAME_SUFFIX", '" (fdroid)"' dimension "store" @@ -169,6 +173,21 @@ dependencies { implementation "com.dropbox.core:dropbox-core-sdk:$versions.dropbox_core_sdk" + // Google Drive + // implementation 'com.google.api-client:google-api-client:1.23.0' + // implementation 'com.google.oauth-client:google-oauth-client-jetty:1.23.0' + // implementation 'com.google.apis:google-api-services-drive:v3-rev110-1.23.0' + + implementation 'com.google.android.gms:play-services-auth:19.0.0' + // implementation 'com.google.android.gms:play-services-drive:17.0.0' + implementation 'com.google.http-client:google-http-client-gson:1.26.0' + implementation('com.google.api-client:google-api-client-android:1.26.0') { + exclude group: 'org.apache.httpcomponents' + } + implementation('com.google.apis:google-api-services-drive:v3-rev136-1.25.0') { + exclude group: 'org.apache.httpcomponents' + } + implementation "com.googlecode.juniversalchardet:juniversalchardet:$versions.juniversalchardet" implementation "com.evernote:android-job:$versions.evernote_android_job" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f463ae55f..ae9e49713 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -93,6 +93,11 @@ android:windowSoftInputMode="stateAlwaysHidden"> + + + getBooks(Uri repoUri) throws IOException { + return null; + // linkedOrThrow(); + // + // List list = new ArrayList<>(); + // + // String path = repoUri.getPath(); + // + // /* Fix root path. */ + // if (path == null || path.equals("/")) { + // path = ROOT_PATH; + // } + // + // /* Strip trailing slashes. */ + // path = path.replaceAll("/+$", ""); + // + // try { + // // TODO: Alter to fit for google + // if (ROOT_PATH.equals(path) || dbxClient.files().getMetadata(path) instanceof FolderMetadata) { + // /* Get folder content. */ + // ListFolderResult result = dbxClient.files().listFolder(path); + // while (true) { + // for (Metadata metadata : result.getEntries()) { + // if (metadata instanceof FileMetadata) { + // FileMetadata file = (FileMetadata) metadata; + // + // if (BookName.isSupportedFormatFileName(file.getName())) { + // Uri uri = repoUri.buildUpon().appendPath(file.getName()).build(); + // VersionedRook book = new VersionedRook( + // repoId, + // RepoType.DROPBOX, + // repoUri, + // uri, + // file.getRev(), + // file.getServerModified().getTime()); + // + // list.add(book); + // } + // } + // } + // + // if (!result.getHasMore()) { + // break; + // } + // + // result = dbxClient.files().listFolderContinue(result.getCursor()); + // } + // + // } else { + // throw new IOException("Not a directory: " + repoUri); + // } + // + // } catch (DbxException e) { + // e.printStackTrace(); + // + // /* If we get NOT_FOUND from Dropbox, just return the empty list. */ + // if (e instanceof GetMetadataErrorException) { + // if (((GetMetadataErrorException) e).errorValue.getPathValue() == LookupError.NOT_FOUND) { + // return list; + // } + // } + // + // throw new IOException("Failed getting the list of files in " + repoUri + + // " listing " + path + ": " + + // (e.getMessage() != null ? e.getMessage() : e.toString())); + // } + // + // return list; + } + + /** + * Download file from Dropbox and store it to a local file. + */ + public VersionedRook download(Uri repoUri, String fileName, File localFile) throws IOException { + return null; + // linkedOrThrow(); + // + // Uri uri = repoUri.buildUpon().appendPath(fileName).build(); + // + // OutputStream out = new BufferedOutputStream(new FileOutputStream(localFile)); + // + // try { + // // TODO: Alter to fit for google + // Metadata pathMetadata = dbxClient.files().getMetadata(uri.getPath()); + // + // if (pathMetadata instanceof FileMetadata) { + // FileMetadata metadata = (FileMetadata) pathMetadata; + // + // String rev = metadata.getRev(); + // long mtime = metadata.getServerModified().getTime(); + // + // dbxClient.files().download(metadata.getPathLower(), rev).download(out); + // + // return new VersionedRook(repoId, RepoType.DROPBOX, repoUri, uri, rev, mtime); + // + // } else { + // throw new IOException("Failed downloading Dropbox file " + uri + ": Not a file"); + // } + // + // } catch (DbxException e) { + // if (e.getMessage() != null) { + // throw new IOException("Failed downloading Dropbox file " + uri + ": " + e.getMessage()); + // } else { + // throw new IOException("Failed downloading Dropbox file " + uri + ": " + e.toString()); + // } + // } finally { + // out.close(); + // } + } + + + /** Upload file to Dropbox. */ + public VersionedRook upload(File file, Uri repoUri, String fileName) throws IOException { + return null; + // linkedOrThrow(); + // + // Uri bookUri = repoUri.buildUpon().appendPath(fileName).build(); + // + // if (file.length() > UPLOAD_FILE_SIZE_LIMIT * 1024 * 1024) { + // throw new IOException(LARGE_FILE); + // } + // + // FileMetadata metadata; + // InputStream in = new FileInputStream(file); + // + // try { + // // TODO: Alter to fit for google + // metadata = dbxClient.files() + // .uploadBuilder(bookUri.getPath()) + // .withMode(WriteMode.OVERWRITE) + // .uploadAndFinish(in); + // + // } catch (DbxException e) { + // if (e.getMessage() != null) { + // throw new IOException("Failed overwriting " + bookUri.getPath() + " on Dropbox: " + e.getMessage()); + // } else { + // throw new IOException("Failed overwriting " + bookUri.getPath() + " on Dropbox: " + e.toString()); + // } + // } + // + // String rev = metadata.getRev(); + // long mtime = metadata.getServerModified().getTime(); + // + // return new VersionedRook(repoId, RepoType.DROPBOX, repoUri, bookUri, rev, mtime); + } + + public void delete(String path) throws IOException { + // linkedOrThrow(); + // + // try { + // // TODO: Alter to fit for google + // if (dbxClient.files().getMetadata(path) instanceof FileMetadata) { + // dbxClient.files().deleteV2(path); + // } else { + // throw new IOException("Not a file: " + path); + // } + // + // } catch (DbxException e) { + // e.printStackTrace(); + // + // if (e.getMessage() != null) { + // throw new IOException("Failed deleting " + path + " on Dropbox: " + e.getMessage()); + // } else { + // throw new IOException("Failed deleting " + path + " on Dropbox: " + e.toString()); + // } + // } + } + + public VersionedRook move(Uri repoUri, Uri from, Uri to) throws IOException { + return null; + // linkedOrThrow(); + // + // try { + // // TODO: Alter to fit for google + // RelocationResult relocationRes = dbxClient.files().moveV2(from.getPath(), to.getPath()); + // Metadata metadata = relocationRes.getMetadata(); + // + // if (! (metadata instanceof FileMetadata)) { + // throw new IOException("Relocated object not a file?"); + // } + // + // FileMetadata fileMetadata = (FileMetadata) metadata; + // + // String rev = fileMetadata.getRev(); + // long mtime = fileMetadata.getServerModified().getTime(); + // + // return new VersionedRook(repoId, RepoType.DROPBOX, repoUri, to, rev, mtime); + // + // } catch (Exception e) { + // e.printStackTrace(); + // + // if (e.getMessage() != null) { // TODO: Move this throwing to utils + // throw new IOException("Failed moving " + from + " to " + to + ": " + e.getMessage(), e); + // } else { + // throw new IOException("Failed moving " + from + " to " + to + ": " + e.toString(), e); + // } + // } + } +} diff --git a/app/src/main/java/com/orgzly/android/repos/GoogleDriveRepo.java b/app/src/main/java/com/orgzly/android/repos/GoogleDriveRepo.java new file mode 100644 index 000000000..ef3d69cfe --- /dev/null +++ b/app/src/main/java/com/orgzly/android/repos/GoogleDriveRepo.java @@ -0,0 +1,69 @@ +package com.orgzly.android.repos; + +import android.content.Context; +import android.net.Uri; + +import com.orgzly.android.util.UriUtils; + +import java.io.File; +import java.io.IOException; + +import java.util.List; + +public class GoogleDriveRepo implements SyncRepo { + public static final String SCHEME = "googledrive"; + + private final Uri repoUri; + private final GoogleDriveClient client; + + public GoogleDriveRepo(RepoWithProps repoWithProps, Context context) { + this.repoUri = Uri.parse(repoWithProps.getRepo().getUrl()); + this.client = new GoogleDriveClient(context, repoWithProps.getRepo().getId()); + } + + @Override + public boolean isConnectionRequired() { + return true; + } + + @Override + public boolean isAutoSyncSupported() { + return false; + } + + @Override + public Uri getUri() { + return repoUri; + } + + @Override + public List getBooks() throws IOException { + return client.getBooks(repoUri); + } + + @Override + public VersionedRook retrieveBook(String fileName, File file) throws IOException { + return client.download(repoUri, fileName, file); + } + + @Override + public VersionedRook storeBook(File file, String fileName) throws IOException { + return client.upload(file, repoUri, fileName); + } + + @Override + public VersionedRook renameBook(Uri fromUri, String name) throws IOException { + Uri toUri = UriUtils.getUriForNewName(fromUri, name); + return client.move(repoUri, fromUri, toUri); + } + + @Override + public void delete(Uri uri) throws IOException { + client.delete(uri.getPath()); + } + + @Override + public String toString() { + return repoUri.toString(); + } +} diff --git a/app/src/main/java/com/orgzly/android/repos/RepoFactory.kt b/app/src/main/java/com/orgzly/android/repos/RepoFactory.kt index 14158df52..e69754820 100644 --- a/app/src/main/java/com/orgzly/android/repos/RepoFactory.kt +++ b/app/src/main/java/com/orgzly/android/repos/RepoFactory.kt @@ -21,6 +21,9 @@ class RepoFactory @Inject constructor( type == RepoType.DROPBOX.id && BuildConfig.IS_DROPBOX_ENABLED -> DropboxRepo(repoWithProps, context) + type == RepoType.GOOGLE_DRIVE.id && BuildConfig.IS_GOOGLE_DRIVE_ENABLED -> + GoogleDriveRepo(repoWithProps, context) + type == RepoType.DIRECTORY.id -> DirectoryRepo(repoWithProps, false) diff --git a/app/src/main/java/com/orgzly/android/repos/RepoType.kt b/app/src/main/java/com/orgzly/android/repos/RepoType.kt index e72cd0d49..a631808f7 100644 --- a/app/src/main/java/com/orgzly/android/repos/RepoType.kt +++ b/app/src/main/java/com/orgzly/android/repos/RepoType.kt @@ -5,10 +5,11 @@ import java.lang.IllegalArgumentException enum class RepoType(val id: Int) { MOCK(1), DROPBOX(2), - DIRECTORY(3), - DOCUMENT(4), - WEBDAV(5), - GIT(6); + GOOGLE_DRIVE(3), + DIRECTORY(4), + DOCUMENT(5), + WEBDAV(6), + GIT(7); companion object { @JvmStatic @@ -16,13 +17,14 @@ enum class RepoType(val id: Int) { return when (type) { 1 -> MOCK 2 -> DROPBOX - 3 -> DIRECTORY - 4 -> DOCUMENT - 5 -> WEBDAV - 6 -> GIT + 3 -> GOOGLE_DRIVE + 4 -> DIRECTORY + 5 -> DOCUMENT + 6 -> WEBDAV + 7 -> GIT else -> throw IllegalArgumentException("Unknown repo type id $type") } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/orgzly/android/ui/repo/googledrive/GoogleDriveRepoActivity.kt b/app/src/main/java/com/orgzly/android/ui/repo/googledrive/GoogleDriveRepoActivity.kt new file mode 100644 index 000000000..13e69bfbe --- /dev/null +++ b/app/src/main/java/com/orgzly/android/ui/repo/googledrive/GoogleDriveRepoActivity.kt @@ -0,0 +1,388 @@ +package com.orgzly.android.ui.repo.googledrive + +import android.annotation.SuppressLint +import android.app.Activity +import android.app.AlertDialog +import android.content.DialogInterface +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.text.TextUtils +import android.view.Menu +import android.view.MenuItem +import android.widget.EditText +import androidx.databinding.DataBindingUtil +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProviders +import com.orgzly.BuildConfig +import com.orgzly.R +import com.orgzly.android.App +import com.orgzly.android.repos.GoogleDriveClient +import com.orgzly.android.repos.GoogleDriveRepo +import com.orgzly.android.repos.RepoFactory +import com.orgzly.android.repos.RepoType +import com.orgzly.android.ui.CommonActivity +import com.orgzly.android.ui.repo.RepoViewModel +import com.orgzly.android.ui.repo.RepoViewModelFactory +import com.orgzly.android.ui.util.ActivityUtils +import com.orgzly.android.ui.util.styledAttributes +import com.orgzly.android.util.LogUtils +import com.orgzly.android.util.MiscUtils +import com.orgzly.android.util.UriUtils +import com.orgzly.databinding.ActivityRepoGoogleDriveBinding +import javax.inject.Inject + +import com.google.android.gms.auth.api.signin.GoogleSignIn; +import com.google.android.gms.auth.api.signin.GoogleSignInClient; +import com.google.android.gms.auth.api.signin.GoogleSignInOptions; +import com.google.android.gms.common.api.Scope; +import com.google.android.gms.tasks.Task; +import com.google.android.gms.tasks.OnCompleteListener; + +import com.google.api.client.extensions.android.http.AndroidHttp; +import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential; +import com.google.api.client.json.gson.GsonFactory; +import com.google.api.services.drive.Drive; +import com.google.api.services.drive.DriveScopes; + +import androidx.annotation.NonNull; +import java.util.Collections; + +import android.util.Log; + +class GoogleDriveRepoActivity : CommonActivity() { + private lateinit var binding: ActivityRepoGoogleDriveBinding + + @Inject + lateinit var repoFactory: RepoFactory + + private lateinit var client: GoogleDriveClient + + private val REQUEST_CODE_SIGN_IN = 1 + + private lateinit var gsiClient: GoogleSignInClient + + private lateinit var viewModel: RepoViewModel + + override fun onCreate(savedInstanceState: Bundle?) { + App.appComponent.inject(this) + + super.onCreate(savedInstanceState) + + binding = DataBindingUtil.setContentView(this, R.layout.activity_repo_google_drive) + + setupActionBar(R.string.google_drive) + + /* Google Drive link / unlink button. */ + binding.activityRepoGoogleDriveLinkButton.setOnClickListener { + if (isGoogleDriveLinked()) { + toggleLinkAfterConfirmation() + } else { + toggleLink() + } + } + + // binding.activityRepoGoogleDriveLinkButton.setOnLongClickListener { + // editAccessToken() + // true + // } + + // Not working when done in XML + binding.activityRepoGoogleDriveDirectory.apply { + setHorizontallyScrolling(false) + + maxLines = 3 + + setOnEditorActionListener { _, _, _ -> + saveAndFinish() + finish() + true + } + } + + val repoId = intent.getLongExtra(ARG_REPO_ID, 0) + + val factory = RepoViewModelFactory.getInstance(dataRepository, repoId) + + viewModel = ViewModelProviders.of(this, factory).get(RepoViewModel::class.java) + + if (viewModel.repoId != 0L) { // Editing existing + viewModel.loadRepoProperties()?.let { repoWithProps -> + val path = Uri.parse(repoWithProps.repo.url).path + + binding.activityRepoGoogleDriveDirectory.setText(path) + } + } + + viewModel.finishEvent.observeSingle(this, Observer { + finish() + }) + + viewModel.alreadyExistsEvent.observeSingle(this, Observer { + showSnackbar(R.string.repository_url_already_exists) + }) + + viewModel.errorEvent.observeSingle(this, Observer { error -> + if (error != null) { + showSnackbar((error.cause ?: error).localizedMessage) + } + }) + + MiscUtils.clearErrorOnTextChange( + binding.activityRepoGoogleDriveDirectory, + binding.activityRepoGoogleDriveDirectoryInputLayout) + + ActivityUtils.openSoftKeyboardWithDelay(this, binding.activityRepoGoogleDriveDirectory) + + client = GoogleDriveClient(this, repoId) + + createSignInClient() + } + + fun createSignInClient() { + Log.d(TAG, "Creating sign-in client") + val signInOptions = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) + .requestEmail() + .requestScopes(Scope(DriveScopes.DRIVE)) + .build() + gsiClient = GoogleSignIn.getClient(this, signInOptions) + } + + override fun onActivityResult(requestCode:Int, resultCode:Int, resultData:Intent?) { + super.onActivityResult(requestCode, resultCode, resultData) + // Result returned from launching the Intent from GoogleSignInClient.getSignInIntent(...); + handleSignInResult(requestCode, resultData) + } + + fun handleSignInResult(requestCode:Int, result:Intent?) { + if (requestCode == REQUEST_CODE_SIGN_IN) + { + GoogleSignIn.getSignedInAccountFromIntent(result) + .addOnSuccessListener({ googleAccount-> + Log.d(TAG, "Signed in as " + googleAccount.getEmail()) + // Use the authenticated account to sign in to the Drive service. + val credential = GoogleAccountCredential.usingOAuth2( + this, Collections.singleton(DriveScopes.DRIVE)) + credential.setSelectedAccount(googleAccount.getAccount()) + val googleDriveService = Drive.Builder( + AndroidHttp.newCompatibleTransport(), + GsonFactory(), + credential) + .setApplicationName("Orgzly") + .build() + // The DriveServiceHelper encapsulates all REST API and SAF functionality. + // Its instantiation is required before handling any onClick actions. + client.setService(googleDriveService); + showSnackbar(R.string.message_google_drive_linked) + }) + .addOnFailureListener({ exception-> Log.d(TAG, "Unable to sign in." + exception) }) + } + } + + // Token stuff is handled by Google sign-in + // private fun editAccessToken() { + // @SuppressLint("InflateParams") + // val view = layoutInflater.inflate(R.layout.dialog_simple_one_liner, null, false) + // + // val editView = view.findViewById(R.id.dialog_input).apply { + // setSelectAllOnFocus(true) + // + // setHint(R.string.access_token) + // + // client.token?.let { + // setText(it) + // } + // } + // + // alertDialog = AlertDialog.Builder(this) + // .setView(view) + // .setTitle(R.string.access_token) + // .setPositiveButton(R.string.set) { _, _ -> + // editView.text.toString().let { value -> + // if (TextUtils.isEmpty(value)) { + // client.unlink(this) + // } else { + // client.setToken(value) + // } + // } + // updateGoogleDriveLinkUnlinkButton() + // } + // .setNeutralButton(R.string.clear) { _, _ -> + // client.unlink(this) + // updateGoogleDriveLinkUnlinkButton() + // } + // .setNegativeButton(R.string.cancel) { _, _ -> } + // .create().apply { + // setOnShowListener { + // ActivityUtils.openSoftKeyboard(this@GoogleDriveRepoActivity, editView) + // } + // + // show() + // } + // } + + public override fun onResume() { + super.onResume() + + updateGoogleDriveLinkUnlinkButton() + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + super.onCreateOptionsMenu(menu) + + menuInflater.inflate(R.menu.done, menu) + + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.done -> { + saveAndFinish() + true + } + + android.R.id.home -> { + finish() + true + } + + else -> + super.onOptionsItemSelected(item) + } + } + + private fun saveAndFinish() { + val directory = binding.activityRepoGoogleDriveDirectory.text.toString().trim { it <= ' ' } + + if (TextUtils.isEmpty(directory)) { + binding.activityRepoGoogleDriveDirectoryInputLayout.error = getString(R.string.can_not_be_empty) + return + } else { + binding.activityRepoGoogleDriveDirectoryInputLayout.error = null + } + + val url = UriUtils.uriFromPath(GoogleDriveRepo.SCHEME, directory).toString() + + val repo = try { + viewModel.validate(RepoType.GOOGLE_DRIVE, url) + } catch (e: Exception) { + e.printStackTrace() + binding.activityRepoGoogleDriveDirectoryInputLayout.error = + getString(R.string.repository_not_valid_with_reason, e.message) + return + } + + viewModel.saveRepo(RepoType.GOOGLE_DRIVE, repo.uri.toString()) + } + + private fun toggleLinkAfterConfirmation() { + val dialogClickListener = DialogInterface.OnClickListener { _, which -> + if (which == DialogInterface.BUTTON_POSITIVE) { + toggleLink() + } + } + + alertDialog = AlertDialog.Builder(this) + .setTitle(R.string.confirm_unlinking_from_google_drive_title) + .setMessage(R.string.confirm_unlinking_from_google_drive_message) + .setPositiveButton(R.string.unlink, dialogClickListener) + .setNegativeButton(R.string.cancel, dialogClickListener) + .show() + } + + private fun toggleLink() { + if (onGoogleDriveLinkToggleRequest()) { // Unlinked + updateGoogleDriveLinkUnlinkButton() + } // Else - Linking process started - button should stay the same. + } + + /** + * Toggle Google Drive link. Link to Google Drive or unlink from it, depending on current state. + * + * @return true if there was a change (Google Drive has been unlinked). + */ + private fun onGoogleDriveLinkToggleRequest(): Boolean { + return if (isGoogleDriveLinked()) { + unlinkGoogleDrive() + showSnackbar(R.string.message_google_drive_unlinked) + true + + } else { + linkGoogleDrive() + false + } + } + + // /** + // * Complete Google Drive linking. + // * After starting Google Drive authentication, user will return to activity. + // * We need to finish the process of authentication. + // */ + // private fun googleDriveCompleteAuthentication() { + // if (!isGoogleDriveLinked()) { + // if (client.finishAuthentication()) { + // showSnackbar(R.string.message_google_drive_linked) + // } + // } else { + // showSnackbar(R.string.message_google_drive_linked) + // } + // } + + private fun updateGoogleDriveLinkUnlinkButton() { + if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG) + + val resources = styledAttributes(R.styleable.Icons) { typedArray -> + if (isGoogleDriveLinked()) { + Pair( + getString(R.string.repo_google_drive_button_linked), + typedArray.getResourceId(R.styleable.Icons_oic_dropbox_linked, 0)) + } else { + Pair( + getString(R.string.repo_google_drive_button_not_linked), + typedArray.getResourceId(R.styleable.Icons_oic_dropbox_not_linked, 0)) + } + } + + binding.activityRepoGoogleDriveLinkButton.text = resources.first + + if (resources.second != 0) { + binding.activityRepoGoogleDriveIcon.setImageResource(resources.second) + } + } + + private fun isGoogleDriveLinked(): Boolean { + // Check for existing Google Sign In account, if the user is already signed in + // the GoogleSignInAccount will be non-null. + return GoogleSignIn.getLastSignedInAccount(this) != null + } + + private fun linkGoogleDrive() { + startActivityForResult(gsiClient.getSignInIntent(), REQUEST_CODE_SIGN_IN) + } + + private fun unlinkGoogleDrive() { + gsiClient.revokeAccess() + .addOnCompleteListener(this, OnCompleteListener() { + fun onComplete(@NonNull task:Task) { + Log.d(TAG, "Signed out") + } + }) + } + + companion object { + private val TAG: String = GoogleDriveRepoActivity::class.java.name + + private const val ARG_REPO_ID = "repo_id" + + @JvmStatic + @JvmOverloads + fun start(activity: Activity, repoId: Long = 0) { + val intent = Intent(Intent.ACTION_VIEW) + .setClass(activity, GoogleDriveRepoActivity::class.java) + .putExtra(ARG_REPO_ID, repoId) + + activity.startActivity(intent) + } + } +} diff --git a/app/src/main/java/com/orgzly/android/ui/repos/ReposActivity.kt b/app/src/main/java/com/orgzly/android/ui/repos/ReposActivity.kt index 826ee8d9f..ac8fcd0a9 100644 --- a/app/src/main/java/com/orgzly/android/ui/repos/ReposActivity.kt +++ b/app/src/main/java/com/orgzly/android/ui/repos/ReposActivity.kt @@ -22,6 +22,7 @@ import com.orgzly.android.repos.* import com.orgzly.android.ui.CommonActivity import com.orgzly.android.ui.repo.directory.DirectoryRepoActivity import com.orgzly.android.ui.repo.dropbox.DropboxRepoActivity +import com.orgzly.android.ui.repo.googledrive.GoogleDriveRepoActivity import com.orgzly.android.ui.repo.git.GitRepoActivity import com.orgzly.android.ui.repo.webdav.WebdavRepoActivity import com.orgzly.android.util.LogUtils @@ -106,6 +107,16 @@ class ReposActivity : CommonActivity(), AdapterView.OnItemClickListener, Activit } } + binding.activityReposGoogleDrive.let { button -> + if (BuildConfig.IS_GOOGLE_DRIVE_ENABLED) { + button.setOnClickListener { + startRepoActivity(R.id.repos_options_menu_item_new_google_drive) + } + } else { + button.visibility = View.GONE + } + } + binding.activityReposGit.let { button -> if (BuildConfig.IS_GIT_ENABLED) { button.setOnClickListener { @@ -157,6 +168,10 @@ class ReposActivity : CommonActivity(), AdapterView.OnItemClickListener, Activit newRepos.removeItem(R.id.repos_options_menu_item_new_dropbox) } + if (!BuildConfig.IS_GOOGLE_DRIVE_ENABLED) { + newRepos.removeItem(R.id.repos_options_menu_item_new_google_drive) + } + if (!BuildConfig.IS_GIT_ENABLED) { newRepos.removeItem(R.id.repos_options_menu_item_new_git) } @@ -172,6 +187,11 @@ class ReposActivity : CommonActivity(), AdapterView.OnItemClickListener, Activit return true } + R.id.repos_options_menu_item_new_google_drive -> { + startRepoActivity(item.itemId) + return true + } + R.id.repos_options_menu_item_new_git -> { startRepoActivity(item.itemId) return true @@ -216,6 +236,11 @@ class ReposActivity : CommonActivity(), AdapterView.OnItemClickListener, Activit return } + R.id.repos_options_menu_item_new_google_drive -> { + GoogleDriveRepoActivity.start(this) + return + } + R.id.repos_options_menu_item_new_git -> { if (ContextCompat.checkSelfPermission(this, READ_WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { GitRepoActivity.start(this) @@ -257,6 +282,9 @@ class ReposActivity : CommonActivity(), AdapterView.OnItemClickListener, Activit RepoType.DROPBOX -> DropboxRepoActivity.start(this, repoEntity.id) + RepoType.GOOGLE_DRIVE -> + GoogleDriveRepoActivity.start(this, repoEntity.id) + RepoType.DIRECTORY -> DirectoryRepoActivity.start(this, repoEntity.id) diff --git a/app/src/main/res/layout/activity_repo_dropbox.xml b/app/src/main/res/layout/activity_repo_dropbox.xml index 89d7baa7c..51b989d3c 100644 --- a/app/src/main/res/layout/activity_repo_dropbox.xml +++ b/app/src/main/res/layout/activity_repo_dropbox.xml @@ -72,4 +72,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/layout/activity_repo_google_drive.xml b/app/src/main/res/layout/activity_repo_google_drive.xml new file mode 100644 index 000000000..2ec891e16 --- /dev/null +++ b/app/src/main/res/layout/activity_repo_google_drive.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + +