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

View File

@@ -64,31 +64,30 @@ androidExtensions {
}
def aux = [
'com.crashlytics.sdk.android:crashlytics:2.9.9',
'com.google.firebase:firebase-core:16.0.8',
'com.crashlytics.sdk.android:crashlytics:2.10.0',
'com.google.firebase:firebase-core:16.0.9',
]
def lifecycleVersion = '2.0.0'
def roomVersion = '2.1.0-alpha07'
def roomVersion = '2.1.0-beta01'
dependencies {
kapt "androidx.room:room-compiler:$roomVersion"
implementation fileTree(dir: 'libs', include: ['*.jar'])
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.lifecycle:lifecycle-common-java8:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-extensions:$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 'com.android.billingclient:billing:1.2.2'
implementation 'com.github.luongvo:BadgeView:1.1.5'
implementation 'com.android.billingclient:billing:2.0.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.linkedin.dexmaker:dexmaker:2.25.0'
implementation 'com.takisoft.preferencex:preferencex-simplemenu:1.0.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'
for (dep in aux) {
freedomImplementation dep

View File

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

View File

@@ -1,40 +1,66 @@
package be.mygod.vpnhotspot
import android.content.DialogInterface
import android.os.Bundle
import android.os.Parcelable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.Spinner
import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatDialogFragment
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.util.launchUrl
import be.mygod.vpnhotspot.widget.SmartSnackbar
import com.android.billingclient.api.*
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_ebeg.view.*
import timber.log.Timber
/**
* Based on: https://github.com/PrivacyApps/donations/blob/747d36a18433c7e9329691054122a8ad337a62d2/Donations/src/main/java/org/sufficientlysecure/donations/DonationsFragment.java
*/
class EBegFragment : AppCompatDialogFragment(), PurchasesUpdatedListener, BillingClientStateListener,
SkuDetailsResponseListener, ConsumeResponseListener {
@Parcelize
data class MessageArg(@StringRes val title: Int, @StringRes val message: Int) : Parcelable
class MessageDialogFragment : AlertDialogFragment<MessageArg, Empty>() {
override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) {
setTitle(arg.title)
setMessage(arg.message)
setNeutralButton(R.string.donations__button_close, null)
class EBegFragment : AppCompatDialogFragment(), SkuDetailsResponseListener {
companion object : BillingClientStateListener, PurchasesUpdatedListener, ConsumeResponseListener {
private lateinit var billingClient: BillingClient
fun init() {
billingClient = BillingClient.newBuilder(app).apply {
enablePendingPurchases()
}.setListener(this).build().also { it.startConnection(this) }
}
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 var skus: MutableList<SkuDetails>? = null
set(value) {
@@ -53,62 +79,27 @@ class EBegFragment : AppCompatDialogFragment(), PurchasesUpdatedListener, Billin
super.onViewCreated(view, savedInstanceState)
dialog!!.setTitle(R.string.settings_misc_donate)
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 {
val sku = skus?.getOrNull(googleSpinner.selectedItemPosition)
if (sku == null) {
openDialog(R.string.donations__google_android_market_not_supported_title,
R.string.donations__google_android_market_not_supported)
} else billingClient.launchBillingFlow(requireActivity(), BillingFlowParams.newBuilder()
.setSkuDetails(sku).build())
if (sku != null) billingClient.launchBillingFlow(requireActivity(), BillingFlowParams.newBuilder().apply {
setSkuDetails(sku)
}.build()) else SmartSnackbar.make(R.string.donations__google_android_market_not_supported).show()
}
@Suppress("ConstantConditionIf")
if (BuildConfig.DONATIONS) (view.donations__more_stub.inflate() as Button)
.setOnClickListener { requireContext().launchUrl("https://mygod.be/donate/") }
}
private fun openDialog(@StringRes title: Int, @StringRes message: Int) {
val fragmentManager = fragmentManager
if (fragmentManager == null) SmartSnackbar.make(message).show() else try {
MessageDialogFragment().withArg(MessageArg(title, message)).show(fragmentManager, "MessageDialogFragment")
} catch (e: IllegalStateException) {
SmartSnackbar.make(message).show()
override fun onSkuDetailsResponse(billingResult: BillingResult?, skuDetailsList: MutableList<SkuDetails>?) {
if (billingResult?.responseCode == BillingClient.BillingResponseCode.OK) skus = skuDetailsList else {
Timber.e("onSkuDetailsResponse: ${billingResult?.responseCode}")
SmartSnackbar.make(R.string.donations__google_android_market_not_supported).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.os.Bundle
import android.view.Gravity
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
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.util.ServiceForegroundConnector
import be.mygod.vpnhotspot.widget.SmartSnackbar
import com.google.android.material.bottomnavigation.BottomNavigationMenuView
import com.google.android.material.bottomnavigation.BottomNavigationView
import q.rorbin.badgeview.QBadgeView
class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemSelectedListener {
private lateinit var binding: ActivityMainBinding
private lateinit var badge: QBadgeView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -32,15 +28,15 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
binding.lifecycleOwner = this
binding.navigation.setOnNavigationItemSelectedListener(this)
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>()
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)
WifiDoubleLock.ActivityListener(this)
}

View File

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

View File

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

View File

@@ -61,11 +61,9 @@
<string name="donations__button_close">Закрыть</string>
<string name="donations__description">Считаете это приложение полезным?\nПоддержите его разработку, отправив пожертвование разработчику!</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_description">Google взимает 30% комиссии с каждого пожертвования!</string>
<string name="donations__google_android_market_donate_button">Пожертвовать!</string>
<string name="donations__google_android_market_text">Сколько?</string>
<string name="donations__thanks_dialog_title">Благодарю!</string>
<string name="donations__thanks_dialog">Благодарю за пожертвование! Я очень это ценю!</string>
</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 -->
<string name="donations__button_close">关闭</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_donate_button">捐赠!</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__description">觉得此应用很有用?\n捐赠给该开发者以支持此应用的开发</string>

View File

@@ -153,11 +153,9 @@
<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__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_description">Google charges a fee of 30%</string>
<string name="donations__google_android_market_donate_button">Donate!</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>
</resources>