mirror of
https://github.com/TwidereProject/Twidere-Android
synced 2025-02-02 17:56:56 +01:00
added invalid account alert
implementing extra features
This commit is contained in:
parent
6f6705168f
commit
ce019ec316
@ -48,6 +48,7 @@ public interface IntentConstants {
|
||||
String INTENT_ACTION_HIDDEN_SETTINGS_ENTRY = INTENT_PACKAGE_PREFIX + "HIDDEN_SETTINGS_ENTRY";
|
||||
String INTENT_ACTION_EMOJI_SUPPORT_ABOUT = INTENT_PACKAGE_PREFIX + "EMOJI_SUPPORT_ABOUT";
|
||||
String INTENT_ACTION_PLUS_SERVICE_SIGN_IN = INTENT_PACKAGE_PREFIX + "PLUS_SERVICE_SIGN_IN";
|
||||
String INTENT_ACTION_IN_APP_PURCHASE = INTENT_PACKAGE_PREFIX + "IN_APP_PURCHASE";
|
||||
|
||||
String INTENT_ACTION_EXTENSION_EDIT_IMAGE = INTENT_PACKAGE_PREFIX + "EXTENSION_EDIT_IMAGE";
|
||||
String INTENT_ACTION_EXTENSION_UPLOAD = INTENT_PACKAGE_PREFIX + "EXTENSION_UPLOAD";
|
||||
|
@ -173,7 +173,7 @@ dependencies {
|
||||
compile "com.github.mariotaku.CommonsLibrary:text:$mariotaku_commons_library_version"
|
||||
compile "com.github.mariotaku.CommonsLibrary:text-kotlin:$mariotaku_commons_library_version"
|
||||
compile 'com.github.mariotaku:KPreferences:0.9.5'
|
||||
compile 'com.github.mariotaku:Chameleon:0.9.2'
|
||||
compile 'com.github.mariotaku:Chameleon:0.9.3-SNAPSHOT'
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
compile 'nl.komponents.kovenant:kovenant:3.3.0'
|
||||
compile 'nl.komponents.kovenant:kovenant-android:3.3.0'
|
||||
|
1
twidere/proguard-rules.pro
vendored
1
twidere/proguard-rules.pro
vendored
@ -68,6 +68,7 @@
|
||||
-keep class * extends org.mariotaku.twidere.util.MapFragmentFactory
|
||||
-keep class * extends org.mariotaku.twidere.util.TwitterCardFragmentFactory
|
||||
-keep class * extends org.mariotaku.twidere.util.Analyzer
|
||||
-keep class * extends org.mariotaku.twidere.util.premium.ExtraFeaturesChecker
|
||||
|
||||
-keepclassmembers class * {
|
||||
private <fields>;
|
||||
|
@ -0,0 +1,13 @@
|
||||
package org.mariotaku.twidere.util.premium
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2016/12/25.
|
||||
*/
|
||||
|
||||
class DummyExtraFeaturesChecker() : ExtraFeaturesChecker() {
|
||||
|
||||
override fun isSupported(): Boolean = false
|
||||
|
||||
override fun isEnabled(): Boolean = false
|
||||
|
||||
}
|
@ -0,0 +1 @@
|
||||
org.mariotaku.twidere.util.premium.DummyExtraFeaturesChecker
|
@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest
|
||||
package="org.mariotaku.twidere"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
@ -18,7 +19,7 @@
|
||||
android:value="AIzaSyCVdCIMFFxdNqHnCPrJ9yKUzoTfs8jhYGc"/>
|
||||
|
||||
<activity
|
||||
android:name="org.mariotaku.twidere.activity.PlusServiceGoogleSignInActivity"
|
||||
android:name=".activity.PlusServiceGoogleSignInActivity"
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.Twidere.NoDisplay">
|
||||
<meta-data
|
||||
@ -30,7 +31,7 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name="org.mariotaku.twidere.activity.DropboxAuthStarterActivity"
|
||||
android:name=".activity.DropboxAuthStarterActivity"
|
||||
android:theme="@style/Theme.Twidere.NoDisplay"/>
|
||||
<activity
|
||||
android:name="com.dropbox.core.android.AuthActivity"
|
||||
@ -45,7 +46,16 @@
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activity.PlayInAppPurchaseActivity"
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.Twidere.NoDisplay">
|
||||
<intent-filter>
|
||||
<action android:name="org.mariotaku.twidere.IN_APP_PURCHASE"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service android:name="org.mariotaku.twidere.service.DropboxDataSyncService"/>
|
||||
<service android:name=".service.DropboxDataSyncService"/>
|
||||
</application>
|
||||
</manifest>
|
@ -0,0 +1,51 @@
|
||||
package org.mariotaku.twidere.model.premium;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.hannesdorfmann.parcelableplease.annotation.ParcelablePlease;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2016/12/25.
|
||||
*/
|
||||
|
||||
@ParcelablePlease
|
||||
public class GooglePurchaseResult implements PurchaseResult, Parcelable {
|
||||
@Override
|
||||
public boolean isValid(Context context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean load(Context context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean save(Context context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
GooglePurchaseResultParcelablePlease.writeToParcel(this, dest, flags);
|
||||
}
|
||||
|
||||
public static final Creator<GooglePurchaseResult> CREATOR = new Creator<GooglePurchaseResult>() {
|
||||
public GooglePurchaseResult createFromParcel(Parcel source) {
|
||||
GooglePurchaseResult target = new GooglePurchaseResult();
|
||||
GooglePurchaseResultParcelablePlease.readFromParcel(target, source);
|
||||
return target;
|
||||
}
|
||||
|
||||
public GooglePurchaseResult[] newArray(int size) {
|
||||
return new GooglePurchaseResult[size];
|
||||
}
|
||||
};
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package org.mariotaku.twidere.activity
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import com.anjlab.android.iab.v3.BillingProcessor
|
||||
import com.anjlab.android.iab.v3.TransactionDetails
|
||||
import org.mariotaku.twidere.Constants
|
||||
import org.mariotaku.twidere.TwidereConstants.EXTRA_DATA
|
||||
import org.mariotaku.twidere.model.premium.GooglePurchaseResult
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2016/12/25.
|
||||
*/
|
||||
|
||||
class PlayInAppPurchaseActivity : Activity(), BillingProcessor.IBillingHandler {
|
||||
|
||||
lateinit var billingProcessor: BillingProcessor
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
billingProcessor = BillingProcessor(this, Constants.GOOGLE_PLAY_LICENCING_PUBKEY, this)
|
||||
if (!isFinishing && BillingProcessor.isIabServiceAvailable(this)) {
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
billingProcessor.release()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (billingProcessor.handleActivityResult(requestCode, resultCode, data)) {
|
||||
return
|
||||
}
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
|
||||
// MARK: Payment methods
|
||||
override fun onBillingError(code: Int, error: Throwable?) {
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onBillingInitialized() {
|
||||
billingProcessor.purchase(this, "android.test.purchased")
|
||||
}
|
||||
|
||||
override fun onProductPurchased(productId: String?, details: TransactionDetails?) {
|
||||
billingProcessor.getPurchaseTransactionDetails("android.test.purchased")
|
||||
val data = Intent()
|
||||
val purchaseResult = GooglePurchaseResult()
|
||||
details?.purchaseInfo?.purchaseData?.let { purchaseData ->
|
||||
|
||||
}
|
||||
data.putExtra(EXTRA_DATA, purchaseResult)
|
||||
setResult(RESULT_OK, data)
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onPurchaseHistoryRestored() {
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package org.mariotaku.twidere.util.premium
|
||||
|
||||
import android.content.Context
|
||||
import com.anjlab.android.iab.v3.BillingProcessor
|
||||
import org.mariotaku.twidere.Constants.GOOGLE_PLAY_LICENCING_PUBKEY
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2016/12/25.
|
||||
*/
|
||||
|
||||
class GooglePlayExtraFeaturesChecker() : ExtraFeaturesChecker() {
|
||||
private lateinit var bp: BillingProcessor
|
||||
|
||||
override fun init(context: Context) {
|
||||
super.init(context)
|
||||
bp = BillingProcessor(context, GOOGLE_PLAY_LICENCING_PUBKEY, null)
|
||||
}
|
||||
|
||||
override fun release() {
|
||||
bp.release()
|
||||
}
|
||||
|
||||
override fun isSupported(): Boolean = true
|
||||
|
||||
override fun isEnabled(): Boolean {
|
||||
val details = bp.getPurchaseTransactionDetails("android.test.purchased") ?: return false
|
||||
return bp.isValidTransactionDetails(details)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1 @@
|
||||
org.mariotaku.twidere.util.premium.GooglePlayExtraFeaturesChecker
|
@ -461,6 +461,9 @@
|
||||
android:name=".activity.IncompatibleAlertActivity"
|
||||
android:label="@string/error_title_device_incompatible"
|
||||
android:theme="@android:style/Theme.DeviceDefault.Dialog"/>
|
||||
<activity
|
||||
android:name=".activity.InvalidAccountAlertActivity"
|
||||
android:theme="@style/Theme.Twidere.NoDisplay"/>
|
||||
<activity
|
||||
android:name=".activity.PlusServiceDashboardActivity"
|
||||
android:label="@string/plus_service_name"
|
||||
|
@ -0,0 +1,16 @@
|
||||
package org.mariotaku.twidere.model.premium;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2016/12/25.
|
||||
*/
|
||||
|
||||
public interface PurchaseResult extends Parcelable {
|
||||
boolean save(Context context);
|
||||
|
||||
boolean load(Context context);
|
||||
|
||||
boolean isValid(Context context);
|
||||
}
|
@ -12,6 +12,7 @@ import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.mariotaku.twidere.R;
|
||||
import org.mariotaku.twidere.annotation.AccountType;
|
||||
@ -188,6 +189,19 @@ public class AccountUtils {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean hasInvalidAccount(@NonNull AccountManager am) {
|
||||
for (Account account : getAccounts(am)) {
|
||||
if (!isAccountValid(am, account)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isAccountValid(@NonNull AccountManager am, Account account) {
|
||||
if (TextUtils.isEmpty(am.peekAuthToken(account, ACCOUNT_AUTH_TOKEN_TYPE))) return false;
|
||||
if (TextUtils.isEmpty(am.getUserData(account, ACCOUNT_USER_DATA_KEY))) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static class AccountFuture implements AccountManagerFuture<Account> {
|
||||
|
||||
private final Account account;
|
||||
|
@ -1,93 +0,0 @@
|
||||
package org.mariotaku.twidere.util.support;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.accounts.AccountManagerCallback;
|
||||
import android.accounts.AccountManagerFuture;
|
||||
import android.accounts.AuthenticatorException;
|
||||
import android.accounts.OperationCanceledException;
|
||||
import android.app.Activity;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.RequiresApi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2016/12/2.
|
||||
*/
|
||||
|
||||
public class AccountManagerSupport {
|
||||
public static AccountManagerFuture<Bundle> removeAccount(@NonNull AccountManager am,
|
||||
@NonNull Account account,
|
||||
@Nullable Activity activity,
|
||||
@Nullable final AccountManagerCallback<Bundle> callback,
|
||||
@Nullable Handler handler) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
|
||||
return AccountManagerSupportL.removeAccount(am, account, activity, callback, handler);
|
||||
}
|
||||
//noinspection deprecation
|
||||
final AccountManagerFuture<Boolean> future = am.removeAccount(account, new AccountManagerCallback<Boolean>() {
|
||||
@Override
|
||||
public void run(AccountManagerFuture<Boolean> future) {
|
||||
if (callback != null) {
|
||||
callback.run(new BooleanToBundleAccountManagerFuture(future));
|
||||
}
|
||||
}
|
||||
}, handler);
|
||||
return new BooleanToBundleAccountManagerFuture(future);
|
||||
}
|
||||
|
||||
public static class AccountManagerSupportL {
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
|
||||
static AccountManagerFuture<Bundle> removeAccount(AccountManager am, Account account,
|
||||
Activity activity,
|
||||
AccountManagerCallback<Bundle> callback,
|
||||
Handler handler) {
|
||||
return am.removeAccount(account, activity, callback, handler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class BooleanToBundleAccountManagerFuture implements AccountManagerFuture<Bundle> {
|
||||
|
||||
private final AccountManagerFuture<Boolean> future;
|
||||
|
||||
BooleanToBundleAccountManagerFuture(AccountManagerFuture<Boolean> future) {
|
||||
this.future = future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancel(boolean mayInterruptIfRunning) {
|
||||
return future.cancel(mayInterruptIfRunning);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return future.isCancelled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDone() {
|
||||
return future.isDone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle getResult() throws OperationCanceledException, IOException, AuthenticatorException {
|
||||
Bundle result = new Bundle();
|
||||
result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, future.getResult());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle getResult(long timeout, TimeUnit unit) throws OperationCanceledException, IOException, AuthenticatorException {
|
||||
Bundle result = new Bundle();
|
||||
result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, future.getResult(timeout, unit));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package org.mariotaku.twidere.activity
|
||||
|
||||
import android.accounts.AccountManager
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.FragmentActivity
|
||||
import android.support.v7.app.AlertDialog
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_INTENT
|
||||
import org.mariotaku.twidere.fragment.BaseDialogFragment
|
||||
import org.mariotaku.twidere.model.util.AccountUtils
|
||||
import org.mariotaku.twidere.util.support.removeAccountSupport
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/4/4.
|
||||
*/
|
||||
class InvalidAccountAlertActivity : FragmentActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val df = InvalidAccountAlertDialogFragment()
|
||||
df.show(supportFragmentManager, "invalid_account_alert")
|
||||
}
|
||||
|
||||
|
||||
class InvalidAccountAlertDialogFragment : BaseDialogFragment() {
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val builder = AlertDialog.Builder(context)
|
||||
builder.setTitle(R.string.title_error_invalid_account)
|
||||
builder.setMessage(R.string.message_error_invalid_account)
|
||||
builder.setPositiveButton(android.R.string.ok) { dialog, which ->
|
||||
val am = AccountManager.get(context)
|
||||
AccountUtils.getAccounts(am).filter { !AccountUtils.isAccountValid(am, it) }.forEach { account ->
|
||||
am.removeAccountSupport(account)
|
||||
}
|
||||
val intent = activity.intent.getParcelableExtra<Intent>(EXTRA_INTENT)
|
||||
if (intent != null) {
|
||||
activity.startActivity(intent)
|
||||
}
|
||||
}
|
||||
builder.setNegativeButton(android.R.string.cancel) { dialog, which ->
|
||||
|
||||
}
|
||||
return builder.create()
|
||||
}
|
||||
|
||||
override fun onDismiss(dialog: DialogInterface?) {
|
||||
super.onDismiss(dialog)
|
||||
if (!activity.isFinishing) {
|
||||
activity.finish()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCancel(dialog: DialogInterface?) {
|
||||
super.onCancel(dialog)
|
||||
if (!activity.isFinishing) {
|
||||
activity.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -19,9 +19,12 @@
|
||||
|
||||
package org.mariotaku.twidere.activity
|
||||
|
||||
import android.accounts.AccountManager
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import org.mariotaku.twidere.BuildConfig
|
||||
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_INTENT
|
||||
import org.mariotaku.twidere.model.util.AccountUtils
|
||||
import org.mariotaku.twidere.util.StrictModeUtils
|
||||
import org.mariotaku.twidere.util.Utils
|
||||
|
||||
@ -33,12 +36,15 @@ open class MainActivity : BaseActivity() {
|
||||
StrictModeUtils.detectAllThreadPolicy()
|
||||
}
|
||||
super.onCreate(savedInstanceState)
|
||||
val am = AccountManager.get(this)
|
||||
if (!Utils.checkDeviceCompatible()) {
|
||||
val intent = Intent(this, IncompatibleAlertActivity::class.java)
|
||||
startActivity(Intent(this, IncompatibleAlertActivity::class.java))
|
||||
} else if (AccountUtils.hasInvalidAccount(am)) {
|
||||
val intent = Intent(this, InvalidAccountAlertActivity::class.java)
|
||||
intent.putExtra(EXTRA_INTENT, Intent(this, HomeActivity::class.java))
|
||||
startActivity(intent)
|
||||
} else {
|
||||
val intent = Intent(this, HomeActivity::class.java)
|
||||
startActivity(intent)
|
||||
startActivity(Intent(this, HomeActivity::class.java))
|
||||
}
|
||||
finish()
|
||||
}
|
||||
|
@ -13,19 +13,35 @@ import android.support.v7.app.AlertDialog
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import kotlinx.android.synthetic.main.activity_plus_service_dashboard.*
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.TwidereConstants.METADATA_KEY_PLUS_SERVICE_SIGN_IN_LABEL
|
||||
import org.mariotaku.twidere.adapter.ArrayAdapter
|
||||
import org.mariotaku.twidere.constant.IntentConstants.INTENT_ACTION_PLUS_SERVICE_SIGN_IN
|
||||
import org.mariotaku.twidere.fragment.BaseDialogFragment
|
||||
import org.mariotaku.twidere.util.premium.ExtraFeaturesChecker
|
||||
|
||||
class PlusServiceDashboardActivity : BaseActivity() {
|
||||
|
||||
private lateinit var extraFeaturesChecker: ExtraFeaturesChecker
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
extraFeaturesChecker = ExtraFeaturesChecker.instance
|
||||
extraFeaturesChecker.init(this)
|
||||
setContentView(R.layout.activity_plus_service_dashboard)
|
||||
// SignInChooserDialogFragment df = new SignInChooserDialogFragment()
|
||||
// df.show(getSupportFragmentManager(), "sign_in_chooser")
|
||||
if (extraFeaturesChecker.isSupported()) {
|
||||
if (extraFeaturesChecker.isEnabled()) {
|
||||
View.inflate(this, R.layout.card_item_extra_features_sync_status, cardsContainer)
|
||||
} else {
|
||||
View.inflate(this, R.layout.card_item_extra_features_purchase_introduction, cardsContainer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
extraFeaturesChecker.release()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
class SignInChooserDialogFragment : BaseDialogFragment(), LoaderManager.LoaderCallbacks<List<ResolveInfo>> {
|
||||
|
@ -44,7 +44,7 @@ import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages.Outbox
|
||||
import org.mariotaku.twidere.util.DataStoreUtils
|
||||
import org.mariotaku.twidere.util.IntentUtils
|
||||
import org.mariotaku.twidere.util.Utils
|
||||
import org.mariotaku.twidere.util.support.AccountManagerSupport
|
||||
import org.mariotaku.twidere.util.support.removeAccountSupport
|
||||
|
||||
/**
|
||||
* Sort and toggle account availability
|
||||
@ -217,7 +217,7 @@ class AccountsManagerFragment : BaseSupportFragment(), LoaderManager.LoaderCallb
|
||||
when (which) {
|
||||
DialogInterface.BUTTON_POSITIVE -> {
|
||||
val accountKey = account.getAccountKey(am)
|
||||
AccountManagerSupport.removeAccount(am, account, null, null, null)
|
||||
am.removeAccountSupport(account)
|
||||
val where = Expression.equalsArgs(AccountSupportColumns.ACCOUNT_KEY).sql
|
||||
val whereArgs = arrayOf(accountKey.toString())
|
||||
// Also delete tweets related to the account we previously
|
||||
|
@ -43,11 +43,13 @@ import android.widget.AutoCompleteTextView
|
||||
import android.widget.ListView
|
||||
import android.widget.TextView
|
||||
import kotlinx.android.synthetic.main.fragment_content_listview.*
|
||||
import org.mariotaku.ktextension.setItemAvailability
|
||||
import org.mariotaku.sqliteqb.library.Columns.Column
|
||||
import org.mariotaku.sqliteqb.library.Expression
|
||||
import org.mariotaku.sqliteqb.library.RawItemArray
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.TwidereConstants.*
|
||||
import org.mariotaku.twidere.activity.AccountSelectorActivity
|
||||
import org.mariotaku.twidere.activity.UserListSelectorActivity
|
||||
import org.mariotaku.twidere.activity.iface.IControlBarActivity
|
||||
import org.mariotaku.twidere.adapter.ComposeAutoCompleteAdapter
|
||||
@ -59,8 +61,12 @@ import org.mariotaku.twidere.provider.TwidereDataStore.Filters
|
||||
import org.mariotaku.twidere.util.*
|
||||
import org.mariotaku.twidere.util.Utils.getDefaultAccountKey
|
||||
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper
|
||||
import org.mariotaku.twidere.util.premium.ExtraFeaturesChecker
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val REQUEST_IMPORT_BLOCKS_SELECT_ACCOUNT = 201
|
||||
private const val REQUEST_IMPORT_MUTES_SELECT_ACCOUNT = 202
|
||||
|
||||
abstract class BaseFiltersFragment : AbsContentListViewFragment<SimpleCursorAdapter>(),
|
||||
LoaderManager.LoaderCallbacks<Cursor?>, MultiChoiceModeListener {
|
||||
private var actionMode: ActionMode? = null
|
||||
@ -325,12 +331,25 @@ abstract class BaseFiltersFragment : AbsContentListViewFragment<SimpleCursorAdap
|
||||
|
||||
class FilteredUsersFragment : BaseFiltersFragment() {
|
||||
|
||||
private lateinit var extraFeaturesChecker: ExtraFeaturesChecker
|
||||
|
||||
public override val contentColumns: Array<String>
|
||||
get() = Filters.Users.COLUMNS
|
||||
|
||||
override val contentUri: Uri
|
||||
get() = Filters.Users.CONTENT_URI
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
extraFeaturesChecker = ExtraFeaturesChecker.instance
|
||||
extraFeaturesChecker.init(context)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
extraFeaturesChecker.release()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
when (requestCode) {
|
||||
REQUEST_SELECT_USER -> {
|
||||
@ -344,6 +363,12 @@ abstract class BaseFiltersFragment : AbsContentListViewFragment<SimpleCursorAdap
|
||||
resolver.delete(Filters.Users.CONTENT_URI, where, whereArgs)
|
||||
resolver.insert(Filters.Users.CONTENT_URI, values)
|
||||
}
|
||||
REQUEST_IMPORT_BLOCKS_SELECT_ACCOUNT -> {
|
||||
|
||||
}
|
||||
REQUEST_IMPORT_MUTES_SELECT_ACCOUNT -> {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -351,15 +376,39 @@ abstract class BaseFiltersFragment : AbsContentListViewFragment<SimpleCursorAdap
|
||||
inflater.inflate(R.menu.menu_filters_users, menu)
|
||||
}
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||
super.onPrepareOptionsMenu(menu)
|
||||
val isFeaturesSupported = extraFeaturesChecker.isSupported()
|
||||
menu.setItemAvailability(R.id.add_user_single, !isFeaturesSupported)
|
||||
menu.setItemAvailability(R.id.add_user_submenu, isFeaturesSupported)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.add -> {
|
||||
R.id.add_user_single, R.id.add_user -> {
|
||||
val intent = Intent(INTENT_ACTION_SELECT_USER)
|
||||
intent.setClass(context, UserListSelectorActivity::class.java)
|
||||
intent.putExtra(EXTRA_ACCOUNT_KEY, getDefaultAccountKey(activity))
|
||||
startActivityForResult(intent, REQUEST_SELECT_USER)
|
||||
return true
|
||||
}
|
||||
R.id.import_from_blocked_users, R.id.import_from_muted_users -> {
|
||||
if (extraFeaturesChecker.isEnabled()) {
|
||||
val intent = Intent(context, AccountSelectorActivity::class.java)
|
||||
intent.putExtra(EXTRA_SINGLE_SELECTION, true)
|
||||
intent.putExtra(EXTRA_SELECT_ONLY_ITEM_AUTOMATICALLY, true)
|
||||
val requestCode = when (item.itemId) {
|
||||
R.id.import_from_blocked_users -> REQUEST_IMPORT_BLOCKS_SELECT_ACCOUNT
|
||||
R.id.import_from_muted_users -> REQUEST_IMPORT_MUTES_SELECT_ACCOUNT
|
||||
else -> throw AssertionError()
|
||||
}
|
||||
startActivityForResult(intent, requestCode)
|
||||
} else {
|
||||
val df = ExtraFeaturesIntroductionDialogFragment()
|
||||
df.show(childFragmentManager, "extra_features_introduction")
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
@ -0,0 +1,28 @@
|
||||
package org.mariotaku.twidere.fragment
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.support.v7.app.AlertDialog
|
||||
import org.mariotaku.twidere.R
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2016/12/25.
|
||||
*/
|
||||
|
||||
class ExtraFeaturesIntroductionDialogFragment : BaseDialogFragment() {
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val builder = AlertDialog.Builder(context)
|
||||
builder.setTitle(R.string.title_extra_features)
|
||||
builder.setView(R.layout.dialog_extra_features_introduction)
|
||||
builder.setPositiveButton(R.string.action_purchase) { dialog, which ->
|
||||
|
||||
}
|
||||
builder.setNegativeButton(R.string.action_later) { dialog, which ->
|
||||
|
||||
}
|
||||
builder.setNeutralButton(R.string.action_restore_purchase) { dialog, which ->
|
||||
|
||||
}
|
||||
return builder.create()
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package org.mariotaku.twidere.fragment.premium
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import kotlinx.android.synthetic.main.fragment_extra_features_introduction.*
|
||||
import org.mariotaku.twidere.BuildConfig
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.constant.IntentConstants.INTENT_ACTION_IN_APP_PURCHASE
|
||||
import org.mariotaku.twidere.fragment.BaseSupportFragment
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2016/12/25.
|
||||
*/
|
||||
|
||||
class ExtraFeaturesIntroductionCardFragment : BaseSupportFragment() {
|
||||
|
||||
// MARK: Fragment lifecycle
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
purchaseButton.setOnClickListener {
|
||||
startActivity(Intent(INTENT_ACTION_IN_APP_PURCHASE).setPackage(BuildConfig.APPLICATION_ID))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.fragment_extra_features_introduction, container, false)
|
||||
}
|
||||
|
||||
}
|
@ -93,8 +93,8 @@ abstract class CursorSupportUsersLoader(
|
||||
val users: List<User>
|
||||
if (useIDs(details)) {
|
||||
val ids = getIDs(twitter, details, paging)
|
||||
setCursors(ids)
|
||||
users = twitter.lookupUsers(ids.iDs)
|
||||
setCursors(ids)
|
||||
} else {
|
||||
users = getCursoredUsers(twitter, details, paging)
|
||||
if (users is CursorSupport) {
|
||||
|
@ -0,0 +1,32 @@
|
||||
package org.mariotaku.twidere.util.premium
|
||||
|
||||
import android.content.Context
|
||||
import android.support.annotation.CallSuper
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2016/12/25.
|
||||
*/
|
||||
|
||||
abstract class ExtraFeaturesChecker {
|
||||
protected lateinit var context: Context
|
||||
|
||||
@CallSuper
|
||||
open fun init(context: Context) {
|
||||
this.context = context
|
||||
}
|
||||
|
||||
open fun release() {
|
||||
}
|
||||
|
||||
abstract fun isSupported(): Boolean
|
||||
|
||||
abstract fun isEnabled(): Boolean
|
||||
|
||||
companion object {
|
||||
|
||||
val instance: ExtraFeaturesChecker
|
||||
get() = ServiceLoader.load(ExtraFeaturesChecker::class.java).first()
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
package org.mariotaku.twidere.util.support
|
||||
|
||||
import android.accounts.*
|
||||
import android.app.Activity
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.support.annotation.RequiresApi
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2016/12/2.
|
||||
*/
|
||||
|
||||
fun AccountManager.removeAccountSupport(
|
||||
account: Account,
|
||||
activity: Activity? = null,
|
||||
callback: AccountManagerCallback<Bundle>? = null,
|
||||
handler: Handler? = null
|
||||
): AccountManagerFuture<Bundle> {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
|
||||
return AccountManagerSupportL.removeAccount(this, account, activity, callback, handler)
|
||||
}
|
||||
|
||||
val future = this.removeAccount(account, { future ->
|
||||
callback?.run(BooleanToBundleAccountManagerFuture(future))
|
||||
}, handler)
|
||||
return BooleanToBundleAccountManagerFuture(future)
|
||||
}
|
||||
|
||||
object AccountManagerSupportL {
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
|
||||
internal fun removeAccount(
|
||||
am: AccountManager, account: Account,
|
||||
activity: Activity?,
|
||||
callback: AccountManagerCallback<Bundle>?,
|
||||
handler: Handler?
|
||||
): AccountManagerFuture<Bundle> {
|
||||
return am.removeAccount(account, activity, callback, handler)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class BooleanToBundleAccountManagerFuture internal constructor(private val future: AccountManagerFuture<Boolean>) : AccountManagerFuture<Bundle> {
|
||||
|
||||
override fun cancel(mayInterruptIfRunning: Boolean): Boolean {
|
||||
return future.cancel(mayInterruptIfRunning)
|
||||
}
|
||||
|
||||
override fun isCancelled(): Boolean {
|
||||
return future.isCancelled
|
||||
}
|
||||
|
||||
override fun isDone(): Boolean {
|
||||
return future.isDone
|
||||
}
|
||||
|
||||
@Throws(OperationCanceledException::class, IOException::class, AuthenticatorException::class)
|
||||
override fun getResult(): Bundle {
|
||||
val result = Bundle()
|
||||
result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, future.result)
|
||||
return result
|
||||
}
|
||||
|
||||
@Throws(OperationCanceledException::class, IOException::class, AuthenticatorException::class)
|
||||
override fun getResult(timeout: Long, unit: TimeUnit): Bundle {
|
||||
val result = Bundle()
|
||||
result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, future.getResult(timeout, unit))
|
||||
return result
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
@ -11,27 +10,12 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:id="@+id/cardsContainer"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin">
|
||||
|
||||
<android.support.v7.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardBackgroundColor="?cardItemBackgroundColor">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/element_spacing_normal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/title_extra_features"/>
|
||||
</RelativeLayout>
|
||||
</android.support.v7.widget.CardView>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.v7.widget.CardView
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardBackgroundColor="?cardItemBackgroundColor"
|
||||
tools:showIn="@layout/activity_plus_service_dashboard">
|
||||
|
||||
<fragment
|
||||
android:id="@+id/extra_features_introduction"
|
||||
class="org.mariotaku.twidere.fragment.premium.ExtraFeaturesIntroductionCardFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:layout="@layout/fragment_extra_features_introduction"/>
|
||||
</android.support.v7.widget.CardView>
|
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.v7.widget.CardView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardBackgroundColor="?cardItemBackgroundColor"
|
||||
tools:showIn="@layout/activity_plus_service_dashboard">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/title_extra_features"/>
|
||||
</android.support.v7.widget.CardView>
|
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/element_spacing_normal">
|
||||
|
||||
<include layout="@layout/layout_extra_features_introduction"/>
|
||||
|
||||
</FrameLayout>
|
@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/element_spacing_normal"
|
||||
tools:showIn="@layout/activity_plus_service_dashboard">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/element_spacing_normal"
|
||||
android:text="@string/title_extra_features"
|
||||
android:textAppearance="?android:textAppearanceMedium"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<include layout="@layout/layout_extra_features_introduction"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end">
|
||||
|
||||
<Button
|
||||
android:id="@+id/purchaseButton"
|
||||
style="?borderlessButtonStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/action_purchase"
|
||||
android:textAllCaps="true"
|
||||
android:textAppearance="?android:textAppearanceSmall"
|
||||
android:textColor="?colorAccent"
|
||||
android:textStyle="bold"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TableLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:showIn="@layout/fragment_extra_features_introduction">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/element_spacing_normal"
|
||||
android:text="@string/extra_features_description"
|
||||
android:textAppearance="?android:textAppearanceMedium"
|
||||
android:textSize="16sp"/>
|
||||
|
||||
<TableRow android:layout_margin="@dimen/element_spacing_normal">
|
||||
|
||||
<org.mariotaku.twidere.view.IconActionView
|
||||
android:layout_width="@dimen/element_size_small"
|
||||
android:layout_height="@dimen/element_size_small"
|
||||
android:src="@drawable/ic_action_refresh"
|
||||
app:iabColor="?android:textColorSecondary"/>
|
||||
|
||||
<TextView
|
||||
android:layout_marginLeft="@dimen/element_spacing_normal"
|
||||
android:layout_marginStart="@dimen/element_spacing_normal"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="@dimen/element_size_small"
|
||||
android:text="@string/extra_features_name_sync_data"
|
||||
android:textSize="15sp"/>
|
||||
</TableRow>
|
||||
|
||||
<TableRow android:layout_margin="@dimen/element_spacing_normal">
|
||||
|
||||
<org.mariotaku.twidere.view.IconActionView
|
||||
android:layout_width="@dimen/element_size_small"
|
||||
android:layout_height="@dimen/element_size_small"
|
||||
android:src="@drawable/ic_action_speaker_muted"
|
||||
app:iabColor="?android:textColorSecondary"/>
|
||||
|
||||
<TextView
|
||||
android:layout_marginLeft="@dimen/element_spacing_normal"
|
||||
android:layout_marginStart="@dimen/element_spacing_normal"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="@dimen/element_size_small"
|
||||
android:text="@string/extra_features_name_import_filter_list"
|
||||
android:textSize="15sp"/>
|
||||
</TableRow>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/element_spacing_normal"
|
||||
android:text="@string/extra_features_restore_purchase_hint"
|
||||
android:textAppearance="?android:textAppearanceMedium"
|
||||
android:textSize="16sp"/>
|
||||
</TableLayout>
|
||||
|
||||
|
||||
|
@ -1,26 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu 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"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:ignore="AlwaysShowAction">
|
||||
|
||||
<item
|
||||
android:id="@+id/add_submenu"
|
||||
android:id="@+id/add_user_single"
|
||||
android:icon="@drawable/ic_action_add"
|
||||
android:title="@string/select_user"
|
||||
app:showAsAction="always"/>
|
||||
<item
|
||||
android:id="@+id/add_user_submenu"
|
||||
android:icon="@drawable/ic_action_add"
|
||||
android:title="@string/add"
|
||||
app:showAsAction="always">
|
||||
<menu>
|
||||
<item
|
||||
android:id="@id/add"
|
||||
android:id="@+id/add_user"
|
||||
android:title="@string/select_user"/>
|
||||
<item
|
||||
android:id="@+id/import_from_blocked_users"
|
||||
android:enabled="@bool/is_debug"
|
||||
android:title="@string/action_filter_import_from_blocked_users"
|
||||
android:visible="@bool/is_debug"/>
|
||||
android:title="@string/action_filter_import_from_blocked_users"/>
|
||||
<item
|
||||
android:id="@+id/import_from_muted_users"
|
||||
android:enabled="@bool/is_debug"
|
||||
android:title="@string/action_filter_import_from_muted_users"
|
||||
android:visible="@bool/is_debug"/>
|
||||
android:title="@string/action_filter_import_from_muted_users"/>
|
||||
</menu>
|
||||
|
||||
</item>
|
||||
|
@ -830,4 +830,12 @@
|
||||
<string name="preference_filter_unavailable_quote_statuses">Filter unavailable quotes</string>
|
||||
<string name="action_filter_import_from_blocked_users">Import from blocked users</string>
|
||||
<string name="action_filter_import_from_muted_users">Import from muted users</string>
|
||||
<string name="action_purchase">Purchase</string>
|
||||
<string name="action_restore_purchase">Restore purchase</string>
|
||||
<string name="extra_features_description">Support Twidere and get enhanced features</string>
|
||||
<string name="extra_features_restore_purchase_hint">If you\'ve already purchased enhanced features, you can use \'Restore purchase\' to enable.</string>
|
||||
<string name="extra_features_name_sync_data">Sync data like filters and drafts</string>
|
||||
<string name="extra_features_name_import_filter_list">Import filter list from blocks/mutes</string>
|
||||
<string name="title_error_invalid_account">Invalid account</string>
|
||||
<string name="message_error_invalid_account">Some account data are corrupted, Twidere will remove those accounts to prevent crash. Sorry for the inconvenience.</string>
|
||||
</resources>
|
Loading…
x
Reference in New Issue
Block a user