improved sync flow

This commit is contained in:
Mariotaku Lee 2017-01-03 23:37:24 +08:00
parent a6585b12bf
commit fdb573e1d7
49 changed files with 262 additions and 81 deletions

View File

@ -225,6 +225,7 @@ public interface SharedPreferenceConstants {
String KEY_NO_VERSION_SUFFIX = "no_version_suffix";
@Preference(type = STRING, hasDefault = true, defaultString = Credentials.Type.OAUTH)
String KEY_CREDENTIALS_TYPE = "credentials_type";
String KEY_CUSTOM_API_TYPE = "api_config_type";
@Preference(type = STRING, hasDefault = true, defaultString = TWITTER_CONSUMER_KEY)
String KEY_CONSUMER_KEY = "consumer_key";
@Preference(type = STRING, hasDefault = true, defaultString = TWITTER_CONSUMER_SECRET)

View File

@ -13,7 +13,8 @@ import nl.komponents.kovenant.ui.failUi
import nl.komponents.kovenant.ui.successUi
import org.mariotaku.twidere.Constants
import org.mariotaku.twidere.constant.IntentConstants.INTENT_PACKAGE_PREFIX
import org.mariotaku.twidere.constant.PURCHASE_RESPONSE_NOT_PURCHASED
import org.mariotaku.twidere.constant.RESULT_NOT_PURCHASED
import org.mariotaku.twidere.constant.RESULT_SERVICE_UNAVAILABLE
import org.mariotaku.twidere.fragment.ProgressDialogFragment
import java.lang.ref.WeakReference
@ -138,7 +139,8 @@ class GooglePlayInAppPurchaseActivity : BaseActivity(), BillingProcessor.IBillin
val resultCode = when (billingResponse) {
BILLING_RESPONSE_RESULT_OK -> Activity.RESULT_OK
BILLING_RESPONSE_RESULT_USER_CANCELED -> Activity.RESULT_CANCELED
BILLING_RESPONSE_RESULT_ITEM_NOT_OWNED -> PURCHASE_RESPONSE_NOT_PURCHASED
BILLING_RESPONSE_RESULT_SERVICE_UNAVAILABLE -> RESULT_SERVICE_UNAVAILABLE
BILLING_RESPONSE_RESULT_ITEM_NOT_OWNED -> RESULT_NOT_PURCHASED
else -> billingResponse
}
return resultCode

View File

@ -1,17 +1,24 @@
package org.mariotaku.twidere.model.sync
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import org.mariotaku.twidere.service.DropboxDataSyncService
import org.mariotaku.twidere.util.sync.SyncController
/**
* Created by mariotaku on 2017/1/2.
*/
class DropboxSyncProviderInfo(val authToken: String) : SyncProviderInfo(DropboxSyncProviderInfo.TYPE) {
override fun writeToPreferences(editor: SharedPreferences.Editor) {
editor.putString(KEY_DROPBOX_AUTH_TOKEN, authToken)
}
override fun newSyncController(context: Context): SyncController {
return DropboxSyncController(context)
}
companion object {
const val TYPE = "dropbox"
@ -22,4 +29,15 @@ class DropboxSyncProviderInfo(val authToken: String) : SyncProviderInfo(DropboxS
}
}
class DropboxSyncController(val context: Context) : SyncController() {
override fun cleanupSyncCache() {
}
override fun performSync() {
context.startService(Intent(context, DropboxDataSyncService::class.java))
}
}
}

View File

@ -8,8 +8,8 @@ import android.support.v7.app.NotificationCompat
import android.util.Log
import android.util.Xml
import com.dropbox.core.DbxDownloader
import com.dropbox.core.DbxException
import com.dropbox.core.DbxRequestConfig
import com.dropbox.core.NetworkIOException
import com.dropbox.core.v2.DbxClientV2
import com.dropbox.core.v2.files.*
import org.mariotaku.kpreferences.get
@ -57,6 +57,8 @@ class DropboxDataSyncService : BaseIntentService("dropbox_data_sync") {
helper.performSync()
} catch (e: IOException) {
Log.w(LOGTAG_SYNC, e)
} catch (e: Exception) {
Log.wtf(LOGTAG_SYNC, e)
}
}
nm.cancel(NOTIFICATION_ID_SYNC_DATA)
@ -70,7 +72,7 @@ class DropboxDataSyncService : BaseIntentService("dropbox_data_sync") {
this.writeMimeMessageTo(context, it.outputStream)
return it.finish()
}
} catch (e: NetworkIOException) {
} catch (e: DbxException) {
throw IOException(e)
}
}
@ -86,31 +88,46 @@ class DropboxDataSyncService : BaseIntentService("dropbox_data_sync") {
}
return parsed
}
} catch (e: NetworkIOException) {
} catch (e: DbxException) {
throw IOException(e)
}
}
@Throws(IOException::class)
override fun removeDrafts(list: List<FileMetadata>): Boolean {
return client.files().deleteBatch(list.map { DeleteArg(it.pathLower) }) != null
try {
return client.files().deleteBatch(list.map { DeleteArg(it.pathLower) }) != null
} catch (e: DbxException) {
throw IOException(e)
}
}
@Throws(IOException::class)
override fun removeDraft(info: FileMetadata): Boolean {
return client.files().delete(info.pathLower) != null
try {
return client.files().delete(info.pathLower) != null
} catch (e: DbxException) {
throw IOException(e)
}
}
override val FileMetadata.draftTimestamp: Long get() = this.clientModified.time
override val FileMetadata.draftFileName: String get() = this.name
@Throws(IOException::class)
override fun listRemoteDrafts(): List<FileMetadata> {
val result = ArrayList<FileMetadata>()
var listResult: ListFolderResult = client.files().listFolder("/Drafts/")
while (true) {
// Do something with files
listResult.entries.mapNotNullTo(result) { it as? FileMetadata }
if (!listResult.hasMore) break
listResult = client.files().listFolderContinue(listResult.cursor)
try {
var listResult: ListFolderResult = client.files().listFolder("/Drafts/")
while (true) {
// Do something with files
listResult.entries.mapNotNullTo(result) { it as? FileMetadata }
if (!listResult.hasMore) break
listResult = client.files().listFolderContinue(listResult.cursor)
}
} catch (e: DbxException) {
throw IOException(e)
}
return result
}
@ -205,6 +222,7 @@ class DropboxDataSyncService : BaseIntentService("dropbox_data_sync") {
uploader?.close()
}
@Throws(IOException::class)
abstract fun performUpload(uploader: UploadUploader, data: Data)
fun uploadData(data: Data): Boolean {
@ -219,12 +237,14 @@ class DropboxDataSyncService : BaseIntentService("dropbox_data_sync") {
companion object {
@Throws(IOException::class)
private fun InputStream.newPullParser(): XmlPullParser {
val parser = Xml.newPullParser()
parser.setInput(this, "UTF-8")
return parser
}
@Throws(IOException::class)
private fun OutputStream.newSerializer(indent: Boolean = true): XmlSerializer {
val serializer = Xml.newSerializer()
serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", indent)
@ -232,9 +252,17 @@ class DropboxDataSyncService : BaseIntentService("dropbox_data_sync") {
return serializer
}
private fun DbxClientV2.newUploader(path: String, clientModified: Long) = files().uploadBuilder(path)
.withMode(WriteMode.OVERWRITE).withMute(true).withClientModified(Date(clientModified)).start()
@Throws(IOException::class)
private fun DbxClientV2.newUploader(path: String, clientModified: Long): UploadUploader {
try {
return files().uploadBuilder(path).withMode(WriteMode.OVERWRITE).withMute(true)
.withClientModified(Date(clientModified)).start()
} catch (e: DbxException) {
throw IOException(e)
}
}
@Throws(IOException::class)
private fun DbxClientV2.newDownloader(path: String): DbxDownloader<FileMetadata> {
try {
return files().downloadBuilder(path).start()
@ -243,6 +271,8 @@ class DropboxDataSyncService : BaseIntentService("dropbox_data_sync") {
throw FileNotFoundException(path)
}
throw IOException(e)
} catch (e: DbxException) {
throw IOException(e)
}
}
}

View File

@ -1,6 +1,7 @@
[
{
"name": "Twidere",
"type": "twitter",
"localized_name": "@string/provider_default",
"api_url_format": "https://[DOMAIN.]twitter.com/",
"auth_type": "oauth",
@ -11,6 +12,7 @@
},
{
"name": "Fanfou",
"type": "fanfou",
"localized_name": "@string/provider_fanfou",
"api_url_format": "http://api.fanfou.com/",
"auth_type": "oauth",
@ -21,6 +23,7 @@
},
{
"name": "Quitter.se",
"type": "statusnet",
"localized_name": "@string/provider_quitter_se",
"api_url_format": "https://quitter.se/api/",
"auth_type": "oauth",
@ -31,6 +34,7 @@
},
{
"name": "LoadAverage.org",
"type": "statusnet",
"localized_name": "@string/provider_loadaverage_org",
"api_url_format": "https://loadaverage.org/api/",
"auth_type": "oauth",
@ -41,6 +45,7 @@
},
{
"name": "GNU Social.net",
"type": "statusnet",
"localized_name": "@string/provider_gnusocial_net",
"api_url_format": "https://gnusocial.net/api/",
"auth_type": "oauth",
@ -51,6 +56,7 @@
},
{
"name": "GNU Social.de",
"type": "statusnet",
"localized_name": "@string/provider_gnusocial_de",
"api_url_format": "https://gnusocial.de/api/",
"auth_type": "oauth",
@ -61,6 +67,7 @@
},
{
"name": "status.vinilox.eu",
"type": "statusnet",
"api_url_format": "https://status.vinilox.eu/api/",
"auth_type": "oauth",
"same_oauth_url": true,
@ -70,6 +77,7 @@
},
{
"name": "Quitter.im",
"type": "statusnet",
"api_url_format": "https://quitter.im/api/",
"auth_type": "oauth",
"same_oauth_url": true,

View File

@ -6,12 +6,14 @@ import android.content.res.Resources;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
import com.hannesdorfmann.parcelableplease.annotation.ParcelablePlease;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.annotation.AccountType;
import org.mariotaku.twidere.model.account.cred.Credentials;
import org.mariotaku.twidere.util.JsonSerializer;
import org.mariotaku.twidere.util.Utils;
@ -34,6 +36,10 @@ public final class CustomAPIConfig implements Parcelable {
@JsonField(name = "name")
String name;
@AccountType
@JsonField(name = "type")
@Nullable
String type;
@JsonField(name = "localized_name")
String localizedName;
@JsonField(name = "api_url_format")
@ -53,9 +59,11 @@ public final class CustomAPIConfig implements Parcelable {
public CustomAPIConfig() {
}
public CustomAPIConfig(String name, String apiUrlFormat, String credentialsType, boolean sameOAuthUrl,
boolean noVersionSuffix, String consumerKey, String consumerSecret) {
public CustomAPIConfig(String name, @Nullable String type, String apiUrlFormat,
String credentialsType, boolean sameOAuthUrl, boolean noVersionSuffix,
String consumerKey, String consumerSecret) {
this.name = name;
this.type = type;
this.apiUrlFormat = apiUrlFormat;
this.credentialsType = credentialsType;
this.sameOAuthUrl = sameOAuthUrl;
@ -64,6 +72,11 @@ public final class CustomAPIConfig implements Parcelable {
this.consumerSecret = consumerSecret;
}
@Nullable
public String getType() {
return type;
}
public String getName() {
return name;
}
@ -165,7 +178,7 @@ public final class CustomAPIConfig implements Parcelable {
}
public static CustomAPIConfig builtin(@NonNull Context context) {
return new CustomAPIConfig(context.getString(R.string.provider_default),
return new CustomAPIConfig(context.getString(R.string.provider_default), AccountType.TWITTER,
DEFAULT_TWITTER_API_URL_FORMAT, Credentials.Type.OAUTH, true, false,
TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET);
}

View File

@ -79,7 +79,7 @@ public class ErrorInfoStore {
}
case CODE_NETWORK_ERROR: {
return new DisplayErrorInfo(code, R.drawable.ic_info_error_generic,
context.getString(R.string.network_error));
context.getString(R.string.message_network_error));
}
case CODE_TIMESTAMP_ERROR: {
return new DisplayErrorInfo(code, R.drawable.ic_info_error_generic,

View File

@ -837,9 +837,9 @@ public final class Utils implements Constants {
if (msg != null && msg.contains("!="))
return getErrorMessage(context, action, context.getString(R.string.ssl_error));
else
return getErrorMessage(context, action, context.getString(R.string.network_error));
return getErrorMessage(context, action, context.getString(R.string.message_network_error));
} else if (te.getCause() instanceof IOException)
return getErrorMessage(context, action, context.getString(R.string.network_error));
return getErrorMessage(context, action, context.getString(R.string.message_network_error));
else if (te.getCause() instanceof JSONException)
return getErrorMessage(context, action, context.getString(R.string.api_data_corrupted));
else
@ -1155,11 +1155,11 @@ public final class Utils implements Constants {
context.getString(R.string.ssl_error));
} else {
message = context.getString(R.string.error_message_with_action, action,
context.getString(R.string.network_error));
context.getString(R.string.message_network_error));
}
} else if (te.getCause() instanceof IOException) {
message = context.getString(R.string.error_message_with_action, action,
context.getString(R.string.network_error));
context.getString(R.string.message_network_error));
} else {
message = context.getString(R.string.error_message_with_action, action,
trimLineBreak(te.getMessage()));

View File

@ -59,6 +59,8 @@ import org.mariotaku.twidere.preference.iface.IDialogPreference
import org.mariotaku.twidere.util.*
import org.mariotaku.twidere.util.KeyboardShortcutsHandler.KeyboardShortcutCallback
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper
import org.mariotaku.twidere.util.support.ActivitySupport
import org.mariotaku.twidere.util.support.ActivitySupport.TaskDescriptionCompat
import org.mariotaku.twidere.util.theme.TwidereAppearanceCreator
import org.mariotaku.twidere.view.iface.IExtendedView.OnFitSystemWindowsListener
import java.lang.reflect.InvocationTargetException
@ -184,6 +186,8 @@ open class BaseActivity : ChameleonActivity(), IExtendedActivity, IThemedActivit
}
delegate.setLocalNightMode(nightMode)
super.onCreate(savedInstanceState)
ActivitySupport.setTaskDescription(this, TaskDescriptionCompat(title.toString(), null,
ColorUtils.setAlphaComponent(overrideTheme.colorToolbar, 0xFF)))
GeneralComponentHelper.build(this).inject(this)
}

View File

@ -3,4 +3,5 @@ package org.mariotaku.twidere.constant
/**
* Created by mariotaku on 2017/1/1.
*/
const val PURCHASE_RESPONSE_NOT_PURCHASED = 1
const val RESULT_SERVICE_UNAVAILABLE = 1
const val RESULT_NOT_PURCHASED = 8

View File

@ -10,6 +10,8 @@ import org.mariotaku.twidere.BuildConfig
import org.mariotaku.twidere.Constants.KEY_DISPLAY_PROFILE_IMAGE
import org.mariotaku.twidere.Constants.KEY_NO_CLOSE_AFTER_TWEET_SENT
import org.mariotaku.twidere.TwidereConstants.*
import org.mariotaku.twidere.annotation.AccountType
import org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_CUSTOM_API_TYPE
import org.mariotaku.twidere.extension.getNonEmptyString
import org.mariotaku.twidere.model.CustomAPIConfig
import org.mariotaku.twidere.model.account.cred.Credentials
@ -124,11 +126,12 @@ object defaultAPIConfigKey : KPreferenceKey<CustomAPIConfig> {
override fun read(preferences: SharedPreferences): CustomAPIConfig {
val apiUrlFormat = preferences.getNonEmptyString(KEY_API_URL_FORMAT, DEFAULT_TWITTER_API_URL_FORMAT)
val authType = preferences.getString(KEY_CREDENTIALS_TYPE, Credentials.Type.OAUTH)
val customApiType = preferences.getString(KEY_CUSTOM_API_TYPE, null) ?: AccountType.TWITTER
val sameOAuthSigningUrl = preferences.getBoolean(KEY_SAME_OAUTH_SIGNING_URL, false)
val noVersionSuffix = preferences.getBoolean(KEY_NO_VERSION_SUFFIX, false)
val consumerKey = preferences.getNonEmptyString(KEY_CONSUMER_KEY, TWITTER_CONSUMER_KEY).trim()
val consumerSecret = preferences.getNonEmptyString(KEY_CONSUMER_SECRET, TWITTER_CONSUMER_SECRET).trim()
return CustomAPIConfig("Default", apiUrlFormat, authType, sameOAuthSigningUrl,
return CustomAPIConfig("Default", customApiType, apiUrlFormat, authType, sameOAuthSigningUrl,
noVersionSuffix, consumerKey, consumerSecret)
}
@ -141,6 +144,7 @@ object defaultAPIConfigKey : KPreferenceKey<CustomAPIConfig> {
editor.remove(KEY_CONSUMER_SECRET)
}
editor.putString(KEY_API_URL_FORMAT, value.apiUrlFormat)
editor.putString(KEY_CUSTOM_API_TYPE, value.type)
editor.putString(KEY_CREDENTIALS_TYPE, value.credentialsType)
editor.putBoolean(KEY_SAME_OAUTH_SIGNING_URL, value.isSameOAuthUrl)
editor.putBoolean(KEY_NO_VERSION_SUFFIX, value.isNoVersionSuffix)

View File

@ -265,31 +265,37 @@ class DraftsFragment : BaseSupportFragment(), LoaderCallbacks<Cursor?>, OnItemCl
private class DeleteDraftsTask(
private val activity: FragmentActivity,
private val ids: LongArray
) : AsyncTask<Any, Any, Int>() {
) : AsyncTask<Any, Any, Unit>() {
private val notificationManager = activity.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
override fun doInBackground(vararg params: Any): Int? {
override fun doInBackground(vararg params: Any) {
val resolver = activity.contentResolver
val where = Expression.inArgs(Column(Drafts._ID), ids.size)
val projection = arrayOf(Drafts.MEDIA)
val c = resolver.query(Drafts.CONTENT_URI, projection, where.sql, ids.toStringArray(), null) ?: return 0
val idxMedia = c.getColumnIndex(Drafts.MEDIA)
c.moveToFirst()
while (!c.isAfterLast) {
val mediaArray = JsonSerializer.parseArray(c.getString(idxMedia), ParcelableMediaUpdate::class.java)
mediaArray?.forEach { media ->
val uri = Uri.parse(media.uri)
if ("file" == uri.scheme) {
val file = File(uri.path)
if (!file.delete()) {
Log.w(LOGTAG, String.format("Unable to delete %s", file))
val selection = where.sql
val selectionArgs = ids.toStringArray()
val c = resolver.query(Drafts.CONTENT_URI, projection, selection, selectionArgs, null) ?: return
@Suppress("ConvertTryFinallyToUseCall")
try {
val idxMedia = c.getColumnIndex(Drafts.MEDIA)
c.moveToFirst()
while (!c.isAfterLast) {
val mediaArray = JsonSerializer.parseArray(c.getString(idxMedia), ParcelableMediaUpdate::class.java)
mediaArray?.forEach { media ->
val uri = Uri.parse(media.uri)
if ("file" == uri.scheme) {
val file = File(uri.path)
if (!file.delete()) {
Log.w(LOGTAG, String.format("Unable to delete %s", file))
}
}
}
c.moveToNext()
}
c.moveToNext()
} finally {
c.close()
}
c.close()
return resolver.delete(Drafts.CONTENT_URI, where.sql, null)
resolver.delete(Drafts.CONTENT_URI, selection, selectionArgs)
}
override fun onPreExecute() {
@ -301,7 +307,7 @@ class DraftsFragment : BaseSupportFragment(), LoaderCallbacks<Cursor?>, OnItemCl
}
}
override fun onPostExecute(result: Int?) {
override fun onPostExecute(result: Unit) {
super.onPostExecute(result)
(activity as IExtendedActivity).executeAfterFragmentResumed {
val activity = it as FragmentActivity

View File

@ -10,7 +10,8 @@ import android.widget.Toast
import kotlinx.android.synthetic.main.fragment_extra_features_introduction.*
import kotlinx.android.synthetic.main.layout_extra_features_introduction.*
import org.mariotaku.twidere.R
import org.mariotaku.twidere.constant.PURCHASE_RESPONSE_NOT_PURCHASED
import org.mariotaku.twidere.constant.RESULT_NOT_PURCHASED
import org.mariotaku.twidere.constant.RESULT_SERVICE_UNAVAILABLE
import org.mariotaku.twidere.fragment.BaseSupportFragment
import org.mariotaku.twidere.util.premium.ExtraFeaturesService
@ -54,8 +55,13 @@ class ExtraFeaturesIntroductionCardFragment : BaseSupportFragment() {
REQUEST_RESTORE_PURCHASE -> {
if (requestCode == Activity.RESULT_OK) {
activity?.recreate()
} else if (resultCode == PURCHASE_RESPONSE_NOT_PURCHASED) {
Toast.makeText(context, R.string.message_extra_features_not_purchased, Toast.LENGTH_SHORT).show()
} else when (resultCode) {
RESULT_NOT_PURCHASED -> {
Toast.makeText(context, R.string.message_extra_features_not_purchased, Toast.LENGTH_SHORT).show()
}
RESULT_SERVICE_UNAVAILABLE -> {
Toast.makeText(context, R.string.message_network_error, Toast.LENGTH_SHORT).show()
}
}
}
}

View File

@ -23,10 +23,9 @@ import org.mariotaku.twidere.util.sync.SyncProviderInfoFactory
class ExtraFeaturesSyncStatusFragment : BaseSupportFragment() {
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
updateButtons()
updateSyncSettingActions()
connectButton.setOnClickListener {
val df = ConnectNetworkStorageSelectionDialogFragment()
df.show(childFragmentManager, "connect_to_storage")
@ -42,12 +41,17 @@ class ExtraFeaturesSyncStatusFragment : BaseSupportFragment() {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
REQUEST_CONNECT_NETWORK_STORAGE -> {
updateButtons()
updateSyncSettingActions()
}
}
}
private fun updateButtons() {
override fun onResume() {
super.onResume()
updateSyncSettingActions()
}
private fun updateSyncSettingActions() {
if (preferences[dataSyncProviderInfoKey] == null) {
statusText.text = getText(R.string.message_sync_data_connect_hint)
connectButton.visibility = View.VISIBLE

View File

@ -1,19 +1,34 @@
package org.mariotaku.twidere.fragment.sync
import android.app.Dialog
import android.os.Bundle
import android.support.v7.app.AlertDialog
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import org.mariotaku.twidere.Constants.SHARED_PREFERENCES_NAME
import org.mariotaku.twidere.R
import org.mariotaku.twidere.constant.dataSyncProviderInfoKey
import org.mariotaku.twidere.fragment.BaseDialogFragment
import org.mariotaku.twidere.fragment.BasePreferenceFragment
import org.mariotaku.twidere.model.sync.SyncProviderInfo
import org.mariotaku.twidere.util.sync.SyncController
import org.mariotaku.twidere.util.sync.SyncProviderInfoFactory
/**
* Created by mariotaku on 2017/1/3.
*/
class SyncSettingsFragment : BasePreferenceFragment() {
private var providerInfo: SyncProviderInfo? = null
private var syncController: SyncController? = null
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
providerInfo = kPreferences[dataSyncProviderInfoKey]
syncController = providerInfo?.newSyncController(context)
setHasOptionsMenu(true)
}
@ -25,4 +40,41 @@ class SyncSettingsFragment : BasePreferenceFragment() {
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_sync_settings, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.disconnect -> {
val df = DisconnectSyncConfirmDialogFragment()
df.show(childFragmentManager, "disconnect_confirm")
}
R.id.sync_now -> {
syncController?.performSync()
}
else -> {
return false
}
}
return true
}
private fun cleanupAndDisconnect() {
syncController?.cleanupSyncCache()
kPreferences[dataSyncProviderInfoKey] = null
activity?.finish()
}
class DisconnectSyncConfirmDialogFragment : BaseDialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = AlertDialog.Builder(context)
val providerInfo = kPreferences[dataSyncProviderInfoKey]!!
val entry = SyncProviderInfoFactory.getProviderEntry(context, providerInfo.type)!!
builder.setMessage(getString(R.string.message_sync_disconnect_from_name_confirm, entry.name))
builder.setPositiveButton(R.string.action_sync_disconnect) { dialog, which ->
(parentFragment as SyncSettingsFragment).cleanupAndDisconnect()
}
builder.setNegativeButton(android.R.string.cancel, null)
return builder.create()
}
}
}

View File

@ -1,6 +1,8 @@
package org.mariotaku.twidere.model.sync
import android.content.Context
import android.content.SharedPreferences
import org.mariotaku.twidere.util.sync.SyncController
/**
* Created by mariotaku on 2017/1/2.
@ -8,4 +10,5 @@ import android.content.SharedPreferences
abstract class SyncProviderInfo(val type: String) {
abstract fun writeToPreferences(editor: SharedPreferences.Editor)
abstract fun newSyncController(context: Context): SyncController
}

View File

@ -24,6 +24,8 @@ import java.util.*
*/
abstract class FileBasedDraftsSyncHelper<RemoteFileInfo>(val context: Context) : ISyncHelper {
@Throws(IOException::class)
override fun performSync(): Boolean {
if (BuildConfig.DEBUG) {
Log.d(LOGTAG_SYNC, "Begin syncing drafts")

View File

@ -0,0 +1,10 @@
package org.mariotaku.twidere.util.sync
/**
* Created by mariotaku on 2017/1/3.
*/
abstract class SyncController {
abstract fun performSync()
abstract fun cleanupSyncCache()
}

View File

@ -31,5 +31,14 @@ abstract class SyncProviderInfoFactory {
}
return result
}
fun getProviderEntry(context: Context, type: String): SyncProviderEntry? {
ServiceLoader.load(SyncProviderInfoFactory::class.java).forEach { factory ->
factory.getSupportedProviders(context).forEach { entry ->
if (entry.type == type) return entry
}
}
return null
}
}
}

View File

@ -379,7 +379,7 @@
<string name="theme_color">لون السِمَة</string>
<string name="wrong_url_format">صيغة الرابط خاطئة.</string>
<string name="wrong_username_password">إسم المستخدم أو كلمة السر خاطئة.</string>
<string name="network_error">خطأ في الشبكة.</string>
<string name="message_network_error">خطأ في الشبكة.</string>
<string name="api_data_corrupted">بيانات الواجهة البرمجية تالفة.</string>
<string name="ssl_error">خطأ في تشفير SSL، تحتاج لتفعيل خيار Ignore SSL error.</string>
<string name="wrong_api_key">رابط API أو شيفرة consumer key/secret غير صحيحة ، تأكد منها مرة أخرى.</string>

View File

@ -392,7 +392,7 @@
<string name="theme_color">Color del tema</string>
<string name="wrong_url_format">Formatu d\'URL incorreutu.</string>
<string name="wrong_username_password">Nome d\'usuariu/contraseña incorreutos.</string>
<string name="network_error">Fallu de rede.</string>
<string name="message_network_error">Fallu de rede.</string>
<string name="api_data_corrupted">Datos d\'API toyíos.</string>
<string name="ssl_error">Fallu SSL, quiciabes precises habilitar la opción «Inorar fallu SSL».</string>
<string name="wrong_api_key">URL d\'API o clave/secretu de consumidor incorreutos. Compruébalos de nueves, por favor.</string>

View File

@ -387,7 +387,7 @@
<string name="theme_color">Color del tema</string>
<string name="wrong_url_format">El format de la URL és incorrecte.</string>
<string name="wrong_username_password">La combinació usuari-contrasenya és incorrecta.</string>
<string name="network_error">Hi ha un error de connexió.</string>
<string name="message_network_error">Hi ha un error de connexió.</string>
<string name="api_data_corrupted">Les dades de l\'API estan malmeses.</string>
<string name="ssl_error">Error d\'SSL. Proveu d\'habilitar l\'opció «Ignora error d\'SSL».</string>
<string name="wrong_api_key">La URL de l\'API o bé el parell clau de consumidor - secret és incorrecte. Si us plau, comproveu-los.</string>

View File

@ -234,7 +234,7 @@
<string name="your_profile_image">Váš profilový obrázek</string>
<string name="wrong_url_format">Nesprávný formát adresy URL.</string>
<string name="wrong_username_password">Nesprávné uživatelské jméno/heslo.</string>
<string name="network_error">Chyba sítě.</string>
<string name="message_network_error">Chyba sítě.</string>
<string name="status_updated">Tweet odeslán.</string>
<string name="profile_updated">Profil aktualizován.</string>
<string name="profile_image_updated">Profilový obrázek aktualizován.</string>

View File

@ -392,7 +392,7 @@
<string name="theme_color">Theme Farbe</string>
<string name="wrong_url_format">Falsches URL-Format.</string>
<string name="wrong_username_password">Falscher Benutzername/Passwort.</string>
<string name="network_error">Netzwerkfehler</string>
<string name="message_network_error">Netzwerkfehler</string>
<string name="api_data_corrupted">API-Daten beschädigt.</string>
<string name="ssl_error">SSL-Fehler, aktivieren von \"SSL-Fehler ignorieren\" könnte helfen.</string>
<string name="wrong_api_key">API URL oder consumer key/secret ist nicht korrekt, bitte nochmals prüfen!</string>

View File

@ -392,7 +392,7 @@
<string name="theme_color">Color del tema</string>
<string name="wrong_url_format">El formato del URL es incorrecto.</string>
<string name="wrong_username_password">Usuario/contraseña incorrectos.</string>
<string name="network_error">Error de red.</string>
<string name="message_network_error">Error de red.</string>
<string name="api_data_corrupted">datos de la API dañados.</string>
<string name="ssl_error">Error de SSL. Posiblemente necesites activar la opción \"Ignorar errores SSL\".</string>
<string name="wrong_api_key">API url o clave de consumidor/secreta incorrecta, por favor, compruébalo de nuevo.</string>

View File

@ -392,7 +392,7 @@
<string name="theme_color">رنگ زمینه</string>
<string name="wrong_url_format">قالب URL نادرست.</string>
<string name="wrong_username_password">نام‌کاربری/گذرواژهٔ نادرست.</string>
<string name="network_error">خطای شبکه.</string>
<string name="message_network_error">خطای شبکه.</string>
<string name="api_data_corrupted">داده‌های API خراب شده است.</string>
<string name="ssl_error">خطای SSL، ممکن است نیاز داشته باشید که «خطای SSL را نادیده بگیر» را فعال کنید.</string>
<string name="wrong_api_key">نشانی API یا کلید/رمز مصرف‌کنندهٔ نادرست است، لطفاً دوباره آن‌ها را بررسی کنید.</string>

View File

@ -392,7 +392,7 @@
<string name="theme_color">Teeman väri</string>
<string name="wrong_url_format">Virheellinen URL-muoto.</string>
<string name="wrong_username_password">Väärä käyttäjänimi/salasana.</string>
<string name="network_error">Verkkovirhe.</string>
<string name="message_network_error">Verkkovirhe.</string>
<string name="api_data_corrupted">API-data on vioittunut.</string>
<string name="ssl_error">SSL-virhe. Sinun täytyy ehkä ottaa käyttöön \'Ohita SSL-virhe\' -valinta.</string>
<string name="wrong_api_key">Virheellinen API-osoite tai kulutusavain/salaus. Tarkista vielä kyseiset kentät.</string>

View File

@ -392,7 +392,7 @@
<string name="theme_color">Couleur du thème</string>
<string name="wrong_url_format">Format d\'URL érroné.</string>
<string name="wrong_username_password">Mauvais nom d\'utilisateur/mot de passe.</string>
<string name="network_error">Erreur réseau.</string>
<string name="message_network_error">Erreur réseau.</string>
<string name="api_data_corrupted">Données API corrompues.</string>
<string name="ssl_error">Erreur SSL, vous pouvez avoir besoin d\'activer l\'option \"Ignorer les erreurs SSL\".</string>
<string name="wrong_api_key">URL de l\'API ou clé client/mot de passe incorrect, merci de vérifier à nouveau.</string>

View File

@ -388,7 +388,7 @@
<string name="theme_color">Cor do Tema</string>
<string name="wrong_url_format">Formato URL erróneo.</string>
<string name="wrong_username_password">Erro no nome de usuario ou contrasinal.</string>
<string name="network_error">Erro de rede.</string>
<string name="message_network_error">Erro de rede.</string>
<string name="api_data_corrupted">Datos API corruptos.</string>
<string name="ssl_error">Erro SSL, se cadra precisas activar a opción \"Ignorar erro SSL\".</string>
<string name="wrong_api_key">URL da API ou clave cliente incorrectas, por favor comproba de novo.</string>

View File

@ -348,7 +348,7 @@
<string name="theme_color">Boja teme</string>
<string name="wrong_url_format">Krivi URL format.</string>
<string name="wrong_username_password">Krivo korisničko ime/lozinka.</string>
<string name="network_error">Greška mreže.</string>
<string name="message_network_error">Greška mreže.</string>
<string name="api_data_corrupted">Korumpirani API podaci.</string>
<string name="ssl_error">SSL greška, možda ćete morati uključiti opciju \"Ignoriraj SSL grešku\".</string>
<string name="wrong_api_key">Nevaljani API url ili korisnički ključ/tajna, provjerite ih opet.</string>

View File

@ -384,7 +384,7 @@
<string name="theme_color">Téma színe</string>
<string name="wrong_url_format">Nem megfelelő URL formátum.</string>
<string name="wrong_username_password">Nem megfelelő felhasználónév/jelszó.</string>
<string name="network_error">Hálózati hiba.</string>
<string name="message_network_error">Hálózati hiba.</string>
<string name="api_data_corrupted">Az API adatok sérültek.</string>
<string name="ssl_error">SSL hiba. Lehet, hogy be kell kapcsolnod az \"SSL hibák figyelmen kívül hagyása\" opciót.</string>
<string name="wrong_api_key">Érvénytelen API hivatkozás, vagy felhasználói kulcs/jelszó. Kérlek ellenőrizd őket újra.</string>

View File

@ -388,7 +388,7 @@
<string name="theme_color">Warna tema</string>
<string name="wrong_url_format">Format URL salah.</string>
<string name="wrong_username_password">Nama/kata sandi salah.</string>
<string name="network_error">Masalah Jaringan.</string>
<string name="message_network_error">Masalah Jaringan.</string>
<string name="api_data_corrupted">API data rusak.</string>
<string name="ssl_error">Masalah SSL, Anda mungkin harus mengaktifkan opsi \"Abaikan masalah SSL\".</string>
<string name="wrong_api_key">Kesalahan pada API url atau consumer key/secret, coba cek kembali.</string>

View File

@ -390,7 +390,7 @@
<string name="theme_color">Colore del tema</string>
<string name="wrong_url_format">Formato URL sbagliato.</string>
<string name="wrong_username_password">Username/password sbagliati.</string>
<string name="network_error">Errore di rete.</string>
<string name="message_network_error">Errore di rete.</string>
<string name="api_data_corrupted">Dati delle API corrotti.</string>
<string name="ssl_error">Errore SSL, dovresti poter abilitare l\'opzione \"Ignora errori SSL\".</string>
<string name="wrong_api_key">URL delle API non corretto, o consumer key/secret errata. Per favore, controllale.</string>

View File

@ -299,7 +299,7 @@
<string name="theme_color">צבע ערכת הנושא</string>
<string name="wrong_url_format">תבנית כתובת URL שגויה.</string>
<string name="wrong_username_password">שם משתמש/סיסמה לא נכונים.</string>
<string name="network_error">שגיאת רשת.</string>
<string name="message_network_error">שגיאת רשת.</string>
<string name="api_data_corrupted">נתוני הAPI פגומים.</string>
<string name="ssl_error">שגיאת SSL, ייתכן שעליך להפעיל את האפשרות \"התעלם משגיאות SSL\".</string>
<string name="wrong_api_key">כתובת URL שגויה לAPI או מפתח/סוד שגויים, אנא בדוק/י אותם שוב.</string>

View File

@ -391,7 +391,7 @@
<string name="theme_color">テーマ色</string>
<string name="wrong_url_format">不正なURL形式です。</string>
<string name="wrong_username_password">間違ったユーザー名/パスワードです。</string>
<string name="network_error">ネットワークエラー</string>
<string name="message_network_error">ネットワークエラー</string>
<string name="api_data_corrupted">APIデータが破損しています。</string>
<string name="ssl_error">SSLエラー、\"SSLエラーを無視\"設定をオンにしてください。</string>
<string name="wrong_api_key">不正なAPI URLかconsumer key/secretなので修正してください。</string>

View File

@ -387,7 +387,7 @@
<string name="theme_color">테마 색상</string>
<string name="wrong_url_format">잘못된 URL 양식입니다.</string>
<string name="wrong_username_password">잘못된 사용자 이름 또는 비밀번호입니다.</string>
<string name="network_error">네트워크 오류입니다.</string>
<string name="message_network_error">네트워크 오류입니다.</string>
<string name="api_data_corrupted">API 데이터가 파손되었습니다.</string>
<string name="ssl_error">SSL 오류입니다. \"SSL 오류 무시\"를 설정할 필요가 있습니다.</string>
<string name="wrong_api_key">부정확한 APL 주소 또는 컨슈머 키/시크릿입니다. 다시 한 번 확인해 주세요.</string>

View File

@ -386,7 +386,7 @@
<string name="theme_color">Themakleur</string>
<string name="wrong_url_format">Verkeerde URL-indeling.</string>
<string name="wrong_username_password">Verkeerde gebruikersnaam/wachtwoord.</string>
<string name="network_error">Netwerkfout</string>
<string name="message_network_error">Netwerkfout</string>
<string name="api_data_corrupted">API-gegevens beschadigd.</string>
<string name="ssl_error">SSL-fout. Wellicht moet je de optie \"Negeer SSL-fouten\" inschakelen.</string>
<string name="wrong_api_key">Onjuiste API url of consument sleutel/geheim, controleer ze nog een keer.</string>

View File

@ -369,7 +369,7 @@
<string name="theme_color">Temafarge</string>
<string name="wrong_url_format">Feil URL-format.</string>
<string name="wrong_username_password">Feil brukernavn eller passord.</string>
<string name="network_error">Nettverksfeil.</string>
<string name="message_network_error">Nettverksfeil.</string>
<string name="api_data_corrupted">API-data er skadet.</string>
<string name="ssl_error">SSL-feil, du må kanskje aktivere \"Ignorer SSL-feil\"-valget.</string>
<string name="wrong_api_key">Feil API-URL eller brukernøkkel/passord. Sjekk at de er riktig skrevet.</string>

View File

@ -337,7 +337,7 @@
<string name="theme_color">Kolor motywu</string>
<string name="wrong_url_format">Nieprawidłowy format URL.</string>
<string name="wrong_username_password">Błędna nazwa użytkownika / hasło.</string>
<string name="network_error">Błąd sieci.</string>
<string name="message_network_error">Błąd sieci.</string>
<string name="api_data_corrupted">Dane API uszkodzone.</string>
<string name="ssl_error">Błąd SSL, można spróbować włączyć opcję \"Ignoruj błąd SSL\".</string>
<string name="wrong_api_key">Nieprawidłowy adres API lub klucz/hasło klienta, proszę sprawdzić dane ponownie.</string>

View File

@ -392,7 +392,7 @@
<string name="theme_color">Cor do tema</string>
<string name="wrong_url_format">Formato de URL incorreto.</string>
<string name="wrong_username_password">Usuário/senha incorretos.</string>
<string name="network_error">Erro de rede.</string>
<string name="message_network_error">Erro de rede.</string>
<string name="api_data_corrupted">Dados da API corrompidos.</string>
<string name="ssl_error">Erro de SSL, você pode ter que habilitar a opção \"Ignorar Erro de SSL\".</string>
<string name="wrong_api_key">URL da API ou chave de usuário incorreta, verifique novamente.</string>

View File

@ -388,7 +388,7 @@
<string name="theme_color">Цвет темы</string>
<string name="wrong_url_format">Неверный URL формат</string>
<string name="wrong_username_password">Неверный логин/пароль</string>
<string name="network_error">Ошибка сети</string>
<string name="message_network_error">Ошибка сети</string>
<string name="api_data_corrupted">Данные API повреждены</string>
<string name="ssl_error">Ошибка SSL, возможно вам нужно включить опцию \"Игнорировать ошибку SSL\".</string>
<string name="wrong_api_key">Неправильный url-адрес API или пользовательский ключ/пароль, пожалуйста проверьте снова.</string>

View File

@ -334,7 +334,7 @@
<string name="theme_color">Temafärg</string>
<string name="wrong_url_format">Fel URL-format.</string>
<string name="wrong_username_password">Fel användarnamn eller lösenord.</string>
<string name="network_error">Network error.</string>
<string name="message_network_error">Network error.</string>
<string name="api_data_corrupted">API-data korrupt.</string>
<string name="empty_content">Empty content</string>
<string name="theme_light">Ljus</string>

View File

@ -390,7 +390,7 @@
<string name="theme_color">สีของธีม</string>
<string name="wrong_url_format">รูปแบบ URL ผิด</string>
<string name="wrong_username_password">username/password ไม่ถูกต้อง</string>
<string name="network_error">ข้อผิดพลาดของเครือข่าย</string>
<string name="message_network_error">ข้อผิดพลาดของเครือข่าย</string>
<string name="api_data_corrupted">ข้อมูล API เสียหาย</string>
<string name="ssl_error">SSL ผิดพลาด คุณต้องเปิดใช้</string>
<string name="wrong_api_key">API URL หรือ consumer key/secret ไม่ถูกต้อง โปรดตรวจสอบอีกครั้ง</string>

View File

@ -389,7 +389,7 @@
<string name="theme_color">Tema rengi</string>
<string name="wrong_url_format">Yanlış URL biçimi.</string>
<string name="wrong_username_password">Yanlış kullanıcı adı / şifre.</string>
<string name="network_error">Bağlantı hatası.</string>
<string name="message_network_error">Bağlantı hatası.</string>
<string name="api_data_corrupted">API verisi bozulmuş.</string>
<string name="ssl_error">SSL hatası, \"SSL hatasını gözardı et\" seçeneğini seçmen gerekiyor olabilir.</string>
<string name="wrong_api_key">Geçersiz API url\'si veya müşteri anahtar\'ı/gizli\'si, Lütfen onları tekrar kontrol edin.</string>

View File

@ -390,7 +390,7 @@
<string name="theme_color">Колір теми</string>
<string name="wrong_url_format">Невірний формат URL.</string>
<string name="wrong_username_password">Невірне ім\'я користувача/пароль.</string>
<string name="network_error">Помилка мережі.</string>
<string name="message_network_error">Помилка мережі.</string>
<string name="api_data_corrupted">Дані API пошкоджені.</string>
<string name="ssl_error">Помилка SSL, можливо, потрібно увімкнути параметр \"Ігнорувати помилку SSL\".</string>
<string name="wrong_api_key">Невірне API url або ключ користувача, перевірте їх ще раз.</string>

View File

@ -392,7 +392,7 @@
<string name="theme_color">主题颜色</string>
<string name="wrong_url_format">URL格式错误</string>
<string name="wrong_username_password">用户名/密码错误</string>
<string name="network_error">网络错误</string>
<string name="message_network_error">网络错误</string>
<string name="api_data_corrupted">API数据损坏</string>
<string name="ssl_error">SSL错误你可能需要启用“忽略SSL错误”选项可能降低安全性</string>
<string name="wrong_api_key">API地址或者consumer key/secret不正确请检查</string>

View File

@ -392,7 +392,7 @@
<string name="theme_color">主題顏色</string>
<string name="wrong_url_format">URL 格式錯誤</string>
<string name="wrong_username_password">用戶名/密碼錯誤</string>
<string name="network_error">網路錯誤</string>
<string name="message_network_error">網路錯誤</string>
<string name="api_data_corrupted">API 資料毀損</string>
<string name="ssl_error">SSL錯誤你可能需要啟用“忽略SSL錯誤”選項可能降低安全性</string>
<string name="wrong_api_key">API地址或者consumer key/secret不正確請檢查</string>

View File

@ -3,6 +3,11 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="AppCompatResource">
<item
android:id="@+id/sync_now"
android:showAsAction="never"
android:title="@string/action_sync_sync_now"
app:showAsAction="never"/>
<item
android:id="@+id/disconnect"
android:showAsAction="never"

View File

@ -394,9 +394,10 @@
<string name="mention_user">Mention <xliff:g id="user">%s</xliff:g></string>
<string name="theme_color">Theme color</string>
<string name="wrong_url_format">Wrong URL format.</string>
<string name="wrong_username_password">Wrong username/password.</string>
<string name="network_error">Network error.</string>
<string name="api_data_corrupted">API data corrupted.</string>
<string name="wrong_username_password">Wrong username/password</string>
<!-- Toast message for network errors -->
<string name="message_network_error">Network error</string>
<string name="api_data_corrupted">API data corrupted</string>
<string name="ssl_error">SSL error, you may need to enable \"Ignore SSL error\" option.</string>
<string name="wrong_api_key">Incorrect API url or consumer key/secret, please check them again.</string>
<string name="status_updated">Tweet sent.</string>
@ -884,5 +885,7 @@
<string name="sync_provider_name_dropbox">Dropbox</string>
<!-- [verb] Disconnect from network storage -->
<string name="action_sync_disconnect">Disconnect</string>
<string name="action_sync_sync_now">Sync now</string>
<string name="title_sync_settings">Sync settings</string>
<string name="message_sync_disconnect_from_name_confirm">Disconnect from <xliff:g example="ownCloud" id="name">%s</xliff:g>?</string>
</resources>