Merge branch 'v2.4' into q-beta

This commit is contained in:
Mygod
2019-05-09 14:03:16 +08:00
10 changed files with 74 additions and 91 deletions

View File

@@ -128,6 +128,8 @@ Undocumented API list:
* (since API 24) [`Landroid/net/ConnectivityManager;->getLastTetherError(Ljava/lang/String;)I,greylist`](https://android.googlesource.com/platform/prebuilts/runtime/+/7cb2ccf/appcompat/hiddenapi-flags.csv#122588) * (since API 24) [`Landroid/net/ConnectivityManager;->getLastTetherError(Ljava/lang/String;)I,greylist`](https://android.googlesource.com/platform/prebuilts/runtime/+/7cb2ccf/appcompat/hiddenapi-flags.csv#122588)
* (since API 24) [`Landroid/net/ConnectivityManager;->startTethering(IZLandroid/net/ConnectivityManager$OnStartTetheringCallback;Landroid/os/Handler;)V,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/7cb2ccf/appcompat/hiddenapi-flags.csv#122683) * (since API 24) [`Landroid/net/ConnectivityManager;->startTethering(IZLandroid/net/ConnectivityManager$OnStartTetheringCallback;Landroid/os/Handler;)V,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/7cb2ccf/appcompat/hiddenapi-flags.csv#122683)
* (since API 24) [`Landroid/net/ConnectivityManager;->stopTethering(I)V,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/7cb2ccf/appcompat/hiddenapi-flags.csv#122685) * (since API 24) [`Landroid/net/ConnectivityManager;->stopTethering(I)V,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/7cb2ccf/appcompat/hiddenapi-flags.csv#122685)
* (since API 23) [`Landroid/net/wifi/WifiConfiguration;->apBand:I,greylist`](https://android.googlesource.com/platform/prebuilts/runtime/+/7cb2ccf/appcompat/hiddenapi-flags.csv#131003)
* (since API 23) [`Landroid/net/wifi/WifiConfiguration;->apChannel:I,greylist`](https://android.googlesource.com/platform/prebuilts/runtime/+/7cb2ccf/appcompat/hiddenapi-flags.csv#131004)
* [`Landroid/net/wifi/WifiManager;->getWifiApConfiguration()Landroid/net/wifi/WifiConfiguration;,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/7cb2ccf/appcompat/hiddenapi-flags.csv#131756) * [`Landroid/net/wifi/WifiManager;->getWifiApConfiguration()Landroid/net/wifi/WifiConfiguration;,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/7cb2ccf/appcompat/hiddenapi-flags.csv#131756)
* [`Landroid/net/wifi/WifiManager;->setWifiApConfiguration(Landroid/net/wifi/WifiConfiguration;)Z,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/7cb2ccf/appcompat/hiddenapi-flags.csv#131825) * [`Landroid/net/wifi/WifiManager;->setWifiApConfiguration(Landroid/net/wifi/WifiConfiguration;)Z,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/7cb2ccf/appcompat/hiddenapi-flags.csv#131825)
* (deprecated since API 26) `Landroid/net/wifi/WifiManager;->setWifiApEnabled(Landroid/net/wifi/WifiConfiguration;Z)Z` * (deprecated since API 26) `Landroid/net/wifi/WifiManager;->setWifiApEnabled(Landroid/net/wifi/WifiConfiguration;Z)Z`

View File

@@ -64,31 +64,30 @@ androidExtensions {
} }
def aux = [ def aux = [
'com.crashlytics.sdk.android:crashlytics:2.9.9', 'com.crashlytics.sdk.android:crashlytics:2.10.0',
'com.google.firebase:firebase-core:16.0.8', 'com.google.firebase:firebase-core:16.0.9',
] ]
def lifecycleVersion = '2.0.0' def lifecycleVersion = '2.0.0'
def roomVersion = '2.1.0-alpha07' def roomVersion = '2.1.0-beta01'
dependencies { dependencies {
kapt "androidx.room:room-compiler:$roomVersion" kapt "androidx.room:room-compiler:$roomVersion"
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.browser:browser:1.0.0' implementation 'androidx.browser:browser:1.0.0'
implementation 'androidx.core:core-ktx:1.0.1' implementation 'androidx.core:core-ktx:1.0.2'
implementation 'androidx.emoji:emoji:1.0.0' implementation 'androidx.emoji:emoji:1.0.0'
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion" implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion" implementation "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
implementation 'androidx.preference:preference:1.1.0-alpha04' implementation 'androidx.preference:preference:1.1.0-alpha05'
implementation "androidx.room:room-ktx:$roomVersion" implementation "androidx.room:room-ktx:$roomVersion"
implementation 'com.android.billingclient:billing:1.2.2' implementation 'com.android.billingclient:billing:2.0.0'
implementation 'com.github.luongvo:BadgeView:1.1.5'
implementation 'com.github.topjohnwu.libsu:core:2.5.0' implementation 'com.github.topjohnwu.libsu:core:2.5.0'
implementation 'com.google.android.material:material:1.0.0' implementation 'com.google.android.material:material:1.1.0-alpha06'
implementation 'com.jakewharton.timber:timber:4.7.1' implementation 'com.jakewharton.timber:timber:4.7.1'
implementation 'com.linkedin.dexmaker:dexmaker:2.25.0' implementation 'com.linkedin.dexmaker:dexmaker:2.25.0'
implementation 'com.takisoft.preferencex:preferencex-simplemenu:1.0.0' implementation 'com.takisoft.preferencex:preferencex-simplemenu:1.0.0'
implementation 'net.glxn.qrgen:android:2.0' implementation 'net.glxn.qrgen:android:2.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1'
for (dep in aux) { for (dep in aux) {
freedomImplementation dep freedomImplementation dep

View File

@@ -50,6 +50,7 @@ class App : Application() {
override fun onFailed(throwable: Throwable?) = Timber.d(throwable) override fun onFailed(throwable: Throwable?) = Timber.d(throwable)
}) })
}) })
EBegFragment.init()
if (DhcpWorkaround.shouldEnable) DhcpWorkaround.enable(true) if (DhcpWorkaround.shouldEnable) DhcpWorkaround.enable(true)
} }

View File

@@ -1,40 +1,66 @@
package be.mygod.vpnhotspot package be.mygod.vpnhotspot
import android.content.DialogInterface
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.Button import android.widget.Button
import android.widget.Spinner import android.widget.Spinner
import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatDialogFragment import androidx.appcompat.app.AppCompatDialogFragment
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.util.launchUrl import be.mygod.vpnhotspot.util.launchUrl
import be.mygod.vpnhotspot.widget.SmartSnackbar import be.mygod.vpnhotspot.widget.SmartSnackbar
import com.android.billingclient.api.* import com.android.billingclient.api.*
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_ebeg.view.* import kotlinx.android.synthetic.main.fragment_ebeg.view.*
import timber.log.Timber import timber.log.Timber
/** /**
* Based on: https://github.com/PrivacyApps/donations/blob/747d36a18433c7e9329691054122a8ad337a62d2/Donations/src/main/java/org/sufficientlysecure/donations/DonationsFragment.java * Based on: https://github.com/PrivacyApps/donations/blob/747d36a18433c7e9329691054122a8ad337a62d2/Donations/src/main/java/org/sufficientlysecure/donations/DonationsFragment.java
*/ */
class EBegFragment : AppCompatDialogFragment(), PurchasesUpdatedListener, BillingClientStateListener, class EBegFragment : AppCompatDialogFragment(), SkuDetailsResponseListener {
SkuDetailsResponseListener, ConsumeResponseListener { companion object : BillingClientStateListener, PurchasesUpdatedListener, ConsumeResponseListener {
@Parcelize private lateinit var billingClient: BillingClient
data class MessageArg(@StringRes val title: Int, @StringRes val message: Int) : Parcelable
class MessageDialogFragment : AlertDialogFragment<MessageArg, Empty>() { fun init() {
override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) { billingClient = BillingClient.newBuilder(app).apply {
setTitle(arg.title) enablePendingPurchases()
setMessage(arg.message) }.setListener(this).build().also { it.startConnection(this) }
setNeutralButton(R.string.donations__button_close, null) }
override fun onBillingSetupFinished(billingResult: BillingResult?) {
if (billingResult?.responseCode == BillingClient.BillingResponseCode.OK) {
billingClient.queryPurchases(BillingClient.SkuType.INAPP).apply {
if (responseCode == BillingClient.BillingResponseCode.OK) {
onPurchasesUpdated(this.billingResult, purchasesList)
}
}
} else Timber.e("onBillingSetupFinished: ${billingResult?.responseCode}")
}
override fun onBillingServiceDisconnected() {
Timber.e("onBillingServiceDisconnected")
billingClient.startConnection(this)
}
override fun onPurchasesUpdated(billingResult: BillingResult?, purchases: MutableList<Purchase>?) {
if (billingResult?.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
// directly consume in-app purchase, so that people can donate multiple times
for (purchase in purchases) if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
billingClient.consumeAsync(ConsumeParams.newBuilder().apply {
setPurchaseToken(purchase.purchaseToken)
}.build(), this)
}
} else Timber.e("onPurchasesUpdated: ${billingResult?.responseCode}")
}
override fun onConsumeResponse(billingResult: BillingResult?, purchaseToken: String?) {
if (billingResult?.responseCode == BillingClient.BillingResponseCode.OK) {
SmartSnackbar.make(R.string.donations__thanks_dialog).show()
} else Timber.e("onConsumeResponse: ${billingResult?.responseCode}")
} }
} }
private lateinit var billingClient: BillingClient
private lateinit var googleSpinner: Spinner private lateinit var googleSpinner: Spinner
private var skus: MutableList<SkuDetails>? = null private var skus: MutableList<SkuDetails>? = null
set(value) { set(value) {
@@ -53,62 +79,27 @@ class EBegFragment : AppCompatDialogFragment(), PurchasesUpdatedListener, Billin
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
dialog!!.setTitle(R.string.settings_misc_donate) dialog!!.setTitle(R.string.settings_misc_donate)
googleSpinner = view.donations__google_android_market_spinner googleSpinner = view.donations__google_android_market_spinner
onBillingServiceDisconnected() billingClient.querySkuDetailsAsync(
SkuDetailsParams.newBuilder().apply {
setSkusList(listOf("donate001", "donate002", "donate005", "donate010", "donate020", "donate050",
"donate100", "donate200", "donatemax"))
setType(BillingClient.SkuType.INAPP)
}.build(), this)
view.donations__google_android_market_donate_button.setOnClickListener { view.donations__google_android_market_donate_button.setOnClickListener {
val sku = skus?.getOrNull(googleSpinner.selectedItemPosition) val sku = skus?.getOrNull(googleSpinner.selectedItemPosition)
if (sku == null) { if (sku != null) billingClient.launchBillingFlow(requireActivity(), BillingFlowParams.newBuilder().apply {
openDialog(R.string.donations__google_android_market_not_supported_title, setSkuDetails(sku)
R.string.donations__google_android_market_not_supported) }.build()) else SmartSnackbar.make(R.string.donations__google_android_market_not_supported).show()
} else billingClient.launchBillingFlow(requireActivity(), BillingFlowParams.newBuilder()
.setSkuDetails(sku).build())
} }
@Suppress("ConstantConditionIf") @Suppress("ConstantConditionIf")
if (BuildConfig.DONATIONS) (view.donations__more_stub.inflate() as Button) if (BuildConfig.DONATIONS) (view.donations__more_stub.inflate() as Button)
.setOnClickListener { requireContext().launchUrl("https://mygod.be/donate/") } .setOnClickListener { requireContext().launchUrl("https://mygod.be/donate/") }
} }
private fun openDialog(@StringRes title: Int, @StringRes message: Int) { override fun onSkuDetailsResponse(billingResult: BillingResult?, skuDetailsList: MutableList<SkuDetails>?) {
val fragmentManager = fragmentManager if (billingResult?.responseCode == BillingClient.BillingResponseCode.OK) skus = skuDetailsList else {
if (fragmentManager == null) SmartSnackbar.make(message).show() else try { Timber.e("onSkuDetailsResponse: ${billingResult?.responseCode}")
MessageDialogFragment().withArg(MessageArg(title, message)).show(fragmentManager, "MessageDialogFragment") SmartSnackbar.make(R.string.donations__google_android_market_not_supported).show()
} catch (e: IllegalStateException) {
SmartSnackbar.make(message).show()
} }
} }
override fun onBillingServiceDisconnected() {
skus = null
billingClient = BillingClient.newBuilder(context ?: return).setListener(this).build()
.also { it.startConnection(this) }
}
override fun onBillingSetupFinished(responseCode: Int) {
if (responseCode == BillingClient.BillingResponse.OK) {
billingClient.querySkuDetailsAsync(
SkuDetailsParams.newBuilder().apply {
setSkusList(listOf("donate001", "donate002", "donate005", "donate010", "donate020", "donate050",
"donate100", "donate200", "donatemax"))
setType(BillingClient.SkuType.INAPP)
}.build(), this)
} else Timber.e("onBillingSetupFinished: $responseCode")
}
override fun onSkuDetailsResponse(responseCode: Int, skuDetailsList: MutableList<SkuDetails>?) {
if (responseCode == BillingClient.BillingResponse.OK) skus = skuDetailsList
else Timber.e("onSkuDetailsResponse: $responseCode")
}
override fun onPurchasesUpdated(responseCode: Int, purchases: MutableList<Purchase>?) {
if (responseCode == BillingClient.BillingResponse.OK && purchases != null) {
// directly consume in-app purchase, so that people can donate multiple times
purchases.forEach { billingClient.consumeAsync(it.purchaseToken, this) }
} else Timber.e("onPurchasesUpdated: $responseCode")
}
override fun onConsumeResponse(responseCode: Int, purchaseToken: String?) {
if (responseCode == BillingClient.BillingResponse.OK) {
openDialog(R.string.donations__thanks_dialog_title, R.string.donations__thanks_dialog)
dismissAllowingStateLoss()
} else Timber.e("onConsumeResponse: $responseCode")
}
} }

View File

@@ -2,7 +2,6 @@ package be.mygod.vpnhotspot
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.Gravity
import android.view.MenuItem import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
@@ -18,13 +17,10 @@ import be.mygod.vpnhotspot.manage.TetheringFragment
import be.mygod.vpnhotspot.net.wifi.WifiDoubleLock import be.mygod.vpnhotspot.net.wifi.WifiDoubleLock
import be.mygod.vpnhotspot.util.ServiceForegroundConnector import be.mygod.vpnhotspot.util.ServiceForegroundConnector
import be.mygod.vpnhotspot.widget.SmartSnackbar import be.mygod.vpnhotspot.widget.SmartSnackbar
import com.google.android.material.bottomnavigation.BottomNavigationMenuView
import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.bottomnavigation.BottomNavigationView
import q.rorbin.badgeview.QBadgeView
class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemSelectedListener { class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemSelectedListener {
private lateinit var binding: ActivityMainBinding private lateinit var binding: ActivityMainBinding
private lateinit var badge: QBadgeView
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -32,15 +28,15 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
binding.lifecycleOwner = this binding.lifecycleOwner = this
binding.navigation.setOnNavigationItemSelectedListener(this) binding.navigation.setOnNavigationItemSelectedListener(this)
if (savedInstanceState == null) displayFragment(TetheringFragment()) if (savedInstanceState == null) displayFragment(TetheringFragment())
badge = QBadgeView(this)
badge.bindTarget((binding.navigation.getChildAt(0) as BottomNavigationMenuView).getChildAt(1))
badge.badgeBackgroundColor = ContextCompat.getColor(this, R.color.colorSecondary)
badge.badgeTextColor = ContextCompat.getColor(this, R.color.primary_text_default_material_light)
badge.badgeGravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
badge.setGravityOffset(16f, 0f, true)
val model = ViewModelProviders.of(this).get<ClientViewModel>() val model = ViewModelProviders.of(this).get<ClientViewModel>()
if (RepeaterService.supported) ServiceForegroundConnector(this, model, RepeaterService::class) if (RepeaterService.supported) ServiceForegroundConnector(this, model, RepeaterService::class)
model.clients.observe(this, Observer { badge.badgeNumber = it.size }) model.clients.observe(this, Observer {
if (it.isNotEmpty()) binding.navigation.showBadge(R.id.navigation_clients).apply {
backgroundColor = ContextCompat.getColor(this@MainActivity, R.color.colorSecondary)
badgeTextColor = ContextCompat.getColor(this@MainActivity, R.color.primary_text_default_material_light)
number = it.size
} else binding.navigation.removeBadge(R.id.navigation_clients)
})
SmartSnackbar.Register(lifecycle, binding.fragmentHolder) SmartSnackbar.Register(lifecycle, binding.fragmentHolder)
WifiDoubleLock.ActivityListener(this) WifiDoubleLock.ActivityListener(this)
} }

View File

@@ -10,7 +10,7 @@
android:orientation="vertical" android:orientation="vertical"
tools:context="be.mygod.vpnhotspot.MainActivity"> tools:context="be.mygod.vpnhotspot.MainActivity">
<androidx.appcompat.widget.Toolbar <com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"

View File

@@ -22,7 +22,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<androidx.appcompat.widget.Toolbar <com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@@ -61,11 +61,9 @@
<string name="donations__button_close">Закрыть</string> <string name="donations__button_close">Закрыть</string>
<string name="donations__description">Считаете это приложение полезным?\nПоддержите его разработку, отправив пожертвование разработчику!</string> <string name="donations__description">Считаете это приложение полезным?\nПоддержите его разработку, отправив пожертвование разработчику!</string>
<string name="donations__google_android_market">Google Play Store</string> <string name="donations__google_android_market">Google Play Store</string>
<string name="donations__google_android_market_not_supported_title">In-App пожертвования не поддерживаются.</string>
<string name="donations__google_android_market_not_supported">Пожертвования через приложение не поддерживаются. Google Play Store установлен правильно?</string> <string name="donations__google_android_market_not_supported">Пожертвования через приложение не поддерживаются. Google Play Store установлен правильно?</string>
<string name="donations__google_android_market_description">Google взимает 30% комиссии с каждого пожертвования!</string> <string name="donations__google_android_market_description">Google взимает 30% комиссии с каждого пожертвования!</string>
<string name="donations__google_android_market_donate_button">Пожертвовать!</string> <string name="donations__google_android_market_donate_button">Пожертвовать!</string>
<string name="donations__google_android_market_text">Сколько?</string> <string name="donations__google_android_market_text">Сколько?</string>
<string name="donations__thanks_dialog_title">Благодарю!</string>
<string name="donations__thanks_dialog">Благодарю за пожертвование! Я очень это ценю!</string> <string name="donations__thanks_dialog">Благодарю за пожертвование! Я очень это ценю!</string>
</resources> </resources>

View File

@@ -144,11 +144,9 @@
<!-- Based on: https://github.com/PrivacyApps/donations/blob/747d36a18433c7e9329691054122a8ad337a62d2/Donations/src/main/res/values-zh/donations__strings.xml --> <!-- Based on: https://github.com/PrivacyApps/donations/blob/747d36a18433c7e9329691054122a8ad337a62d2/Donations/src/main/res/values-zh/donations__strings.xml -->
<string name="donations__button_close">关闭</string> <string name="donations__button_close">关闭</string>
<string name="donations__google_android_market">Google Play 商店</string> <string name="donations__google_android_market">Google Play 商店</string>
<string name="donations__google_android_market_not_supported_title">不支持 In-App 捐赠。</string>
<string name="donations__google_android_market_not_supported">不支持 In-App 捐赠。你的 Google Play 商店是否安装正确了呢?</string> <string name="donations__google_android_market_not_supported">不支持 In-App 捐赠。你的 Google Play 商店是否安装正确了呢?</string>
<string name="donations__google_android_market_donate_button">捐赠!</string> <string name="donations__google_android_market_donate_button">捐赠!</string>
<string name="donations__google_android_market_text">捐赠多少?</string> <string name="donations__google_android_market_text">捐赠多少?</string>
<string name="donations__thanks_dialog_title">谢谢!</string>
<string name="donations__thanks_dialog">谢谢捐赠!\n非常感谢您</string> <string name="donations__thanks_dialog">谢谢捐赠!\n非常感谢您</string>
<string name="donations__description">觉得此应用很有用?\n捐赠给该开发者以支持此应用的开发</string> <string name="donations__description">觉得此应用很有用?\n捐赠给该开发者以支持此应用的开发</string>

View File

@@ -153,11 +153,9 @@
<string name="donations__button_close">Close</string> <string name="donations__button_close">Close</string>
<string name="donations__description">Do you find this application useful?\nSupport its development by sending a donation to the developer!</string> <string name="donations__description">Do you find this application useful?\nSupport its development by sending a donation to the developer!</string>
<string name="donations__google_android_market">Google Play Store</string> <string name="donations__google_android_market">Google Play Store</string>
<string name="donations__google_android_market_not_supported_title">In-App Donations are not supported.</string>
<string name="donations__google_android_market_not_supported">In-App Donations are not supported. Is Google Play Store installed correctly?</string> <string name="donations__google_android_market_not_supported">In-App Donations are not supported. Is Google Play Store installed correctly?</string>
<string name="donations__google_android_market_description">Google charges a fee of 30%</string> <string name="donations__google_android_market_description">Google charges a fee of 30%</string>
<string name="donations__google_android_market_donate_button">Donate!</string> <string name="donations__google_android_market_donate_button">Donate!</string>
<string name="donations__google_android_market_text">How much?</string> <string name="donations__google_android_market_text">How much?</string>
<string name="donations__thanks_dialog_title">Thanks!</string>
<string name="donations__thanks_dialog">Thanks for donating!\nI really appreciate this!</string> <string name="donations__thanks_dialog">Thanks for donating!\nI really appreciate this!</string>
</resources> </resources>