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 }