1
0
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:
Mariotaku Lee 2016-12-26 00:20:08 +08:00
parent 6f6705168f
commit ce019ec316
32 changed files with 683 additions and 132 deletions

View File

@ -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";

View File

@ -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'

View File

@ -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>;

View File

@ -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
}

View File

@ -0,0 +1 @@
org.mariotaku.twidere.util.premium.DummyExtraFeaturesChecker

View File

@ -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>

View File

@ -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];
}
};
}

View File

@ -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() {
}
}

View File

@ -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)
}
}

View File

@ -0,0 +1 @@
org.mariotaku.twidere.util.premium.GooglePlayExtraFeaturesChecker

View File

@ -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"

View File

@ -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);
}

View File

@ -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;

View File

@ -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;
}
}
}

View File

@ -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()
}
}
}
}

View File

@ -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()
}

View File

@ -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>> {

View File

@ -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

View File

@ -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)
}

View File

@ -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()
}
}

View File

@ -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)
}
}

View File

@ -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) {

View File

@ -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()
}
}

View File

@ -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
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>