taler-android

Android apps for GNU Taler (wallet, PoS, cashier)
Log | Files | Refs | README | LICENSE

Utils.kt (7336B)


      1 /*
      2  * This file is part of GNU Taler
      3  * (C) 2020 Taler Systems S.A.
      4  *
      5  * GNU Taler is free software; you can redistribute it and/or modify it under the
      6  * terms of the GNU General Public License as published by the Free Software
      7  * Foundation; either version 3, or (at your option) any later version.
      8  *
      9  * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
     10  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11  * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12  *
     13  * You should have received a copy of the GNU General Public License along with
     14  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15  */
     16 
     17 package net.taler.wallet
     18 
     19 import android.content.Context
     20 import android.net.ConnectivityManager
     21 import android.net.ConnectivityManager.NetworkCallback
     22 import android.net.NetworkCapabilities
     23 import android.net.NetworkRequest
     24 import android.net.Uri
     25 import android.net.wifi.WifiConfiguration
     26 import android.net.wifi.WifiManager
     27 import android.net.wifi.WifiNetworkSpecifier
     28 import android.os.Build.VERSION.SDK_INT
     29 import android.util.TypedValue
     30 import android.widget.Toast
     31 import android.widget.Toast.LENGTH_LONG
     32 import androidx.annotation.RequiresApi
     33 import androidx.browser.customtabs.CustomTabsIntent
     34 import androidx.compose.foundation.layout.Spacer
     35 import androidx.compose.foundation.layout.WindowInsets
     36 import androidx.compose.foundation.layout.WindowInsetsSides
     37 import androidx.compose.foundation.layout.only
     38 import androidx.compose.foundation.layout.systemBars
     39 import androidx.compose.foundation.layout.windowInsetsBottomHeight
     40 import androidx.compose.foundation.layout.windowInsetsPadding
     41 import androidx.compose.runtime.Composable
     42 import androidx.compose.runtime.DisposableEffect
     43 import androidx.compose.runtime.getValue
     44 import androidx.compose.runtime.rememberCoroutineScope
     45 import androidx.compose.runtime.rememberUpdatedState
     46 import androidx.compose.ui.Modifier
     47 import androidx.compose.ui.geometry.Size
     48 import androidx.compose.ui.graphics.Canvas
     49 import androidx.compose.ui.graphics.ImageBitmap
     50 import androidx.compose.ui.graphics.drawscope.CanvasDrawScope
     51 import androidx.compose.ui.graphics.painter.Painter
     52 import androidx.compose.ui.unit.Density
     53 import androidx.compose.ui.unit.LayoutDirection
     54 import androidx.core.content.getSystemService
     55 import androidx.fragment.app.Fragment
     56 import androidx.fragment.app.FragmentActivity
     57 import kotlinx.coroutines.CoroutineScope
     58 import kotlinx.coroutines.delay
     59 import kotlinx.coroutines.launch
     60 import kotlinx.serialization.json.Json
     61 import net.taler.common.Amount
     62 import net.taler.common.AmountParserException
     63 import net.taler.lib.android.showError
     64 import net.taler.lib.android.startActivitySafe
     65 import net.taler.wallet.backend.TalerErrorInfo
     66 
     67 const val CURRENCY_BTC = "BITCOINBTC"
     68 
     69 fun connectToWifi(context: Context, ssid: String) {
     70     if (SDK_INT >= 29) {
     71         connectToWifi29(context, ssid)
     72     } else {
     73         connectToWifiDeprecated(context, ssid)
     74     }
     75 }
     76 
     77 @RequiresApi(29)
     78 private fun connectToWifi29(context: Context, ssid: String) {
     79     val wifiManager = context.getSystemService(WifiManager::class.java)
     80     if (wifiManager?.isWifiEnabled == false) {
     81         // we are not allowed to enable the WiFi anymore, so show at least a hint about it
     82         Toast.makeText(context, R.string.wifi_disabled_error, LENGTH_LONG).show()
     83     }
     84 
     85     val specifier = WifiNetworkSpecifier.Builder()
     86         .setSsid(ssid)
     87         .build()
     88     val request = NetworkRequest.Builder()
     89         .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
     90         .setNetworkSpecifier(specifier)
     91         .build()
     92     val connectivityManager = context.getSystemService(ConnectivityManager::class.java)
     93     connectivityManager?.requestNetwork(request, NetworkCallback())
     94 }
     95 
     96 @Suppress("DEPRECATION")
     97 private fun connectToWifiDeprecated(context: Context, ssid: String) {
     98     context.getSystemService<WifiManager>()?.apply {
     99         if (!isWifiEnabled) {
    100             val enabledResult = setWifiEnabled(true)
    101             while (enabledResult && !isWifiEnabled) Thread.sleep(25)
    102         }
    103         val wifiConfig = WifiConfiguration().apply {
    104             SSID = "\"$ssid\""
    105             allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE)
    106         }
    107         addNetwork(wifiConfig).let { netId ->
    108             if (netId == -1) {
    109                 val str = context.getString(R.string.wifi_connect_error, ssid)
    110                 Toast.makeText(context, str, LENGTH_LONG).show()
    111             } else {
    112                 disconnect()
    113                 enableNetwork(netId, true)
    114                 reconnect()
    115             }
    116         }
    117     }
    118 }
    119 
    120 fun cleanExchange(exchange: String) = exchange.let {
    121     if (it.startsWith("https://", ignoreCase = true)) it.substring(8) else it
    122 }.trimEnd('/')
    123 
    124 fun getAmount(currency: String, text: String): Amount? {
    125     return try {
    126         Amount.fromString(currency, text)
    127     } catch (e: AmountParserException) {
    128         null
    129     }
    130 }
    131 
    132 fun Context.getAttrColor(attr: Int): Int {
    133     val value = TypedValue()
    134     theme.resolveAttribute(attr, value, true)
    135     return value.data
    136 }
    137 
    138 fun launchInAppBrowser(context: Context, url: String) {
    139     val builder = CustomTabsIntent.Builder()
    140     val intent = builder.build().intent
    141     intent.data = Uri.parse(url)
    142     context.startActivitySafe(intent)
    143 }
    144 
    145 fun Fragment.showError(error: TalerErrorInfo) {
    146     @Suppress("OPT_IN_USAGE")
    147     val json = Json {
    148         prettyPrint = true
    149         prettyPrintIndent = "  "
    150     }
    151     val message = json.encodeToString(error)
    152     showError(message)
    153 }
    154 
    155 fun FragmentActivity.showError(error: TalerErrorInfo) {
    156     @Suppress("OPT_IN_USAGE")
    157     val json = Json {
    158         prettyPrint = true
    159         prettyPrintIndent = "  "
    160     }
    161     val message = json.encodeToString(error)
    162     showError(message)
    163 }
    164 
    165 fun Context.getThemeColor(attr: Int): Int {
    166     val typedValue = TypedValue()
    167     theme.resolveAttribute(attr, typedValue, true)
    168     return typedValue.data
    169 }
    170 
    171 @Composable
    172 fun <T> T.useDebounce(
    173     delayMillis: Long = 300L,
    174     coroutineScope: CoroutineScope = rememberCoroutineScope(),
    175     onChange: suspend (T) -> Unit
    176 ): T{
    177     val state by rememberUpdatedState(this)
    178 
    179     DisposableEffect(state){
    180         val job = coroutineScope.launch {
    181             delay(delayMillis)
    182             onChange(state)
    183         }
    184         onDispose {
    185             job.cancel()
    186         }
    187     }
    188 
    189     return state
    190 }
    191 
    192 @Composable
    193 fun BottomInsetsSpacer() = Spacer(
    194     Modifier.windowInsetsBottomHeight(
    195         WindowInsets.systemBars,
    196     ),
    197 )
    198 
    199 @Composable
    200 fun Modifier.systemBarsPaddingBottom() =
    201     windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Bottom))
    202 
    203 @Composable
    204 fun Modifier.systemBarsPaddingHorizontal() =
    205     windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Horizontal))
    206 
    207 @Composable
    208 fun Modifier.systemBarsPaddingAllExceptTop() =
    209     windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom))
    210 
    211 fun Painter.toImageBitmap(
    212     size: Size,
    213     density: Density,
    214     layoutDirection: LayoutDirection,
    215 ): ImageBitmap {
    216     val bitmap = ImageBitmap(size.width.toInt(), size.height.toInt())
    217     val canvas = Canvas(bitmap)
    218     CanvasDrawScope().draw(density, layoutDirection, canvas, size) {
    219         draw(size)
    220     }
    221     return bitmap
    222 }