1
0
mirror of https://github.com/TwidereProject/Twidere-Android synced 2025-02-07 15:28:51 +01:00

adding google drive sync

This commit is contained in:
Mariotaku Lee 2017-01-19 19:43:20 +08:00
parent a97edf5571
commit dcd6c81bcd
17 changed files with 406 additions and 111 deletions

View File

@ -98,6 +98,7 @@ dependencies {
// START Non-FOSS component // START Non-FOSS component
googleCompile "com.google.android.gms:play-services-maps:$play_services_version" googleCompile "com.google.android.gms:play-services-maps:$play_services_version"
googleCompile "com.google.android.gms:play-services-auth:$play_services_version" googleCompile "com.google.android.gms:play-services-auth:$play_services_version"
googleCompile "com.google.android.gms:play-services-drive:$play_services_version"
googleCompile 'com.google.maps.android:android-maps-utils:0.4.4' googleCompile 'com.google.maps.android:android-maps-utils:0.4.4'
googleCompile('com.crashlytics.sdk.android:crashlytics:2.6.5@aar') { transitive = true } googleCompile('com.crashlytics.sdk.android:crashlytics:2.6.5@aar') { transitive = true }
googleCompile 'com.anjlab.android.iab.v3:library:1.0.38' googleCompile 'com.anjlab.android.iab.v3:library:1.0.38'

View File

@ -31,7 +31,10 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name=".activity.DropboxAuthStarterActivity" android:name=".activity.sync.DropboxAuthStarterActivity"
android:theme="@style/Theme.Twidere.NoDisplay"/>
<activity
android:name=".activity.sync.GoogleDriveAuthActivity"
android:theme="@style/Theme.Twidere.NoDisplay"/> android:theme="@style/Theme.Twidere.NoDisplay"/>
<activity <activity
android:name="com.dropbox.core.android.AuthActivity" android:name="com.dropbox.core.android.AuthActivity"

View File

@ -1,9 +1,10 @@
package org.mariotaku.twidere.activity package org.mariotaku.twidere.activity.sync
import android.os.Bundle import android.os.Bundle
import com.dropbox.core.android.Auth import com.dropbox.core.android.Auth
import org.mariotaku.kpreferences.set import org.mariotaku.kpreferences.set
import org.mariotaku.twidere.Constants.DROPBOX_APP_KEY import org.mariotaku.twidere.Constants.DROPBOX_APP_KEY
import org.mariotaku.twidere.activity.BaseActivity
import org.mariotaku.twidere.constant.dataSyncProviderInfoKey import org.mariotaku.twidere.constant.dataSyncProviderInfoKey
import org.mariotaku.twidere.model.sync.DropboxSyncProviderInfo import org.mariotaku.twidere.model.sync.DropboxSyncProviderInfo

View File

@ -0,0 +1,79 @@
package org.mariotaku.twidere.activity.sync
import android.app.Activity
import android.content.Intent
import android.content.IntentSender
import android.os.Bundle
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GooglePlayServicesUtil
import com.google.android.gms.common.api.GoogleApiClient
import com.google.android.gms.drive.Drive
import org.mariotaku.kpreferences.set
import org.mariotaku.twidere.activity.BaseActivity
import org.mariotaku.twidere.constant.dataSyncProviderInfoKey
import org.mariotaku.twidere.model.sync.GoogleDriveSyncProviderInfo
class GoogleDriveAuthActivity : BaseActivity(), GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener {
private lateinit var googleApiClient: GoogleApiClient
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
googleApiClient = GoogleApiClient.Builder(this)
.addApi(Drive.API)
.addScope(Drive.SCOPE_APPFOLDER)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build()
}
override fun onStart() {
super.onStart()
googleApiClient.connect()
}
override fun onStop() {
googleApiClient.disconnect()
super.onStop()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
when (requestCode) {
RESOLVE_CONNECTION_REQUEST_CODE -> if (resultCode == Activity.RESULT_OK) {
googleApiClient.connect()
}
}
}
override fun onConnectionFailed(connectionResult: ConnectionResult) {
if (connectionResult.hasResolution()) {
try {
connectionResult.startResolutionForResult(this, RESOLVE_CONNECTION_REQUEST_CODE)
} catch (e: IntentSender.SendIntentException) {
// Unable to resolve, message user appropriately
}
} else {
preferences[dataSyncProviderInfoKey] = null
GooglePlayServicesUtil.showErrorDialogFragment(connectionResult.errorCode, this, null, 0) {
finish()
}
}
}
override fun onConnected(connectionHint: Bundle?) {
preferences[dataSyncProviderInfoKey] = GoogleDriveSyncProviderInfo()
finish()
}
override fun onConnectionSuspended(cause: Int) {
finish()
}
companion object {
private const val RESOLVE_CONNECTION_REQUEST_CODE: Int = 101
}
}

View File

@ -0,0 +1,24 @@
package org.mariotaku.twidere.model.sync
import android.content.Context
import android.content.SharedPreferences
import org.mariotaku.twidere.util.sync.SyncTaskRunner
import org.mariotaku.twidere.util.sync.google.GoogleDriveSyncTaskRunner
class GoogleDriveSyncProviderInfo : SyncProviderInfo(GoogleDriveSyncProviderInfo.TYPE) {
override fun writeToPreferences(editor: SharedPreferences.Editor) {
}
override fun newSyncTaskRunner(context: Context): SyncTaskRunner {
return GoogleDriveSyncTaskRunner(context)
}
companion object {
const val TYPE = "google_drive"
fun newInstance(preferences: SharedPreferences): GoogleDriveSyncProviderInfo? {
return GoogleDriveSyncProviderInfo()
}
}
}

View File

@ -1,11 +1,14 @@
package org.mariotaku.twidere.util.sync package org.mariotaku.twidere.util.sync
import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import org.mariotaku.twidere.R import org.mariotaku.twidere.R
import org.mariotaku.twidere.activity.DropboxAuthStarterActivity import org.mariotaku.twidere.activity.sync.DropboxAuthStarterActivity
import org.mariotaku.twidere.activity.sync.GoogleDriveAuthActivity
import org.mariotaku.twidere.model.sync.DropboxSyncProviderInfo import org.mariotaku.twidere.model.sync.DropboxSyncProviderInfo
import org.mariotaku.twidere.model.sync.GoogleDriveSyncProviderInfo
import org.mariotaku.twidere.model.sync.SyncProviderEntry import org.mariotaku.twidere.model.sync.SyncProviderEntry
import org.mariotaku.twidere.model.sync.SyncProviderInfo import org.mariotaku.twidere.model.sync.SyncProviderInfo
@ -17,6 +20,7 @@ class NonFreeSyncProviderInfoFactory : SyncProviderInfoFactory() {
override fun getInfoForType(type: String, preferences: SharedPreferences): SyncProviderInfo? { override fun getInfoForType(type: String, preferences: SharedPreferences): SyncProviderInfo? {
return when (type) { return when (type) {
DropboxSyncProviderInfo.TYPE -> DropboxSyncProviderInfo.newInstance(preferences) DropboxSyncProviderInfo.TYPE -> DropboxSyncProviderInfo.newInstance(preferences)
GoogleDriveSyncProviderInfo.TYPE -> GoogleDriveSyncProviderInfo.newInstance(preferences)
else -> null else -> null
} }
} }
@ -25,7 +29,11 @@ class NonFreeSyncProviderInfoFactory : SyncProviderInfoFactory() {
return listOf( return listOf(
SyncProviderEntry(DropboxSyncProviderInfo.TYPE, SyncProviderEntry(DropboxSyncProviderInfo.TYPE,
context.getString(R.string.sync_provider_name_dropbox), context.getString(R.string.sync_provider_name_dropbox),
Intent(context, DropboxAuthStarterActivity::class.java)) Intent(context, DropboxAuthStarterActivity::class.java)),
SyncProviderEntry(GoogleDriveSyncProviderInfo.TYPE,
context.getString(R.string.sync_provider_name_google_drive),
Intent(context, GoogleDriveAuthActivity::class.java))
) )
} }
} }

View File

@ -15,7 +15,10 @@ import org.mariotaku.twidere.util.sync.FileBasedDraftsSyncAction
import java.io.IOException import java.io.IOException
import java.util.* import java.util.*
class DropboxDraftsSyncAction(context: Context, val client: DbxClientV2) : FileBasedDraftsSyncAction<FileMetadata>(context) { class DropboxDraftsSyncAction(
context: Context,
val client: DbxClientV2
) : FileBasedDraftsSyncAction<FileMetadata>(context) {
@Throws(IOException::class) @Throws(IOException::class)
override fun Draft.saveToRemote(): FileMetadata { override fun Draft.saveToRemote(): FileMetadata {
try { try {

View File

@ -0,0 +1,96 @@
package org.mariotaku.twidere.util.sync.google
import android.content.Context
import com.google.android.gms.common.api.GoogleApiClient
import com.google.android.gms.drive.Drive
import com.google.android.gms.drive.DriveFile
import com.google.android.gms.drive.DriveId
import com.google.android.gms.drive.MetadataChangeSet
import org.mariotaku.twidere.extension.model.filename
import org.mariotaku.twidere.extension.model.readMimeMessageFrom
import org.mariotaku.twidere.extension.model.writeMimeMessageTo
import org.mariotaku.twidere.model.Draft
import org.mariotaku.twidere.util.sync.FileBasedDraftsSyncAction
import java.io.IOException
import java.util.*
class GoogleDriveDraftsSyncAction(
context: Context,
val client: GoogleApiClient
) : FileBasedDraftsSyncAction<GoogleDriveDraftsSyncAction.DriveFileInfo>(context) {
@Throws(IOException::class)
override fun Draft.saveToRemote(): DriveFileInfo {
try {
val folder = Drive.DriveApi.getAppFolder(client)
val driveContents = Drive.DriveApi.newDriveContents(client).await().driveContents
this.writeMimeMessageTo(context, driveContents.outputStream)
val filename = "/Drafts/$filename"
val changeSet = MetadataChangeSet.Builder().setTitle(filename).build()
val driveFile = folder.createFile(client, changeSet, driveContents).await().driveFile
return DriveFileInfo(driveFile.driveId, filename, Date())
} catch (e: Exception) {
throw IOException(e)
}
}
@Throws(IOException::class)
override fun Draft.loadFromRemote(info: DriveFileInfo): Boolean {
try {
val file = info.driveId.asDriveFile()
val result = file.open(client, DriveFile.MODE_READ_ONLY, null).await()
result.driveContents.inputStream.use {
val parsed = this.readMimeMessageFrom(context, it)
if (parsed) {
this.timestamp = info.draftTimestamp
this.unique_id = info.draftFileName.substringBeforeLast(".eml")
}
return parsed
}
} catch (e: Exception) {
throw IOException(e)
}
}
@Throws(IOException::class)
override fun removeDrafts(list: List<DriveFileInfo>): Boolean {
try {
list.forEach { info ->
info.driveId.asDriveFile().delete(client).await()
}
return true
} catch (e: Exception) {
throw IOException(e)
}
}
@Throws(IOException::class)
override fun removeDraft(info: DriveFileInfo): Boolean {
try {
return info.driveId.asDriveFile().delete(client).await().isSuccess
} catch (e: Exception) {
throw IOException(e)
}
}
override val DriveFileInfo.draftTimestamp: Long get() = this.modifiedDate.time
override val DriveFileInfo.draftFileName: String get() = this.name
@Throws(IOException::class)
override fun listRemoteDrafts(): List<DriveFileInfo> {
val pendingResult = Drive.DriveApi.getAppFolder(client).listChildren(client)
val result = ArrayList<DriveFileInfo>()
try {
val requestResult = pendingResult.await()
requestResult.metadataBuffer.mapTo(result) { metadata ->
DriveFileInfo(metadata.driveId, metadata.originalFilename, metadata.modifiedDate)
}
} catch (e: Exception) {
throw IOException(e)
}
return result
}
data class DriveFileInfo(val driveId: DriveId, val name: String, val modifiedDate: Date)
}

View File

@ -1,11 +1,56 @@
package org.mariotaku.twidere.util.sync.google package org.mariotaku.twidere.util.sync.google
import android.content.ComponentCallbacks
import android.content.Context import android.content.Context
import android.os.Bundle
import com.dropbox.core.DbxRequestConfig
import com.dropbox.core.v2.DbxClientV2
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.api.GoogleApiClient
import com.google.android.gms.drive.Drive
import nl.komponents.kovenant.task
import nl.komponents.kovenant.then
import nl.komponents.kovenant.ui.failUi
import nl.komponents.kovenant.ui.successUi
import org.mariotaku.twidere.BuildConfig
import org.mariotaku.twidere.util.TaskServiceRunner
import org.mariotaku.twidere.util.sync.ISyncAction
import org.mariotaku.twidere.util.sync.SyncTaskRunner
import org.mariotaku.twidere.util.sync.UserColorsSyncProcessor
import org.mariotaku.twidere.util.sync.UserNicknamesSyncProcessor
import org.mariotaku.twidere.util.sync.dropbox.DropboxDraftsSyncAction
import org.mariotaku.twidere.util.sync.dropbox.DropboxFiltersDataSyncAction
import org.mariotaku.twidere.util.sync.dropbox.DropboxPreferencesValuesSyncAction
import java.net.ConnectException
/** /**
* Created by mariotaku on 2017/1/6. * Created by mariotaku on 2017/1/6.
*/ */
class GoogleDriveSyncTaskRunner(context: Context) { class GoogleDriveSyncTaskRunner(context: Context) : SyncTaskRunner(context) {
override fun onRunningTask(action: String, callback: (Boolean) -> Unit): Boolean {
val client = GoogleApiClient.Builder(context)
.addApi(Drive.API)
.addScope(Drive.SCOPE_APPFOLDER)
.build()
val syncAction: ISyncAction = when (action) {
TaskServiceRunner.ACTION_SYNC_DRAFTS -> GoogleDriveDraftsSyncAction(context, client)
else -> null
} ?: return false
task {
val connResult = client.blockingConnect()
if (!connResult.isSuccess) {
throw ConnectException()
}
syncAction.execute()
}.successUi {
callback(true)
}.failUi {
callback(false)
}.always {
client.disconnect()
}
return true
}
} }

View File

@ -477,6 +477,7 @@
<activity <activity
android:name=".activity.PremiumDashboardActivity" android:name=".activity.PremiumDashboardActivity"
android:label="@string/title_premium_features_name" android:label="@string/title_premium_features_name"
android:parentActivityName=".activity.HomeActivity"
android:theme="@style/Theme.Twidere"> android:theme="@style/Theme.Twidere">
<meta-data <meta-data

View File

@ -8,7 +8,8 @@
"same_oauth_url": true, "same_oauth_url": true,
"no_version_suffix": false, "no_version_suffix": false,
"consumer_key": "i5XtSVfoWjLjKlnrvhiLPMZC0", "consumer_key": "i5XtSVfoWjLjKlnrvhiLPMZC0",
"consumer_secret": "sQncmZ2atQR6tKbqnnAtqjrECqN8k6FD4p4OoNS0XTDkUz3HGH" "consumer_secret": "sQncmZ2atQR6tKbqnnAtqjrECqN8k6FD4p4OoNS0XTDkUz3HGH",
"sign_up_url": "https://twitter.com/signup"
}, },
{ {
"name": "Fanfou", "name": "Fanfou",
@ -19,7 +20,8 @@
"same_oauth_url": true, "same_oauth_url": true,
"no_version_suffix": true, "no_version_suffix": true,
"consumer_key": "86d1146dda1d21d59351008a1d1058fd", "consumer_key": "86d1146dda1d21d59351008a1d1058fd",
"consumer_secret": "c00f4b83dbfc52e2ed78a21d4edfc3cc" "consumer_secret": "c00f4b83dbfc52e2ed78a21d4edfc3cc",
"sign_up_url": "http://fanfou.com/register"
}, },
{ {
"name": "Quitter.se", "name": "Quitter.se",
@ -30,7 +32,8 @@
"same_oauth_url": true, "same_oauth_url": true,
"no_version_suffix": true, "no_version_suffix": true,
"consumer_key": "3584cd86bd8e04948be15650acbd0d59", "consumer_key": "3584cd86bd8e04948be15650acbd0d59",
"consumer_secret": "84a39eff81c0a0ecbfba5239d25c0367" "consumer_secret": "84a39eff81c0a0ecbfba5239d25c0367",
"sign_up_url": "https://quitter.se/"
}, },
{ {
"name": "LoadAverage.org", "name": "LoadAverage.org",

View File

@ -55,6 +55,9 @@ public final class CustomAPIConfig implements Parcelable {
String consumerKey; String consumerKey;
@JsonField(name = "consumer_secret") @JsonField(name = "consumer_secret")
String consumerSecret; String consumerSecret;
@Nullable
@JsonField(name = "sign_up_url")
String signUpUrl;
public CustomAPIConfig() { public CustomAPIConfig() {
} }
@ -139,6 +142,11 @@ public final class CustomAPIConfig implements Parcelable {
this.noVersionSuffix = noVersionSuffix; this.noVersionSuffix = noVersionSuffix;
} }
@Nullable
public String getSignUpUrl() {
return signUpUrl;
}
@Override @Override
public int describeContents() { public int describeContents() {
return 0; return 0;

View File

@ -24,6 +24,7 @@ import android.accounts.AccountAuthenticatorResponse
import android.accounts.AccountManager import android.accounts.AccountManager
import android.app.Activity import android.app.Activity
import android.app.Dialog import android.app.Dialog
import android.content.ActivityNotFoundException
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
@ -31,6 +32,7 @@ import android.content.res.ColorStateList
import android.net.Uri import android.net.Uri
import android.os.AsyncTask import android.os.AsyncTask
import android.os.Bundle import android.os.Bundle
import android.support.customtabs.CustomTabsIntent
import android.support.v4.app.DialogFragment import android.support.v4.app.DialogFragment
import android.support.v4.content.ContextCompat import android.support.v4.content.ContextCompat
import android.support.v4.util.ArraySet import android.support.v4.util.ArraySet
@ -52,6 +54,8 @@ import android.widget.Toast
import com.bluelinelabs.logansquare.LoganSquare import com.bluelinelabs.logansquare.LoganSquare
import com.rengwuxian.materialedittext.MaterialEditText import com.rengwuxian.materialedittext.MaterialEditText
import kotlinx.android.synthetic.main.activity_sign_in.* import kotlinx.android.synthetic.main.activity_sign_in.*
import org.mariotaku.chameleon.Chameleon
import org.mariotaku.chameleon.ChameleonUtils
import org.mariotaku.kpreferences.get import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.* import org.mariotaku.ktextension.*
import org.mariotaku.microblog.library.MicroBlog import org.mariotaku.microblog.library.MicroBlog
@ -71,6 +75,7 @@ import org.mariotaku.twidere.activity.iface.APIEditorActivity
import org.mariotaku.twidere.annotation.AccountType import org.mariotaku.twidere.annotation.AccountType
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_API_CONFIG import org.mariotaku.twidere.constant.IntentConstants.EXTRA_API_CONFIG
import org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_CREDENTIALS_TYPE import org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_CREDENTIALS_TYPE
import org.mariotaku.twidere.constant.chromeCustomTabKey
import org.mariotaku.twidere.constant.defaultAPIConfigKey import org.mariotaku.twidere.constant.defaultAPIConfigKey
import org.mariotaku.twidere.constant.randomizeAccountNameKey import org.mariotaku.twidere.constant.randomizeAccountNameKey
import org.mariotaku.twidere.extension.model.getColor import org.mariotaku.twidere.extension.model.getColor
@ -125,7 +130,6 @@ class SignInActivity : BaseActivity(), OnClickListener, TextWatcher {
val isTwipOMode = apiConfig.credentialsType == Credentials.Type.EMPTY val isTwipOMode = apiConfig.credentialsType == Credentials.Type.EMPTY
usernamePasswordContainer.visibility = if (isTwipOMode) View.GONE else View.VISIBLE usernamePasswordContainer.visibility = if (isTwipOMode) View.GONE else View.VISIBLE
signInSignUpContainer.orientation = if (isTwipOMode) LinearLayout.VERTICAL else LinearLayout.HORIZONTAL
editUsername.addTextChangedListener(this) editUsername.addTextChangedListener(this)
editPassword.addTextChangedListener(this) editPassword.addTextChangedListener(this)
@ -208,15 +212,12 @@ class SignInActivity : BaseActivity(), OnClickListener, TextWatcher {
when (apiConfig.credentialsType) { when (apiConfig.credentialsType) {
Credentials.Type.XAUTH, Credentials.Type.BASIC -> { Credentials.Type.XAUTH, Credentials.Type.BASIC -> {
usernamePasswordContainer.visibility = View.VISIBLE usernamePasswordContainer.visibility = View.VISIBLE
signInSignUpContainer.orientation = LinearLayout.HORIZONTAL
} }
Credentials.Type.EMPTY -> { Credentials.Type.EMPTY -> {
usernamePasswordContainer.visibility = View.GONE usernamePasswordContainer.visibility = View.GONE
signInSignUpContainer.orientation = LinearLayout.VERTICAL
} }
else -> { else -> {
usernamePasswordContainer.visibility = View.GONE usernamePasswordContainer.visibility = View.GONE
signInSignUpContainer.orientation = LinearLayout.VERTICAL
} }
} }
} }
@ -224,8 +225,25 @@ class SignInActivity : BaseActivity(), OnClickListener, TextWatcher {
override fun onClick(v: View) { override fun onClick(v: View) {
when (v) { when (v) {
signUp -> { signUp -> {
val intent = Intent(Intent.ACTION_VIEW).setData(Uri.parse(TWITTER_SIGNUP_URL)) val signUpUrl = apiConfig.signUpUrl ?: return
val uri = Uri.parse(signUpUrl)
if (preferences[chromeCustomTabKey]) {
val builder = CustomTabsIntent.Builder()
builder.setToolbarColor(overrideTheme.colorToolbar)
val intent = builder.build()
try {
intent.launchUrl(this, uri)
} catch (e: ActivityNotFoundException) {
// Ignore
}
} else {
val intent = Intent(Intent.ACTION_VIEW, uri)
try {
startActivity(intent) startActivity(intent)
} catch (e: ActivityNotFoundException) {
// Ignore
}
}
} }
signIn -> { signIn -> {
if (usernamePasswordContainer.visibility != View.VISIBLE) { if (usernamePasswordContainer.visibility != View.VISIBLE) {
@ -235,11 +253,9 @@ class SignInActivity : BaseActivity(), OnClickListener, TextWatcher {
doLogin() doLogin()
} }
passwordSignIn -> { passwordSignIn -> {
executeAfterFragmentResumed { executeAfterFragmentResumed { fragment ->
val fm = supportFragmentManager
val df = PasswordSignInDialogFragment() val df = PasswordSignInDialogFragment()
df.show(fm.beginTransaction(), "password_sign_in") df.show(fragment.supportFragmentManager, "password_sign_in")
Unit
} }
} }
} }
@ -377,6 +393,16 @@ class SignInActivity : BaseActivity(), OnClickListener, TextWatcher {
signIn.isEnabled = true signIn.isEnabled = true
} }
} }
signUp.visibility = if (apiConfig.signUpUrl != null) {
View.VISIBLE
} else {
View.GONE
}
passwordSignIn.visibility = if (apiConfig.type == null || apiConfig.type == AccountType.TWITTER) {
View.VISIBLE
} else {
View.GONE
}
} }
internal fun onSignInResult(result: SignInResponse) { internal fun onSignInResult(result: SignInResponse) {

View File

@ -37,6 +37,7 @@ import android.widget.RadioGroup
import android.widget.RadioGroup.OnCheckedChangeListener import android.widget.RadioGroup.OnCheckedChangeListener
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import com.bluelinelabs.logansquare.LoganSquare
import kotlinx.android.synthetic.main.activity_api_editor.* import kotlinx.android.synthetic.main.activity_api_editor.*
import kotlinx.android.synthetic.main.layout_api_editor.* import kotlinx.android.synthetic.main.layout_api_editor.*
import kotlinx.android.synthetic.main.layout_api_editor_advanced_fields.* import kotlinx.android.synthetic.main.layout_api_editor_advanced_fields.*
@ -63,6 +64,38 @@ import javax.inject.Inject
class APIEditorActivity : BaseActivity(), OnCheckedChangeListener, OnClickListener, CompoundButton.OnCheckedChangeListener { class APIEditorActivity : BaseActivity(), OnCheckedChangeListener, OnClickListener, CompoundButton.OnCheckedChangeListener {
private var editNoVersionSuffixChanged: Boolean = false private var editNoVersionSuffixChanged: Boolean = false
private lateinit var apiConfig: CustomAPIConfig
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_api_editor)
if (savedInstanceState != null) {
apiConfig = savedInstanceState.getParcelable(EXTRA_API_CONFIG)
} else {
apiConfig = intent.getParcelableExtra(EXTRA_API_CONFIG) ?: kPreferences[defaultAPIConfigKey]
}
editAuthType.setOnCheckedChangeListener(this)
editNoVersionSuffix.setOnCheckedChangeListener(this)
save.setOnClickListener(this)
apiUrlFormatHelp.setOnClickListener(this)
loadDefaults.visibility = View.VISIBLE
loadDefaults.setOnClickListener(this)
editApiUrlFormat.setText(apiConfig.apiUrlFormat)
editSameOAuthSigningUrl.isChecked = apiConfig.isSameOAuthUrl
editNoVersionSuffix.isChecked = apiConfig.isNoVersionSuffix
editConsumerKey.setText(apiConfig.consumerKey)
editConsumerSecret.setText(apiConfig.consumerSecret)
editAuthType.check(getAuthTypeId(apiConfig.credentialsType))
if (editAuthType.checkedRadioButtonId == -1) {
oauth.isChecked = true
}
}
override fun onCheckedChanged(group: RadioGroup, checkedId: Int) { override fun onCheckedChanged(group: RadioGroup, checkedId: Int) {
val authType = getCheckedAuthType(checkedId) val authType = getCheckedAuthType(checkedId)
val isOAuth = authType == Credentials.Type.OAUTH || authType == Credentials.Type.XAUTH val isOAuth = authType == Credentials.Type.OAUTH || authType == Credentials.Type.XAUTH
@ -97,10 +130,6 @@ class APIEditorActivity : BaseActivity(), OnCheckedChangeListener, OnClickListen
} }
} }
private fun checkApiUrl(): Boolean {
return MicroBlogAPIFactory.verifyApiFormat(editApiUrlFormat.text.toString())
}
public override fun onSaveInstanceState(outState: Bundle) { public override fun onSaveInstanceState(outState: Bundle) {
outState.putParcelable(EXTRA_API_CONFIG, createCustomAPIConfig()) outState.putParcelable(EXTRA_API_CONFIG, createCustomAPIConfig())
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
@ -113,8 +142,22 @@ class APIEditorActivity : BaseActivity(), OnCheckedChangeListener, OnClickListen
finish() finish()
} }
private fun checkApiUrl(): Boolean {
return MicroBlogAPIFactory.verifyApiFormat(editApiUrlFormat.text.toString())
}
private fun applyApiConfig() {
editApiUrlFormat.setText(apiConfig.apiUrlFormat)
editAuthType.check(getAuthTypeId(apiConfig.credentialsType))
editSameOAuthSigningUrl.isChecked = apiConfig.isSameOAuthUrl
editNoVersionSuffix.isChecked = apiConfig.isNoVersionSuffix
editConsumerKey.setText(apiConfig.consumerKey)
editConsumerSecret.setText(apiConfig.consumerSecret)
}
private fun createCustomAPIConfig(): CustomAPIConfig { private fun createCustomAPIConfig(): CustomAPIConfig {
return CustomAPIConfig().apply { return apiConfig.apply {
this.apiUrlFormat = editApiUrlFormat.text.toString() this.apiUrlFormat = editApiUrlFormat.text.toString()
this.credentialsType = getCheckedAuthType(editAuthType.checkedRadioButtonId) this.credentialsType = getCheckedAuthType(editAuthType.checkedRadioButtonId)
this.isSameOAuthUrl = editSameOAuthSigningUrl.isChecked this.isSameOAuthUrl = editSameOAuthSigningUrl.isChecked
@ -124,53 +167,12 @@ class APIEditorActivity : BaseActivity(), OnCheckedChangeListener, OnClickListen
} }
} }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_api_editor)
val apiConfig: CustomAPIConfig
if (savedInstanceState != null) {
apiConfig = savedInstanceState.getParcelable(EXTRA_API_CONFIG)
} else {
apiConfig = intent.getParcelableExtra(EXTRA_API_CONFIG) ?: kPreferences[defaultAPIConfigKey]
}
editAuthType.setOnCheckedChangeListener(this)
editNoVersionSuffix.setOnCheckedChangeListener(this)
save.setOnClickListener(this)
apiUrlFormatHelp.setOnClickListener(this)
loadDefaults.visibility = View.VISIBLE
loadDefaults.setOnClickListener(this)
editApiUrlFormat.setText(apiConfig.apiUrlFormat)
editSameOAuthSigningUrl.isChecked = apiConfig.isSameOAuthUrl
editNoVersionSuffix.isChecked = apiConfig.isNoVersionSuffix
editConsumerKey.setText(apiConfig.consumerKey)
editConsumerSecret.setText(apiConfig.consumerSecret)
editAuthType.check(getAuthTypeId(apiConfig.credentialsType))
if (editAuthType.checkedRadioButtonId == -1) {
oauth.isChecked = true
}
}
private fun setAPIConfig(apiConfig: CustomAPIConfig) {
editApiUrlFormat.setText(apiConfig.apiUrlFormat)
editAuthType.check(getAuthTypeId(apiConfig.credentialsType))
editSameOAuthSigningUrl.isChecked = apiConfig.isSameOAuthUrl
editNoVersionSuffix.isChecked = apiConfig.isNoVersionSuffix
editConsumerKey.setText(apiConfig.consumerKey)
editConsumerSecret.setText(apiConfig.consumerSecret)
}
class LoadDefaultsChooserDialogFragment : BaseDialogFragment(), DialogInterface.OnClickListener, class LoadDefaultsChooserDialogFragment : BaseDialogFragment(), DialogInterface.OnClickListener,
LoaderManager.LoaderCallbacks<List<CustomAPIConfig>?> { LoaderManager.LoaderCallbacks<List<CustomAPIConfig>?> {
private lateinit var adapter: ArrayAdapter<CustomAPIConfig> private lateinit var adapter: ArrayAdapter<CustomAPIConfig>
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val context = context
val configs = CustomAPIConfig.listDefault(context) val configs = CustomAPIConfig.listDefault(context)
adapter = CustomAPIConfigArrayAdapter(context, configs) adapter = CustomAPIConfigArrayAdapter(context, configs)
val builder = AlertDialog.Builder(context) val builder = AlertDialog.Builder(context)
@ -182,7 +184,9 @@ class APIEditorActivity : BaseActivity(), OnCheckedChangeListener, OnClickListen
} }
override fun onClick(dialog: DialogInterface, which: Int) { override fun onClick(dialog: DialogInterface, which: Int) {
(activity as APIEditorActivity).setAPIConfig(adapter.getItem(which)) val activity = activity as APIEditorActivity
activity.apiConfig = adapter.getItem(which)
activity.applyApiConfig()
dismiss() dismiss()
} }
@ -212,17 +216,16 @@ class APIEditorActivity : BaseActivity(), OnCheckedChangeListener, OnClickListen
override fun loadInBackground(): List<CustomAPIConfig>? { override fun loadInBackground(): List<CustomAPIConfig>? {
val request = HttpRequest(GET.METHOD, DEFAULT_API_CONFIGS_URL, val request = HttpRequest(GET.METHOD, DEFAULT_API_CONFIGS_URL,
null, null, null) null, null, null)
var response: HttpResponse? = null
try { try {
response = client.newCall(request).execute() return client.newCall(request).execute().use { response ->
if (response!!.isSuccessful) { if (response.isSuccessful) {
val `is` = response.body.stream() return@use LoganSquare.parseList(response.body.stream(),
return JsonSerializer.parseList(`is`, CustomAPIConfig::class.java) CustomAPIConfig::class.java)
}
return@use null
} }
} catch (e: IOException) { } catch (e: IOException) {
// Ignore // Ignore
} finally {
Utils.closeSilently(response)
} }
return null return null
} }

View File

@ -19,6 +19,7 @@ import org.mariotaku.twidere.fragment.BaseFragment
import org.mariotaku.twidere.fragment.ExtraFeaturesIntroductionDialogFragment import org.mariotaku.twidere.fragment.ExtraFeaturesIntroductionDialogFragment
import org.mariotaku.twidere.fragment.sync.SyncSettingsFragment import org.mariotaku.twidere.fragment.sync.SyncSettingsFragment
import org.mariotaku.twidere.model.analyzer.PurchaseFinished import org.mariotaku.twidere.model.analyzer.PurchaseFinished
import org.mariotaku.twidere.model.sync.SyncProviderEntry
import org.mariotaku.twidere.util.Analyzer import org.mariotaku.twidere.util.Analyzer
import org.mariotaku.twidere.util.premium.ExtraFeaturesService import org.mariotaku.twidere.util.premium.ExtraFeaturesService
import org.mariotaku.twidere.util.sync.SyncProviderInfoFactory import org.mariotaku.twidere.util.sync.SyncProviderInfoFactory
@ -77,12 +78,14 @@ class SyncStatusFragment : BaseFragment() {
} }
private fun updateSyncSettingActions() { private fun updateSyncSettingActions() {
if (preferences[dataSyncProviderInfoKey] == null) { val providerInfo = preferences[dataSyncProviderInfoKey]
if (providerInfo == null) {
statusText.text = getText(R.string.message_sync_data_connect_hint) statusText.text = getText(R.string.message_sync_data_connect_hint)
connectButton.visibility = View.VISIBLE connectButton.visibility = View.VISIBLE
settingsButton.visibility = View.GONE settingsButton.visibility = View.GONE
} else { } else {
statusText.text = getString(R.string.message_sync_data_synced_with_name, "Dropbox") val providerEntry = SyncProviderInfoFactory.getProviderEntry(context, providerInfo.type)!!
statusText.text = getString(R.string.message_sync_data_synced_with_name, providerEntry.name)
connectButton.visibility = View.GONE connectButton.visibility = View.GONE
settingsButton.visibility = View.VISIBLE settingsButton.visibility = View.VISIBLE
} }

View File

@ -17,8 +17,7 @@
~ along with this program. If not, see <http://www.gnu.org/licenses/>. ~ along with this program. If not, see <http://www.gnu.org/licenses/>.
--> -->
<ScrollView <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
@ -44,8 +43,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/username" android:hint="@string/username"
android:inputType="textEmailAddress" android:inputType="textEmailAddress"
android:typeface="normal" android:maxLines="1"
android:maxLines="1"/> android:typeface="normal" />
<EditText <EditText
android:id="@+id/editPassword" android:id="@+id/editPassword"
@ -54,49 +53,39 @@
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:hint="@string/password" android:hint="@string/password"
android:inputType="textPassword" android:inputType="textPassword"
android:typeface="normal" android:maxLines="1"
android:maxLines="1"/> android:typeface="normal" />
</LinearLayout> </LinearLayout>
<LinearLayout
android:id="@+id/signInSignUpContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:orientation="horizontal"
android:padding="8dp">
<android.support.v7.widget.AppCompatButton
android:id="@+id/signUp"
style="?android:buttonStyleSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:minHeight="@dimen/element_size_normal"
android:text="@string/register"/>
<android.support.v7.widget.AppCompatButton <android.support.v7.widget.AppCompatButton
android:id="@+id/signIn" android:id="@+id/signIn"
style="?android:buttonStyleSmall" style="?android:buttonStyleSmall"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1"
android:minHeight="@dimen/element_size_normal" android:minHeight="@dimen/element_size_normal"
android:text="@string/sign_in" android:text="@string/sign_in"
app:backgroundTint="@color/material_light_green"/> app:backgroundTint="@color/material_light_green" />
</LinearLayout>
<Button
android:id="@+id/signUp"
style="?android:borderlessButtonStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start|center_vertical"
android:minHeight="@dimen/element_size_xsmall"
android:text="@string/register"
android:textAppearance="?android:textAppearanceSmall" />
<Button <Button
android:id="@+id/passwordSignIn" android:id="@+id/passwordSignIn"
style="?android:borderlessButtonStyle" style="?android:borderlessButtonStyle"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8dp" android:gravity="start|center_vertical"
android:clickable="true" android:minHeight="@dimen/element_size_xsmall"
android:gravity="center_vertical" android:text="@string/label_password_sign_in"
android:minHeight="36dp" android:textAppearance="?android:textAppearanceSmall" />
android:text="@string/password_sign_in_twitter_only"
android:textAppearance="?android:textAppearanceSmall"/>
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

View File

@ -888,6 +888,7 @@
<string name="message_sync_data_synced_with_name">Twidere is now synced with <xliff:g example="ownCloud" id="name">%s</xliff:g></string> <string name="message_sync_data_synced_with_name">Twidere is now synced with <xliff:g example="ownCloud" id="name">%s</xliff:g></string>
<string name="title_sync">Data sync</string> <string name="title_sync">Data sync</string>
<string name="sync_provider_name_dropbox">Dropbox</string> <string name="sync_provider_name_dropbox">Dropbox</string>
<string name="sync_provider_name_google_drive">Google Drive</string>
<!-- [verb] Disconnect from network storage --> <!-- [verb] Disconnect from network storage -->
<string name="action_sync_disconnect">Disconnect</string> <string name="action_sync_disconnect">Disconnect</string>
<string name="action_sync_sync_now">Sync now</string> <string name="action_sync_sync_now">Sync now</string>
@ -904,4 +905,5 @@
<string name="title_filters_subscription_url">URL</string> <string name="title_filters_subscription_url">URL</string>
<string name="label_filters_subscription">Subscription</string> <string name="label_filters_subscription">Subscription</string>
<string name="summary_interactions_not_available">Only available with official keys</string> <string name="summary_interactions_not_available">Only available with official keys</string>
<string name="label_password_sign_in">Password sign in</string>
</resources> </resources>