1
0
mirror of https://github.com/TwidereProject/Twidere-Android synced 2025-02-17 04:00:48 +01:00

improved extra features dialog

This commit is contained in:
Mariotaku Lee 2017-01-09 13:16:23 +08:00
parent 9b5d0e7c64
commit f9f058042b
33 changed files with 327 additions and 425 deletions

View File

@ -183,6 +183,7 @@ public interface TwidereConstants extends SharedPreferenceConstants, IntentConst
int REQUEST_SETTINGS = 19;
int REQUEST_OPEN_DOCUMENT = 20;
int REQUEST_REQUEST_PERMISSIONS = 30;
int REQUEST_PURCHASE_EXTRA_FEATURES = 41;
int TABLE_ID_ACCOUNTS = 1;
int TABLE_ID_STATUSES = 12;

View File

@ -134,9 +134,9 @@ public class ParcelableStatus implements Parcelable, Comparable<ParcelableStatus
public String in_reply_to_status_id;
@ParcelableThisPlease
@JsonField(name = "in_reply_to_user_id", typeConverter = UserKeyConverter.class)
@CursorField(value = Statuses.IN_REPLY_TO_USER_ID, converter = UserKeyCursorFieldConverter.class)
@CursorField(value = Statuses.IN_REPLY_TO_USER_KEY, converter = UserKeyCursorFieldConverter.class)
@Nullable
public UserKey in_reply_to_user_id;
public UserKey in_reply_to_user_key;
@ParcelableThisPlease
@JsonField(name = "my_retweet_id")
@CursorField(Statuses.MY_RETWEET_ID)
@ -413,7 +413,7 @@ public class ParcelableStatus implements Parcelable, Comparable<ParcelableStatus
", favorite_count=" + favorite_count +
", reply_count=" + reply_count +
", in_reply_to_status_id='" + in_reply_to_status_id + '\'' +
", in_reply_to_user_id=" + in_reply_to_user_id +
", in_reply_to_user_id=" + in_reply_to_user_key +
", my_retweet_id='" + my_retweet_id + '\'' +
", quoted_id='" + quoted_id + '\'' +
", quoted_timestamp=" + quoted_timestamp +

View File

@ -782,7 +782,7 @@ public interface TwidereDataStore {
String IN_REPLY_TO_STATUS_ID = "in_reply_to_status_id";
String IN_REPLY_TO_USER_ID = "in_reply_to_user_id";
String IN_REPLY_TO_USER_KEY = "in_reply_to_user_id";
String IN_REPLY_TO_USER_NAME = "in_reply_to_user_name";

View File

@ -2,6 +2,7 @@ import fr.avianey.androidsvgdrawable.gradle.SvgDrawableTask
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'androidsvgdrawable'
@ -90,6 +91,7 @@ dependencies {
kapt 'com.hannesdorfmann.parcelableplease:processor:1.0.2'
kapt 'com.google.dagger:dagger-compiler:2.8'
kapt 'com.github.mariotaku.ObjectCursor:processor:0.9.12'
kapt 'nz.bradcampbell:paperparcel-compiler:2.0.0-beta2'
compile project(':twidere.component.common')
compile project(':twidere.component.nyan')
@ -177,6 +179,7 @@ dependencies {
compile 'nl.komponents.kovenant:kovenant-android:3.3.0'
compile 'nl.komponents.kovenant:kovenant-functional:3.3.0'
compile 'nl.komponents.kovenant:kovenant-combine:3.3.0'
compile 'nz.bradcampbell:paperparcel:2.0.0-beta2'
}
task svgToDrawable(type: SvgDrawableTask) {

View File

@ -2,25 +2,23 @@ package org.mariotaku.twidere.util.premium
import android.content.Context
import android.content.Intent
import org.mariotaku.twidere.R
/**
* Created by mariotaku on 2016/12/25.
*/
class DummyExtraFeaturesService : ExtraFeaturesService() {
override val introductionLayout: Int = R.layout.card_item_extra_features_purchase_introduction
override val dashboardLayouts: IntArray = throw UnsupportedOperationException()
override fun getDashboardLayouts() = intArrayOf()
override fun isSupported(): Boolean = false
override fun isEnabled(): Boolean = false
override fun isEnabled(feature: String): Boolean = false
override fun destroyPurchase(): Boolean = false
override fun createPurchaseIntent(context: Context): Intent = throw UnsupportedOperationException()
override fun createPurchaseIntent(context: Context, feature: String): Intent? = null
override fun createRestorePurchaseIntent(context: Context): Intent? = null
override fun createRestorePurchaseIntent(context: Context, feature: String): Intent? = null
}

View File

@ -1,51 +0,0 @@
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

@ -13,23 +13,21 @@ import nl.komponents.kovenant.ui.alwaysUi
import nl.komponents.kovenant.ui.failUi
import nl.komponents.kovenant.ui.successUi
import org.mariotaku.twidere.Constants
import org.mariotaku.twidere.constant.EXTRA_CURRENCY
import org.mariotaku.twidere.constant.EXTRA_PRICE
import org.mariotaku.twidere.constant.IntentConstants.INTENT_PACKAGE_PREFIX
import org.mariotaku.twidere.constant.RESULT_NOT_PURCHASED
import org.mariotaku.twidere.constant.RESULT_SERVICE_UNAVAILABLE
import org.mariotaku.twidere.activity.premium.AbsExtraFeaturePurchaseActivity
import org.mariotaku.twidere.fragment.ProgressDialogFragment
import org.mariotaku.twidere.util.premium.GooglePlayExtraFeaturesService
import java.lang.ref.WeakReference
/**
* Created by mariotaku on 2016/12/25.
*/
class GooglePlayInAppPurchaseActivity : BaseActivity(), BillingProcessor.IBillingHandler {
class GooglePlayInAppPurchaseActivity : AbsExtraFeaturePurchaseActivity(),
BillingProcessor.IBillingHandler {
private lateinit var billingProcessor: BillingProcessor
private val productId: String get() = intent.getStringExtra(EXTRA_PRODUCT_ID)
private val productId: String get() = GooglePlayExtraFeaturesService.getProductId(requestingFeature)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -79,16 +77,14 @@ class GooglePlayInAppPurchaseActivity : BaseActivity(), BillingProcessor.IBillin
getProductDetailsAndFinish()
}
else -> {
setResult(getResultCode(billingResponse))
finish()
finishWithError(getResultCode(billingResponse))
}
}
}
private fun handlePurchased(sku: SkuDetails, transaction: TransactionDetails) {
val data = Intent().putExtra(EXTRA_PRICE, sku.priceValue).putExtra(EXTRA_CURRENCY, sku.currency)
setResult(RESULT_OK, data)
finish()
val result = PurchaseResult(requestingFeature, sku.priceValue, sku.currency)
finishWithResult(result)
}
@ -143,6 +139,5 @@ class GooglePlayInAppPurchaseActivity : BaseActivity(), BillingProcessor.IBillin
private const val TAG_PURCHASE_PROCESS = "get_purchase_process"
const val EXTRA_PRODUCT_ID = "product_id"
const val ACTION_RESTORE_PURCHASE = "${INTENT_PACKAGE_PREFIX}RESTORE_PURCHASE"
}
}

View File

@ -6,19 +6,18 @@ import com.anjlab.android.iab.v3.BillingProcessor
import org.mariotaku.twidere.Constants.GOOGLE_PLAY_LICENCING_PUBKEY
import org.mariotaku.twidere.R
import org.mariotaku.twidere.activity.GooglePlayInAppPurchaseActivity
import org.mariotaku.twidere.activity.premium.AbsExtraFeaturePurchaseActivity
/**
* Created by mariotaku on 2016/12/25.
*/
class GooglePlayExtraFeaturesService() : ExtraFeaturesService() {
private val EXTRA_FEATURE_PRODUCT_ID = "twidere.extra.features"
private val PRODUCT_ID_EXTRA_FEATURES_PACK = "twidere.extra.features"
private lateinit var bp: BillingProcessor
override val dashboardLayouts: IntArray = intArrayOf(R.layout.card_item_extra_features_sync_status)
override val introductionLayout: Int = R.layout.card_item_extra_features_purchase_introduction
override fun getDashboardLayouts() = intArrayOf(R.layout.card_item_extra_features_sync_status)
override fun init(context: Context) {
super.init(context)
@ -31,26 +30,42 @@ class GooglePlayExtraFeaturesService() : ExtraFeaturesService() {
override fun isSupported(): Boolean = true
override fun isEnabled(): Boolean {
val details = bp.getPurchaseTransactionDetails(EXTRA_FEATURE_PRODUCT_ID) ?: return false
return bp.isValidTransactionDetails(details)
override fun isEnabled(feature: String): Boolean {
if (bp.hasValidTransaction(PRODUCT_ID_EXTRA_FEATURES_PACK)) return true
val productId = getProductId(feature) ?: return false
return bp.hasValidTransaction(productId)
}
override fun destroyPurchase(): Boolean {
return bp.consumePurchase(EXTRA_FEATURE_PRODUCT_ID)
return bp.consumePurchase(PRODUCT_ID_EXTRA_FEATURES_PACK)
}
override fun createPurchaseIntent(context: Context): Intent {
return Intent(context, GooglePlayInAppPurchaseActivity::class.java).apply {
putExtra(GooglePlayInAppPurchaseActivity.EXTRA_PRODUCT_ID, EXTRA_FEATURE_PRODUCT_ID)
override fun createPurchaseIntent(context: Context, feature: String): Intent? {
return AbsExtraFeaturePurchaseActivity.purchaseIntent(context,
GooglePlayInAppPurchaseActivity::class.java, feature)
}
override fun createRestorePurchaseIntent(context: Context, feature: String): Intent? {
return AbsExtraFeaturePurchaseActivity.restorePurchaseIntent(context,
GooglePlayInAppPurchaseActivity::class.java, feature)
}
private fun BillingProcessor.hasValidTransaction(productId: String): Boolean {
val details = getPurchaseTransactionDetails(productId) ?: return false
return isValidTransactionDetails(details)
}
companion object {
@JvmStatic
fun getProductId(feature: String): String {
return when (feature) {
FEATURE_FEATURES_PACK -> "twidere.extra.features"
FEATURE_SYNC_DATA -> "twidere.extra.feature.data_sync"
FEATURE_FILTERS_IMPORT -> "twidere.extra.feature.filter_import"
FEATURE_FILTERS_SUBSCRIPTION -> "twidere.extra.feature.filter_subscription"
FEATURE_SCHEDULE_STATUS -> "twidere.extra.feature.schedule_status"
else -> throw UnsupportedOperationException(feature)
}
}
}
override fun createRestorePurchaseIntent(context: Context): Intent? {
return Intent(context, GooglePlayInAppPurchaseActivity::class.java).apply {
action = GooglePlayInAppPurchaseActivity.ACTION_RESTORE_PURCHASE
putExtra(GooglePlayInAppPurchaseActivity.EXTRA_PRODUCT_ID, EXTRA_FEATURE_PRODUCT_ID)
}
}
}

View File

@ -1,16 +0,0 @@
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

@ -27,12 +27,8 @@ class PremiumDashboardActivity : BaseActivity() {
extraFeaturesService = ExtraFeaturesService.newInstance(this)
setContentView(R.layout.activity_premium_dashboard)
if (extraFeaturesService.isSupported()) {
if (extraFeaturesService.isEnabled()) {
extraFeaturesService.dashboardLayouts.forEach { layout ->
View.inflate(this, layout, cardsContainer)
}
} else {
View.inflate(this, extraFeaturesService.introductionLayout, cardsContainer)
extraFeaturesService.getDashboardLayouts().forEach { layout ->
View.inflate(this, layout, cardsContainer)
}
}
}

View File

@ -0,0 +1,67 @@
package org.mariotaku.twidere.activity.premium
import android.content.Context
import android.content.Intent
import android.os.Parcel
import android.os.Parcelable
import org.mariotaku.twidere.activity.BaseActivity
import org.mariotaku.twidere.constant.IntentConstants
import paperparcel.PaperParcel
/**
* Created by mariotaku on 2017/1/8.
*/
abstract class AbsExtraFeaturePurchaseActivity : BaseActivity() {
protected val requestingFeature: String get() = intent.getStringExtra(EXTRA_REQUESTING_FEATURE)
protected fun finishWithError(code: Int) {
setResult(code)
finish()
}
protected fun finishWithResult(result: PurchaseResult) {
setResult(RESULT_OK, Intent().putExtra(EXTRA_PURCHASE_RESULT, result))
finish()
}
@PaperParcel
data class PurchaseResult(val feature: String, val price: Double, val currency: String) : Parcelable {
companion object {
@JvmField val CREATOR = PaperParcelAbsExtraFeaturePurchaseActivity_PurchaseResult.CREATOR
}
override fun describeContents() = 0
override fun writeToParcel(dest: Parcel, flags: Int) {
PaperParcelAbsExtraFeaturePurchaseActivity_PurchaseResult.writeToParcel(this, dest, flags)
}
}
companion object {
const val RESULT_SERVICE_UNAVAILABLE = 1
const val RESULT_INTERNAL_ERROR = 6
const val RESULT_NOT_PURCHASED = 8
const val EXTRA_PURCHASE_RESULT = "purchase_result"
const val EXTRA_REQUESTING_FEATURE = "requesting_feature"
const val ACTION_RESTORE_PURCHASE = "${IntentConstants.INTENT_PACKAGE_PREFIX}RESTORE_PURCHASE"
@JvmStatic
fun <T : AbsExtraFeaturePurchaseActivity> purchaseIntent(context: Context, cls: Class<T>, feature: String): Intent {
val intent = Intent(context, cls)
intent.putExtra(EXTRA_REQUESTING_FEATURE, feature)
return intent
}
@JvmStatic
fun <T : AbsExtraFeaturePurchaseActivity> restorePurchaseIntent(context: Context, cls: Class<T>, feature: String): Intent {
val intent = Intent(context, cls)
intent.action = ACTION_RESTORE_PURCHASE
intent.putExtra(EXTRA_REQUESTING_FEATURE, feature)
return intent
}
}
}

View File

@ -1,11 +0,0 @@
package org.mariotaku.twidere.constant
/**
* Created by mariotaku on 2017/1/1.
*/
const val RESULT_SERVICE_UNAVAILABLE = 1
const val RESULT_INTERNAL_ERROR = 6
const val RESULT_NOT_PURCHASED = 8
const val EXTRA_PRICE = "price"
const val EXTRA_CURRENCY = "currency"

View File

@ -80,7 +80,9 @@ import org.mariotaku.twidere.util.KeyboardShortcutsHandler.KeyboardShortcutCallb
import org.mariotaku.twidere.view.ShapedImageView
import java.util.*
class AccountsDashboardFragment : BaseSupportFragment(), LoaderCallbacks<AccountsInfo>, OnSharedPreferenceChangeListener, OnClickListener, KeyboardShortcutCallback, NavigationView.OnNavigationItemSelectedListener {
class AccountsDashboardFragment : BaseSupportFragment(), LoaderCallbacks<AccountsInfo>,
OnSharedPreferenceChangeListener, OnClickListener, KeyboardShortcutCallback,
NavigationView.OnNavigationItemSelectedListener {
private val systemWindowsInsets = Rect()
@ -342,6 +344,7 @@ class AccountsDashboardFragment : BaseSupportFragment(), LoaderCallbacks<Account
menu.setItemAvailability(R.id.favorites, useStarsForLikes)
menu.setItemAvailability(R.id.likes, !useStarsForLikes)
menu.setItemAvailability(R.id.premium_features, extraFeaturesService.isSupported())
var hasLists = false
var hasGroups = false
var hasPublicTimeline = false

View File

@ -27,6 +27,7 @@ import com.squareup.otto.Bus
import org.mariotaku.twidere.fragment.iface.IBaseFragment
import org.mariotaku.twidere.util.*
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper
import org.mariotaku.twidere.util.premium.ExtraFeaturesService
import javax.inject.Inject
open class BaseSupportFragment : Fragment(), IBaseFragment<BaseSupportFragment> {
@ -56,6 +57,8 @@ open class BaseSupportFragment : Fragment(), IBaseFragment<BaseSupportFragment>
lateinit var errorInfoStore: ErrorInfoStore
@Inject
lateinit var validator: TwidereValidator
@Inject
lateinit var extraFeaturesService: ExtraFeaturesService
private val actionHelper = IBaseFragment.ActionHelper(this)
@ -84,6 +87,7 @@ open class BaseSupportFragment : Fragment(), IBaseFragment<BaseSupportFragment>
}
override fun onDestroy() {
extraFeaturesService.release()
super.onDestroy()
DebugModeUtils.watchReferenceLeak(this)
}

View File

@ -2,8 +2,13 @@ package org.mariotaku.twidere.fragment
import android.app.Dialog
import android.os.Bundle
import android.support.v4.app.FragmentManager
import android.support.v7.app.AlertDialog
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import org.mariotaku.ktextension.Bundle
import org.mariotaku.ktextension.set
import org.mariotaku.twidere.R
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_REQUEST_CODE
import org.mariotaku.twidere.model.analyzer.PurchaseConfirm
@ -20,6 +25,9 @@ class ExtraFeaturesIntroductionDialogFragment : BaseDialogFragment() {
private lateinit var extraFeaturesService: ExtraFeaturesService
val feature: String get() = arguments.getString(EXTRA_FEATURE)
val requestCode: Int get() = arguments.getInt(EXTRA_REQUEST_CODE, 0)
override fun onDestroy() {
extraFeaturesService.release()
super.onDestroy()
@ -31,21 +39,13 @@ class ExtraFeaturesIntroductionDialogFragment : BaseDialogFragment() {
builder.setTitle(R.string.title_extra_features)
builder.setView(R.layout.dialog_extra_features_introduction)
builder.setPositiveButton(R.string.action_purchase) { dialog, which ->
val requestCode = arguments?.getInt(EXTRA_REQUEST_CODE) ?: 0
val purchaseIntent = extraFeaturesService.createPurchaseIntent(context)
if (requestCode == 0) {
startActivity(purchaseIntent)
} else if (parentFragment != null) {
parentFragment.startActivityForResult(purchaseIntent, requestCode)
} else {
activity.startActivityForResult(purchaseIntent, requestCode)
}
startPurchase(feature)
Analyzer.log(PurchaseConfirm(PurchaseFinished.NAME_EXTRA_FEATURES))
}
builder.setNegativeButton(R.string.action_later) { dialog, which ->
}
val restorePurchaseIntent = extraFeaturesService.createRestorePurchaseIntent(context)
val restorePurchaseIntent = extraFeaturesService.createRestorePurchaseIntent(context, feature)
if (restorePurchaseIntent != null) {
builder.setNeutralButton(R.string.action_restore_purchase) { dialog, which ->
startActivity(restorePurchaseIntent)
@ -59,10 +59,43 @@ class ExtraFeaturesIntroductionDialogFragment : BaseDialogFragment() {
} else {
View.GONE
}
val description = ExtraFeaturesService.getIntroduction(context, feature)
val featureIcon = it.findViewById(R.id.featureIcon) as ImageView
val featureDescription = it.findViewById(R.id.featureDescription) as TextView
featureIcon.setImageResource(description.icon)
featureDescription.text = description.description
it.findViewById(R.id.buyFeaturesPack).setOnClickListener {
startPurchase(ExtraFeaturesService.FEATURE_FEATURES_PACK)
dismiss()
}
}
if (savedInstanceState == null) {
Analyzer.log(PurchaseIntroduction(PurchaseFinished.NAME_EXTRA_FEATURES, "introduction dialog"))
}
return dialog
}
private fun startPurchase(feature: String) {
val purchaseIntent = extraFeaturesService.createPurchaseIntent(context, feature)
if (requestCode == 0) {
startActivity(purchaseIntent)
} else if (parentFragment != null) {
parentFragment.startActivityForResult(purchaseIntent, requestCode)
} else {
activity.startActivityForResult(purchaseIntent, requestCode)
}
}
companion object {
const val EXTRA_FEATURE = "feature"
fun show(fm: FragmentManager, feature: String, requestCode: Int = 0): ExtraFeaturesIntroductionDialogFragment {
val df = ExtraFeaturesIntroductionDialogFragment()
df.arguments = Bundle {
this[EXTRA_FEATURE] = feature
this[EXTRA_REQUEST_CODE] = requestCode
}
df.show(fm, "extra_features_introduction")
return df
}
}
}

View File

@ -297,6 +297,6 @@ abstract class BaseFiltersFragment : AbsContentListViewFragment<SimpleCursorAdap
internal const val REQUEST_ADD_USER_SELECT_ACCOUNT = 201
internal const val REQUEST_IMPORT_BLOCKS_SELECT_ACCOUNT = 202
internal const val REQUEST_IMPORT_MUTES_SELECT_ACCOUNT = 203
internal const val REQUEST_PURCHASE_EXTRA_FEATURES = 211
}
}

View File

@ -14,7 +14,6 @@ import android.view.MenuItem
import android.view.View
import android.widget.TextView
import org.mariotaku.kpreferences.KPreferences
import org.mariotaku.ktextension.Bundle
import org.mariotaku.ktextension.setItemAvailability
import org.mariotaku.sqliteqb.library.Expression
import org.mariotaku.twidere.R
@ -23,7 +22,6 @@ import org.mariotaku.twidere.activity.AccountSelectorActivity
import org.mariotaku.twidere.activity.LinkHandlerActivity
import org.mariotaku.twidere.activity.UserListSelectorActivity
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_ACCOUNT_HOST
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_REQUEST_CODE
import org.mariotaku.twidere.constant.nameFirstKey
import org.mariotaku.twidere.fragment.ExtraFeaturesIntroductionDialogFragment
import org.mariotaku.twidere.model.ParcelableUser
@ -40,8 +38,6 @@ import javax.inject.Inject
class FilteredUsersFragment : BaseFiltersFragment() {
private lateinit var extraFeaturesService: ExtraFeaturesService
public override val contentColumns: Array<String>
get() = TwidereDataStore.Filters.Users.COLUMNS
@ -50,13 +46,8 @@ class FilteredUsersFragment : BaseFiltersFragment() {
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
extraFeaturesService = ExtraFeaturesService.newInstance(context)
}
override fun onDestroy() {
extraFeaturesService.release()
super.onDestroy()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
@ -93,7 +84,7 @@ class FilteredUsersFragment : BaseFiltersFragment() {
}
REQUEST_PURCHASE_EXTRA_FEATURES -> {
if (resultCode == Activity.RESULT_OK) {
Analyzer.log(PurchaseFinished.create(PurchaseFinished.NAME_EXTRA_FEATURES, data))
Analyzer.log(PurchaseFinished.create(data!!))
}
}
}
@ -129,14 +120,12 @@ class FilteredUsersFragment : BaseFiltersFragment() {
else -> return false
}
if (!isExtraFeatures || extraFeaturesService.isEnabled()) {
if (!isExtraFeatures || extraFeaturesService.isEnabled(ExtraFeaturesService.FEATURE_FILTERS_IMPORT)) {
startActivityForResult(intent, requestCode)
} else {
val df = ExtraFeaturesIntroductionDialogFragment()
df.arguments = Bundle {
putInt(EXTRA_REQUEST_CODE, REQUEST_PURCHASE_EXTRA_FEATURES)
}
df.show(childFragmentManager, "extra_features_introduction")
ExtraFeaturesIntroductionDialogFragment.show(childFragmentManager,
feature = ExtraFeaturesService.FEATURE_FILTERS_IMPORT,
requestCode = REQUEST_PURCHASE_EXTRA_FEATURES)
}
return true
}

View File

@ -1,88 +0,0 @@
package org.mariotaku.twidere.fragment.premium
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
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.RESULT_NOT_PURCHASED
import org.mariotaku.twidere.constant.RESULT_SERVICE_UNAVAILABLE
import org.mariotaku.twidere.fragment.BaseSupportFragment
import org.mariotaku.twidere.model.analyzer.PurchaseConfirm
import org.mariotaku.twidere.model.analyzer.PurchaseFinished
import org.mariotaku.twidere.model.analyzer.PurchaseIntroduction
import org.mariotaku.twidere.util.Analyzer
import org.mariotaku.twidere.util.premium.ExtraFeaturesService
/**
* Created by mariotaku on 2016/12/25.
*/
class ExtraFeaturesIntroductionCardFragment : BaseSupportFragment() {
lateinit var extraFeaturesService: ExtraFeaturesService
private val REQUEST_PURCHASE: Int = 301
private val REQUEST_RESTORE_PURCHASE: Int = 302
// MARK: Fragment lifecycle
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
extraFeaturesService = ExtraFeaturesService.newInstance(context)
purchaseButton.setOnClickListener {
Analyzer.log(PurchaseConfirm(PurchaseFinished.NAME_EXTRA_FEATURES))
startActivityForResult(extraFeaturesService.createPurchaseIntent(context), REQUEST_PURCHASE)
}
val restorePurchaseIntent = extraFeaturesService.createRestorePurchaseIntent(context)
if (restorePurchaseIntent != null) {
restorePurchaseHint.visibility = View.VISIBLE
restorePurchaseButton.visibility = View.VISIBLE
restorePurchaseButton.setOnClickListener {
startActivityForResult(restorePurchaseIntent, REQUEST_RESTORE_PURCHASE)
}
} else {
restorePurchaseHint.visibility = View.GONE
restorePurchaseButton.visibility = View.GONE
restorePurchaseButton.setOnClickListener(null)
}
if (savedInstanceState == null) {
Analyzer.log(PurchaseIntroduction(PurchaseFinished.NAME_EXTRA_FEATURES, "enhanced features dashboard"))
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
REQUEST_PURCHASE -> {
when (resultCode) {
Activity.RESULT_OK -> {
Analyzer.log(PurchaseFinished.create(PurchaseFinished.NAME_EXTRA_FEATURES, data))
activity?.recreate()
}
}
}
REQUEST_RESTORE_PURCHASE -> {
when (resultCode) {
Activity.RESULT_OK -> {
activity?.recreate()
}
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()
}
}
}
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_extra_features_introduction, container, false)
}
}

View File

@ -1,5 +1,6 @@
package org.mariotaku.twidere.fragment.premium
import android.app.Activity
import android.app.Dialog
import android.content.Intent
import android.os.Bundle
@ -10,27 +11,40 @@ import android.view.ViewGroup
import kotlinx.android.synthetic.main.fragment_extra_features_sync_status.*
import org.mariotaku.kpreferences.get
import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants.REQUEST_PURCHASE_EXTRA_FEATURES
import org.mariotaku.twidere.activity.FragmentContentActivity
import org.mariotaku.twidere.constant.dataSyncProviderInfoKey
import org.mariotaku.twidere.fragment.BaseDialogFragment
import org.mariotaku.twidere.fragment.BaseSupportFragment
import org.mariotaku.twidere.fragment.ExtraFeaturesIntroductionDialogFragment
import org.mariotaku.twidere.fragment.sync.SyncSettingsFragment
import org.mariotaku.twidere.model.analyzer.PurchaseFinished
import org.mariotaku.twidere.util.Analyzer
import org.mariotaku.twidere.util.premium.ExtraFeaturesService
import org.mariotaku.twidere.util.sync.SyncProviderInfoFactory
/**
* Created by mariotaku on 2016/12/28.
*/
class ExtraFeaturesSyncStatusFragment : BaseSupportFragment() {
class SyncStatusFragment : BaseSupportFragment() {
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
updateSyncSettingActions()
connectButton.setOnClickListener {
if (!extraFeaturesService.isEnabled(ExtraFeaturesService.FEATURE_SYNC_DATA)) {
showExtraFeaturesIntroduction()
return@setOnClickListener
}
val df = ConnectNetworkStorageSelectionDialogFragment()
df.show(childFragmentManager, "connect_to_storage")
}
settingsButton.setOnClickListener {
if (!extraFeaturesService.isEnabled(ExtraFeaturesService.FEATURE_SYNC_DATA)) {
showExtraFeaturesIntroduction()
return@setOnClickListener
}
val intent = Intent(context, FragmentContentActivity::class.java)
intent.putExtra(FragmentContentActivity.EXTRA_FRAGMENT, SyncSettingsFragment::class.java.name)
intent.putExtra(FragmentContentActivity.EXTRA_TITLE, getString(R.string.title_sync_settings))
@ -43,6 +57,11 @@ class ExtraFeaturesSyncStatusFragment : BaseSupportFragment() {
REQUEST_CONNECT_NETWORK_STORAGE -> {
updateSyncSettingActions()
}
REQUEST_PURCHASE_EXTRA_FEATURES -> {
if (resultCode == Activity.RESULT_OK) {
Analyzer.log(PurchaseFinished.create(data!!))
}
}
}
}
@ -51,6 +70,12 @@ class ExtraFeaturesSyncStatusFragment : BaseSupportFragment() {
updateSyncSettingActions()
}
private fun showExtraFeaturesIntroduction() {
ExtraFeaturesIntroductionDialogFragment.show(childFragmentManager,
feature = ExtraFeaturesService.FEATURE_SYNC_DATA,
requestCode = REQUEST_PURCHASE_EXTRA_FEATURES)
}
private fun updateSyncSettingActions() {
if (preferences[dataSyncProviderInfoKey] == null) {
statusText.text = getText(R.string.message_sync_data_connect_hint)

View File

@ -1,8 +1,7 @@
package org.mariotaku.twidere.model.analyzer
import android.app.Activity
import android.content.Intent
import org.mariotaku.twidere.constant.*
import org.mariotaku.twidere.activity.premium.AbsExtraFeaturePurchaseActivity
import org.mariotaku.twidere.util.Analyzer
/**
@ -18,22 +17,12 @@ data class PurchaseFinished(val productName: String) : Analyzer.Event {
companion object {
const val NAME_EXTRA_FEATURES = "Enhanced Features"
internal fun getFailReason(resultCode: Int): String {
return when (resultCode) {
Activity.RESULT_CANCELED -> "cancelled"
RESULT_SERVICE_UNAVAILABLE -> "service unavailable"
RESULT_INTERNAL_ERROR -> "internal error"
RESULT_NOT_PURCHASED -> "not purchased"
else -> "unknown"
}
}
fun create(name: String, data: Intent?): PurchaseFinished {
val result = PurchaseFinished(name)
if (data != null) {
result.price = data.getDoubleExtra(EXTRA_PRICE, Double.NaN)
result.currency = data.getStringExtra(EXTRA_CURRENCY)
}
fun create(data: Intent): PurchaseFinished {
val purchaseResult: AbsExtraFeaturePurchaseActivity.PurchaseResult
= data.getParcelableExtra(AbsExtraFeaturePurchaseActivity.EXTRA_PURCHASE_RESULT)
val result = PurchaseFinished(purchaseResult.feature)
result.price = purchaseResult.price
result.currency = purchaseResult.currency
return result
}
}

View File

@ -125,7 +125,7 @@ object ParcelableStatusUtils {
result.in_reply_to_name = getInReplyToName(status)
result.in_reply_to_screen_name = status.inReplyToScreenName
result.in_reply_to_status_id = status.inReplyToStatusId
result.in_reply_to_user_id = getInReplyToUserId(status, accountKey)
result.in_reply_to_user_key = getInReplyToUserId(status, accountKey)
val user = status.user
result.user_key = UserKeyUtils.fromUser(user)
@ -280,8 +280,8 @@ object ParcelableStatusUtils {
status.retweet_user_nickname = manager.getUserNickname(status.retweeted_by_user_key!!)
}
if (status.in_reply_to_user_id != null) {
status.in_reply_to_user_nickname = manager.getUserNickname(status.in_reply_to_user_id!!)
if (status.in_reply_to_user_key != null) {
status.in_reply_to_user_nickname = manager.getUserNickname(status.in_reply_to_user_key!!)
}
}

View File

@ -52,6 +52,7 @@ import org.mariotaku.twidere.util.imageloader.URLFileNameGenerator
import org.mariotaku.twidere.util.media.TwidereMediaDownloader
import org.mariotaku.twidere.util.media.UILFileCache
import org.mariotaku.twidere.util.net.TwidereDns
import org.mariotaku.twidere.util.premium.ExtraFeaturesService
import org.mariotaku.twidere.util.refresh.AutoRefreshController
import org.mariotaku.twidere.util.refresh.JobSchedulerAutoRefreshController
import org.mariotaku.twidere.util.refresh.LegacyAutoRefreshController
@ -247,7 +248,7 @@ class ApplicationModule(private val application: Application) {
@Provides
@Singleton
fun syncController(kPreferences: KPreferences): SyncController {
fun syncController(): SyncController {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return JobSchedulerSyncController(application)
}
@ -262,7 +263,7 @@ class ApplicationModule(private val application: Application) {
@Provides
@Singleton
fun taskCreator(kPreferences: KPreferences, syncPreferences: SyncPreferences, bus: Bus): TaskServiceRunner {
fun taskCreator(kPreferences: KPreferences, bus: Bus): TaskServiceRunner {
return TaskServiceRunner(application, kPreferences, bus)
}
@ -280,6 +281,12 @@ class ApplicationModule(private val application: Application) {
return HotMobiLogger(application)
}
@Provides
@Singleton
fun extraFeaturesService(): ExtraFeaturesService {
return ExtraFeaturesService.newInstance(application)
}
private fun createDiskCache(dirName: String, preferences: SharedPreferencesWrapper): DiskCache {
val cacheDir = Utils.getExternalCacheDir(application, dirName)
val fallbackCacheDir = Utils.getInternalCacheDir(application, dirName)

View File

@ -3,6 +3,7 @@ package org.mariotaku.twidere.util.premium
import android.content.Context
import android.content.Intent
import android.support.annotation.CallSuper
import org.mariotaku.twidere.R
import java.util.*
/**
@ -12,9 +13,7 @@ import java.util.*
abstract class ExtraFeaturesService {
protected lateinit var context: Context
abstract val introductionLayout: Int
abstract val dashboardLayouts: IntArray
abstract fun getDashboardLayouts(): IntArray
@CallSuper
protected open fun init(context: Context) {
@ -26,25 +25,46 @@ abstract class ExtraFeaturesService {
abstract fun isSupported(): Boolean
abstract fun isEnabled(): Boolean
abstract fun isEnabled(feature: String): Boolean
/**
* For debug purpose only, this will remove purchased product
*/
abstract fun destroyPurchase(): Boolean
abstract fun createPurchaseIntent(context: Context): Intent
abstract fun createPurchaseIntent(context: Context, feature: String): Intent?
abstract fun createRestorePurchaseIntent(context: Context): Intent?
abstract fun createRestorePurchaseIntent(context: Context, feature: String): Intent?
data class Introduction(val icon: Int, val description: String)
companion object {
const val FEATURE_FEATURES_PACK = "features_pack"
const val FEATURE_FILTERS_IMPORT = "import_filters"
const val FEATURE_FILTERS_SUBSCRIPTION = "filters_subscriptions"
const val FEATURE_SYNC_DATA = "sync_data"
const val FEATURE_SCHEDULE_STATUS = "schedule_status"
fun newInstance(context: Context): ExtraFeaturesService {
val instance = ServiceLoader.load(ExtraFeaturesService::class.java).first()
instance.init(context)
return instance
}
fun getIntroduction(context: Context, feature: String): Introduction {
return when (feature) {
FEATURE_FEATURES_PACK -> Introduction(R.drawable.ic_action_infinity, "")
FEATURE_FILTERS_IMPORT -> Introduction(R.drawable.ic_action_speaker_muted,
context.getString(R.string.extra_feature_description_filters_import))
FEATURE_SYNC_DATA -> Introduction(R.drawable.ic_action_refresh,
context.getString(R.string.extra_feature_description_sync_data))
FEATURE_FILTERS_SUBSCRIPTION -> Introduction(R.drawable.ic_action_speaker_muted,
context.getString(R.string.extra_feature_description_filters_subscription))
FEATURE_SCHEDULE_STATUS -> Introduction(R.drawable.ic_action_time,
context.getString(R.string.extra_feature_description_schedule_status))
else -> throw UnsupportedOperationException(feature)
}
}
}
}

View File

@ -128,6 +128,7 @@ class StatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View)
val twitter = adapter.twitterWrapper
val linkify = adapter.twidereLinkify
val formatter = adapter.bidiFormatter
val colorNameManager = adapter.userColorNameManager
val nameFirst = adapter.nameFirst
val showCardActions = isCardActionsShown
@ -154,7 +155,7 @@ class StatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View)
statusContentUpperSpace.visibility = View.GONE
} else if (status.retweet_id != null) {
val retweetedBy = UserColorNameManager.decideDisplayName(status.retweet_user_nickname,
val retweetedBy = colorNameManager.getDisplayName(status.retweeted_by_user_key!!,
status.retweeted_by_user_name, status.retweeted_by_user_screen_name, nameFirst)
statusInfoLabel.text = context.getString(R.string.name_retweeted, formatter.unicodeWrap(retweetedBy))
statusInfoIcon.setImageResource(R.drawable.ic_activity_action_retweet)
@ -162,8 +163,8 @@ class StatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View)
statusInfoIcon.visibility = View.VISIBLE
statusContentUpperSpace.visibility = View.GONE
} else if (status.in_reply_to_status_id != null && status.in_reply_to_user_id != null && displayInReplyTo) {
val inReplyTo = UserColorNameManager.decideDisplayName(status.in_reply_to_user_nickname,
} else if (status.in_reply_to_status_id != null && status.in_reply_to_user_key != null && displayInReplyTo) {
val inReplyTo = colorNameManager.getDisplayName(status.in_reply_to_user_key!!,
status.in_reply_to_name, status.in_reply_to_screen_name, nameFirst)
statusInfoLabel.text = context.getString(R.string.in_reply_to_name, formatter.unicodeWrap(inReplyTo))
statusInfoIcon.setImageResource(R.drawable.ic_activity_action_reply)
@ -189,9 +190,10 @@ class StatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View)
quotedNameView.visibility = View.VISIBLE
quotedTextView.visibility = View.VISIBLE
quotedNameView.setName(UserColorNameManager.decideNickname(status.quoted_user_nickname,
val quoted_user_key = status.quoted_user_key!!
quotedNameView.setName(colorNameManager.getUserNickname(quoted_user_key,
status.quoted_user_name))
quotedNameView.setScreenName("@" + status.quoted_user_screen_name)
quotedNameView.setScreenName("@${status.quoted_user_screen_name}")
var quotedDisplayEnd = -1
if (status.extras.quoted_display_text_range != null) {
@ -220,8 +222,9 @@ class StatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View)
quotedTextView.visibility = View.VISIBLE
}
if (status.quoted_user_color != 0) {
quotedView.drawStart(status.quoted_user_color)
val quoted_user_color = colorNameManager.getUserColor(quoted_user_key)
if (quoted_user_color != 0) {
quotedView.drawStart(quoted_user_color)
} else {
quotedView.drawStart(ThemeUtils.getColorFromAttribute(context, R.attr.quoteIndicatorBackgroundColor, 0))
}
@ -289,7 +292,8 @@ class StatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View)
} else {
timeView.setTime(status.timestamp)
}
nameView.setName(UserColorNameManager.decideNickname(status.user_nickname, status.user_name))
nameView.setName(colorNameManager.getUserNickname(status.user_key, status.user_name))
nameView.setScreenName("@${status.user_screen_name}")
if (adapter.profileImageEnabled) {

View File

@ -852,8 +852,8 @@
<string name="extra_features_description">Unterstütze Twidere und erhalte erweiterte Funktionen</string>
<!-- Enhanced (paid) features title -->
<string name="extra_features_restore_purchase_hint">Wenn du die erweiterten Funktionen bereits erworben hast, kannst du \"Wiederherstellen\" verwenden um diese wiederherzustellen.</string>
<string name="extra_features_name_sync_data">Synchronisieren von Daten wie Filter und Entwürfe</string>
<string name="extra_features_name_import_filter_list">Importiere Filterliste von blockierten/stummgeschalteten</string>
<string name="extra_feature_description_sync_data">Synchronisieren von Daten wie Filter und Entwürfe</string>
<string name="extra_feature_description_filters_import">Importiere Filterliste von blockierten/stummgeschalteten</string>
<string name="title_error_invalid_account">Ungültiges Konto</string>
<string name="message_error_invalid_account">Einige Kontodaten sind beschädigt, Twidere entfernt diese Konten um einen Absturz zu verhindern.</string>
<string name="title_premium_features_name">Twidere ∞</string>

View File

@ -851,7 +851,7 @@
<string name="extra_features_description">Soutenez Twidere et accédez à des fonctionnalités avancées</string>
<!-- Enhanced (paid) features title -->
<string name="extra_features_restore_purchase_hint">Si vous avez déjà acheté des fonctionnalités avancées, vous pouvez utiliser \'Restaurer\' pour importer vos achats.</string>
<string name="extra_features_name_sync_data">Synchroniser les données tel que les filtres et brouillons</string>
<string name="extra_feature_description_sync_data">Synchroniser les données tel que les filtres et brouillons</string>
<string name="title_error_invalid_account">Compte non valide</string>
<string name="message_error_invalid_account">Certaines données de compte sont corrompues, Twidere va supprimer ces comptes pour éviter de planter.</string>
<string name="title_premium_features_name">Twidere ∞</string>

View File

@ -852,8 +852,8 @@
<string name="extra_features_description">支持 Twidere 并获得增强功能</string>
<!-- Enhanced (paid) features title -->
<string name="extra_features_restore_purchase_hint">如果你已经购买增强功能,可以使用“恢复”来恢复购买。</string>
<string name="extra_features_name_sync_data">同步数据,如过滤器和草稿</string>
<string name="extra_features_name_import_filter_list">从被隐藏/阻止的用户导入过滤列表</string>
<string name="extra_feature_description_sync_data">同步数据,如过滤器和草稿</string>
<string name="extra_feature_description_filters_import">从被隐藏/阻止的用户导入过滤列表</string>
<string name="title_error_invalid_account">帐号无效</string>
<string name="message_error_invalid_account">一些帐号数据已损坏Twidere 将删除这些帐户以避免崩溃。</string>
<string name="title_premium_features_name">Twidere ∞</string>

View File

@ -1,17 +0,0 @@
<?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_premium_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

@ -9,7 +9,7 @@
<fragment
android:id="@+id/extra_features_sync_status"
class="org.mariotaku.twidere.fragment.premium.ExtraFeaturesSyncStatusFragment"
class="org.mariotaku.twidere.fragment.premium.SyncStatusFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:layout="@layout/fragment_extra_features_sync_status"/>

View File

@ -1,11 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
<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"
android:padding="@dimen/element_spacing_normal">
<include layout="@layout/layout_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"/>
</FrameLayout>
<TableRow android:layout_margin="@dimen/element_spacing_normal">
<org.mariotaku.twidere.view.IconActionView
android:id="@+id/featureIcon"
android:layout_width="@dimen/element_size_small"
android:layout_height="@dimen/element_size_small"
app:iabColor="?android:textColorSecondary"
tools:src="@drawable/ic_action_refresh"/>
<TextView
android:id="@+id/featureDescription"
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:textSize="15sp"
tools:text="@string/extra_feature_description_sync_data"/>
</TableRow>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/element_spacing_normal"
android:text="@string/extra_features_pack_description"
android:textAppearance="?android:textAppearanceMedium"
android:textSize="16sp"/>
<Button
android:id="@+id/buyFeaturesPack"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/action_purchase_features_pack"/>
<TextView
android:id="@+id/restorePurchaseHint"
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,54 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="@dimen/element_spacing_normal">
<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/restorePurchaseButton"
style="?borderlessButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:text="@string/action_restore_purchase"
android:textAllCaps="true"
android:textAppearance="?android:textAppearanceSmall"
android:textColor="?colorAccent"
android:textStyle="bold"/>
<Space
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<Button
android:id="@+id/purchaseButton"
style="?borderlessButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:text="@string/action_purchase"
android:textAllCaps="true"
android:textAppearance="?android:textAppearanceSmall"
android:textColor="?colorAccent"
android:textStyle="bold"/>
</LinearLayout>
</LinearLayout>

View File

@ -1,63 +0,0 @@
<?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:id="@+id/restorePurchaseHint"
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

@ -856,10 +856,13 @@
<string name="action_restore_purchase">Restore</string>
<!-- Enhanced (paid) features description -->
<string name="extra_features_description">Support Twidere and get enhanced features</string>
<string name="extra_features_pack_description">Or buy features pack to get all features (including features in future releases)</string>
<!-- Enhanced (paid) features title -->
<string name="extra_features_restore_purchase_hint">If you\'ve already purchased enhanced features, you can use \'Restore\' to restore purchase.</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="extra_feature_description_sync_data">Sync data with Dropbox, Google Drive etc</string>
<string name="extra_feature_description_filters_import">Import filter list from blocks/mutes</string>
<string name="extra_feature_description_filters_subscription">Filters list subscription</string>
<string name="extra_feature_description_schedule_status">Schedule tweet (send later)</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.</string>
<string name="title_premium_features_name">Twidere ∞</string>
@ -890,4 +893,5 @@
<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>
<string name="message_sync_last_synced_time">Last synced: <xliff:g example="2017/1/1 12:00" id="time">%s</xliff:g></string>
<string name="action_purchase_features_pack">Buy features pack</string>
</resources>