mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-10 08:27:04 +00:00
refactor: adopt androidx.core KTX helpers (Bitmap/Uri/SharedPreferences)
This commit is contained in:
@@ -282,6 +282,26 @@ Do this before considering the task complete.
|
||||
- The only acceptable inline fully-qualified names are: a genuine name
|
||||
collision (prefer `import ... as Alias` instead), or where the language
|
||||
requires it. Comments, KDoc, and string literals are exempt.
|
||||
- **Prefer the `androidx.core` KTX extension over the raw platform Java call**
|
||||
when one exists — this is what Android Lint's `UseKtx` flags. Common swaps:
|
||||
`Bitmap.createBitmap(w, h, cfg)` → `createBitmap(w, h)`,
|
||||
`Bitmap.createScaledBitmap(src, w, h, f)` → `src.scale(w, h, f)`,
|
||||
`Uri.parse(s)` → `s.toUri()`, and `prefs.edit()…apply()` → `prefs.edit { }`.
|
||||
Only adopt the KTX form when it's behaviour-preserving: keep any explicit
|
||||
argument that differs from the extension's default (a non-`ARGB_8888`
|
||||
`Bitmap.Config`, `scale(filter = false)`), and leave calls the KTX has no
|
||||
equivalent for (e.g. the `createBitmap` pixels/matrix overloads, or a
|
||||
conditional-`apply()` editor loop) untouched.
|
||||
- **This "prefer the KTX sugar" rule does NOT extend to collection operators.**
|
||||
The KTX preference is about platform wrappers (`Bitmap`/`Uri`/`SharedPreferences`),
|
||||
which compile to the identical call. Collections are the opposite: in hot
|
||||
event/parse paths Quartz deliberately uses raw JVM arrays (`TagArray =
|
||||
Array<Array<String>>`) and the inline `fast*` operators (`fastForEach`,
|
||||
`fastAny`, `fastFirstOrNull`, `fastFirstNotNullOfOrNull`, … in
|
||||
`nip01Core/core/TagArray.kt`) instead of Kotlin `List` + stdlib
|
||||
`forEach`/`map`/`filter`/`any` — the `fast*` variants allocate no iterator,
|
||||
no intermediate list, and no lambda object. Don't "modernize" those into
|
||||
stdlib collection calls; match the surrounding hot-path style.
|
||||
|
||||
### Navigation Shell
|
||||
- **Desktop**: Sidebar + main content area
|
||||
|
||||
@@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import androidx.core.graphics.createBitmap
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.vitorpamplona.amethyst.model.AccountSettings
|
||||
@@ -81,7 +82,7 @@ class ImageUploadTesting {
|
||||
.build()
|
||||
|
||||
private fun getBitmap(): ByteArray {
|
||||
val bitmap = Bitmap.createBitmap(200, 300, Bitmap.Config.ARGB_8888)
|
||||
val bitmap = createBitmap(200, 300)
|
||||
for (x in 0 until bitmap.width) {
|
||||
for (y in 0 until bitmap.height) {
|
||||
bitmap.setPixel(x, y, Color.rgb(Random.nextInt(), Random.nextInt(), Random.nextInt()))
|
||||
|
||||
+2
-1
@@ -21,6 +21,7 @@
|
||||
package com.vitorpamplona.amethyst.service.images
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import androidx.core.graphics.createBitmap
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import org.junit.After
|
||||
@@ -44,7 +45,7 @@ class ThumbnailDiskCacheInstrumentedTest {
|
||||
cacheDir = File(appContext.cacheDir, "thumbnail-test-${UUID.randomUUID()}")
|
||||
cache = ThumbnailDiskCache(cacheDir)
|
||||
sourceFile = File(appContext.cacheDir, "source-${UUID.randomUUID()}.jpg")
|
||||
val bitmap = Bitmap.createBitmap(64, 64, Bitmap.Config.ARGB_8888)
|
||||
val bitmap = createBitmap(64, 64)
|
||||
sourceFile.outputStream().use { bitmap.compress(Bitmap.CompressFormat.JPEG, 90, it) }
|
||||
bitmap.recycle()
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import android.os.Messenger
|
||||
import android.os.RemoteException
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import androidx.core.net.toUri
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.commons.favorites.FavoriteApp
|
||||
import com.vitorpamplona.amethyst.commons.napplet.NappletBroker
|
||||
@@ -359,7 +360,7 @@ class NappletBrokerService : Service() {
|
||||
val intent =
|
||||
Intent(applicationContext, MainActivity::class.java).apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
data = Uri.parse("nostr:connectedapp?coordinate=" + Uri.encode(coordinate))
|
||||
data = ("nostr:connectedapp?coordinate=" + Uri.encode(coordinate)).toUri()
|
||||
}
|
||||
runCatching { applicationContext.startActivity(intent) }
|
||||
.onFailure { Log.w("NappletBrokerService", "Could not open Connected Apps detail", it) }
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
package com.vitorpamplona.amethyst.napplet
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.core.net.toUri
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
@@ -86,7 +86,7 @@ object WebAppNetworkRegistry {
|
||||
}
|
||||
|
||||
/** The host key for [url] (e.g. `vitorpamplona.com`), or the raw string if it has no host. */
|
||||
fun hostKeyOf(url: String): String = runCatching { Uri.parse(url).host }.getOrNull()?.takeIf { it.isNotBlank() } ?: url
|
||||
fun hostKeyOf(url: String): String = runCatching { url.toUri().host }.getOrNull()?.takeIf { it.isNotBlank() } ?: url
|
||||
|
||||
/** Whether the site behind [url] routes through Tor. Defaults to true (Tor) for any site never set. */
|
||||
fun useTor(url: String): Boolean = modes[hostKeyOf(url)] ?: true
|
||||
|
||||
+2
-1
@@ -27,6 +27,7 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.net.toUri
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.MainActivity
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
@@ -64,7 +65,7 @@ object CalendarReminderNotifier {
|
||||
val tapIntent =
|
||||
Intent(context, MainActivity::class.java).apply {
|
||||
action = Intent.ACTION_VIEW
|
||||
data = android.net.Uri.parse(deepLink)
|
||||
data = deepLink.toUri()
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
}
|
||||
val tapPendingIntent =
|
||||
|
||||
+3
-2
@@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.service.calendar
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
|
||||
/**
|
||||
* Device-wide preferences for the calendar reminder worker.
|
||||
@@ -40,13 +41,13 @@ class CalendarReminderPrefs(
|
||||
fun isEnabled(): Boolean = prefs.getBoolean(KEY_ENABLED, DEFAULT_ENABLED)
|
||||
|
||||
fun setEnabled(enabled: Boolean) {
|
||||
prefs.edit().putBoolean(KEY_ENABLED, enabled).apply()
|
||||
prefs.edit { putBoolean(KEY_ENABLED, enabled) }
|
||||
}
|
||||
|
||||
fun leadMinutes(): Int = prefs.getInt(KEY_LEAD_MINUTES, DEFAULT_LEAD_MINUTES)
|
||||
|
||||
fun setLeadMinutes(minutes: Int) {
|
||||
prefs.edit().putInt(KEY_LEAD_MINUTES, minutes).apply()
|
||||
prefs.edit { putInt(KEY_LEAD_MINUTES, minutes) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
+2
-1
@@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.service.calendar
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
|
||||
/**
|
||||
* Persistent "I've already notified for this event" set. Backed by [SharedPreferences] because
|
||||
@@ -54,7 +55,7 @@ class CalendarReminderStore(
|
||||
eventId: String,
|
||||
eventStartSeconds: Long,
|
||||
) {
|
||||
prefs.edit().putLong(keyFor(eventId), eventStartSeconds).apply()
|
||||
prefs.edit { putLong(keyFor(eventId), eventStartSeconds) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+2
-1
@@ -25,6 +25,7 @@ import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Path
|
||||
import androidx.core.graphics.createBitmap
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
@@ -57,7 +58,7 @@ fun roadEventPinBitmap(
|
||||
val cx = width / 2f
|
||||
val cy = pad + radius
|
||||
|
||||
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
val bitmap = createBitmap(width, height)
|
||||
val canvas = Canvas(bitmap)
|
||||
|
||||
// Head circle + pointer drawn as a single path so they share one shadow
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.note.types
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -46,6 +45,7 @@ import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.graphics.createBitmap
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
@@ -160,8 +160,7 @@ private fun Ps1SaveIconImage(icon: Ps1SaveIcon) {
|
||||
val frames =
|
||||
remember(icon) {
|
||||
icon.frames.map { pixels ->
|
||||
Bitmap
|
||||
.createBitmap(Ps1SaveIcon.SIZE, Ps1SaveIcon.SIZE, Bitmap.Config.ARGB_8888)
|
||||
createBitmap(Ps1SaveIcon.SIZE, Ps1SaveIcon.SIZE)
|
||||
.apply { setPixels(pixels, 0, Ps1SaveIcon.SIZE, 0, 0, Ps1SaveIcon.SIZE, Ps1SaveIcon.SIZE) }
|
||||
.asImageBitmap()
|
||||
}
|
||||
|
||||
+3
-3
@@ -20,7 +20,6 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.browser
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.annotation.RequiresApi
|
||||
@@ -44,6 +43,7 @@ import androidx.compose.ui.layout.boundsInWindow
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.R
|
||||
@@ -193,14 +193,14 @@ private fun EmbeddedWebAppTab(
|
||||
}
|
||||
|
||||
/** The host of [url] for the tab title, falling back to the raw string. */
|
||||
internal fun hostLabel(url: String): String = runCatching { Uri.parse(url).host }.getOrNull()?.takeIf { it.isNotBlank() } ?: url
|
||||
internal fun hostLabel(url: String): String = runCatching { url.toUri().host }.getOrNull()?.takeIf { it.isNotBlank() } ?: url
|
||||
|
||||
/**
|
||||
* The `scheme://host[:port]` origin of [url] — the exact form the sandbox reports for NIP-07 consent, so
|
||||
* it matches the `browser:<origin>` permission-ledger key. Null when [url] has no usable scheme/host.
|
||||
*/
|
||||
internal fun browserOrigin(url: String): String? {
|
||||
val uri = runCatching { Uri.parse(url) }.getOrNull() ?: return null
|
||||
val uri = runCatching { url.toUri() }.getOrNull() ?: return null
|
||||
val scheme = uri.scheme?.takeIf { it.isNotBlank() } ?: return null
|
||||
val host = uri.host?.takeIf { it.isNotBlank() } ?: return null
|
||||
return "$scheme://$host" + if (uri.port > 0) ":${uri.port}" else ""
|
||||
|
||||
+3
-2
@@ -59,6 +59,7 @@ import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.net.toUri
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
@@ -582,11 +583,11 @@ private fun LocationRow(location: String) {
|
||||
val trimmed = location.trim()
|
||||
val intent =
|
||||
if (isUrl) {
|
||||
Intent(Intent.ACTION_VIEW, Uri.parse(trimmed))
|
||||
Intent(Intent.ACTION_VIEW, trimmed.toUri())
|
||||
} else {
|
||||
// `geo:0,0?q=<location>` is the Android geo intent; the user's
|
||||
// installed maps app handles it.
|
||||
Intent(Intent.ACTION_VIEW, Uri.parse("geo:0,0?q=${Uri.encode(trimmed)}"))
|
||||
Intent(Intent.ACTION_VIEW, "geo:0,0?q=${Uri.encode(trimmed)}".toUri())
|
||||
}
|
||||
// runCatching swallows ActivityNotFoundException when no handler is
|
||||
// installed — we don't have anywhere useful to fall back to.
|
||||
|
||||
+2
-1
@@ -21,6 +21,7 @@
|
||||
package com.vitorpamplona.amethyst.commons.blurhash
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import androidx.core.graphics.scale
|
||||
|
||||
actual class PlatformImage(
|
||||
val bitmap: Bitmap,
|
||||
@@ -43,7 +44,7 @@ actual class PlatformImage(
|
||||
actual fun scale(
|
||||
width: Int,
|
||||
height: Int,
|
||||
): PlatformImage = PlatformImage(Bitmap.createScaledBitmap(bitmap, width, height, false))
|
||||
): PlatformImage = PlatformImage(bitmap.scale(width, height, false))
|
||||
|
||||
actual companion object {
|
||||
actual fun create(
|
||||
|
||||
+3
-2
@@ -21,6 +21,7 @@
|
||||
package com.vitorpamplona.amethyst.commons.keystorage
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.edit
|
||||
import androidx.security.crypto.EncryptedSharedPreferences
|
||||
import androidx.security.crypto.MasterKey
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -86,7 +87,7 @@ actual class SecureKeyStorage private actual constructor() {
|
||||
) {
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
encryptedPrefs.edit().putString(KEY_PREFIX + npub, privKeyHex).apply()
|
||||
encryptedPrefs.edit { putString(KEY_PREFIX + npub, privKeyHex) }
|
||||
} catch (e: Exception) {
|
||||
throw SecureStorageException("Failed to save private key", e)
|
||||
}
|
||||
@@ -108,7 +109,7 @@ actual class SecureKeyStorage private actual constructor() {
|
||||
val key = KEY_PREFIX + npub
|
||||
val existed = encryptedPrefs.contains(key)
|
||||
if (existed) {
|
||||
encryptedPrefs.edit().remove(key).apply()
|
||||
encryptedPrefs.edit { remove(key) }
|
||||
}
|
||||
existed
|
||||
} catch (e: Exception) {
|
||||
|
||||
+2
-1
@@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.commons.nip64Chess
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
|
||||
actual class ChessDismissedGamesStorage private actual constructor() {
|
||||
private var prefs: SharedPreferences? = null
|
||||
@@ -48,6 +49,6 @@ actual class ChessDismissedGamesStorage private actual constructor() {
|
||||
userPubkey: String,
|
||||
ids: Set<String>,
|
||||
) {
|
||||
prefs?.edit()?.putStringSet(prefsKey(userPubkey), ids)?.apply()
|
||||
prefs?.edit { putStringSet(prefsKey(userPubkey), ids) }
|
||||
}
|
||||
}
|
||||
|
||||
+7
-5
@@ -54,6 +54,8 @@ import androidx.activity.ComponentActivity
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.graphics.scale
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.webkit.JavaScriptReplyProxy
|
||||
@@ -456,7 +458,7 @@ class NappletBrowserActivity : ComponentActivity() {
|
||||
runCatching {
|
||||
val scaled =
|
||||
if (icon.width > ICON_MAX_PX || icon.height > ICON_MAX_PX) {
|
||||
Bitmap.createScaledBitmap(icon, ICON_MAX_PX, ICON_MAX_PX, true)
|
||||
icon.scale(ICON_MAX_PX, ICON_MAX_PX)
|
||||
} else {
|
||||
icon
|
||||
}
|
||||
@@ -597,7 +599,7 @@ class NappletBrowserActivity : ComponentActivity() {
|
||||
// Key the persisted choice on the host actually displayed (which may differ from startUrl after
|
||||
// in-page navigation), so the preference sticks to the right site.
|
||||
val liveUrl = if (this::webView.isInitialized) webView.url ?: startUrl else startUrl
|
||||
val host = runCatching { Uri.parse(liveUrl).host }.getOrNull()?.takeIf { it.isNotBlank() } ?: return
|
||||
val host = runCatching { liveUrl.toUri().host }.getOrNull()?.takeIf { it.isNotBlank() } ?: return
|
||||
val msg =
|
||||
Message.obtain(null, NappletIpc.MSG_SET_WEB_TOR).apply {
|
||||
data =
|
||||
@@ -613,7 +615,7 @@ class NappletBrowserActivity : ComponentActivity() {
|
||||
|
||||
private var title: String = ""
|
||||
|
||||
private fun barTitle(): String = title.ifBlank { runCatching { Uri.parse(startUrl).host }.getOrNull() ?: getString(CommonsR.string.napplet_untitled) }
|
||||
private fun barTitle(): String = title.ifBlank { runCatching { startUrl.toUri().host }.getOrNull() ?: getString(CommonsR.string.napplet_untitled) }
|
||||
|
||||
/**
|
||||
* The top pull-down sheet: a small grabber at the top edge (out of the corner where a site shows its
|
||||
@@ -644,7 +646,7 @@ class NappletBrowserActivity : ComponentActivity() {
|
||||
*/
|
||||
private fun openPermissions() {
|
||||
val liveUrl = if (this::webView.isInitialized) webView.url ?: startUrl else startUrl
|
||||
val uri = runCatching { Uri.parse(liveUrl) }.getOrNull() ?: return
|
||||
val uri = runCatching { liveUrl.toUri() }.getOrNull() ?: return
|
||||
val scheme = uri.scheme?.takeIf { it.isNotBlank() } ?: return
|
||||
val host = uri.host?.takeIf { it.isNotBlank() } ?: return
|
||||
val origin = "$scheme://$host" + if (uri.port > 0) ":${uri.port}" else ""
|
||||
@@ -774,6 +776,6 @@ class NappletBrowserActivity : ComponentActivity() {
|
||||
.putExtra(EXTRA_THEME, theme)
|
||||
.putExtra(EXTRA_IS_FAVORITE, isFavorite)
|
||||
// Distinct task identity per URL for documentLaunchMode=intoExisting.
|
||||
.setData(Uri.parse(url))
|
||||
.setData(url.toUri())
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -45,6 +45,7 @@ import android.webkit.WebSettings
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.graphics.createBitmap
|
||||
import androidx.privacysandbox.ui.provider.toCoreLibInfo
|
||||
import androidx.webkit.JavaScriptReplyProxy
|
||||
import androidx.webkit.WebMessageCompat
|
||||
@@ -206,7 +207,7 @@ class NappletBrowserService : Service() {
|
||||
val outW = (boxW * zoom).toInt().coerceAtLeast(1)
|
||||
val outH = (boxH * zoom).toInt().coerceAtLeast(1)
|
||||
val t0 = SystemClock.elapsedRealtimeNanos()
|
||||
val bitmap = Bitmap.createBitmap(outW, outH, Bitmap.Config.ARGB_8888)
|
||||
val bitmap = createBitmap(outW, outH)
|
||||
val canvas = Canvas(bitmap)
|
||||
canvas.drawColor(tab.bgColor)
|
||||
// Map the source rect (centered on cx,cy in view px) into the zoomed output bitmap.
|
||||
|
||||
+2
-1
@@ -45,6 +45,7 @@ import android.webkit.WebSettings
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.graphics.createBitmap
|
||||
import androidx.privacysandbox.ui.provider.toCoreLibInfo
|
||||
import androidx.webkit.JavaScriptReplyProxy
|
||||
import androidx.webkit.ProxyConfig
|
||||
@@ -238,7 +239,7 @@ class NappletHostService : Service() {
|
||||
val outW = (boxW * zoom).toInt().coerceAtLeast(1)
|
||||
val outH = (boxH * zoom).toInt().coerceAtLeast(1)
|
||||
val t0 = SystemClock.elapsedRealtimeNanos()
|
||||
val bitmap = Bitmap.createBitmap(outW, outH, Bitmap.Config.ARGB_8888)
|
||||
val bitmap = createBitmap(outW, outH)
|
||||
val canvas = Canvas(bitmap)
|
||||
canvas.drawColor(tab.bgColor)
|
||||
canvas.scale(zoom, zoom)
|
||||
|
||||
Reference in New Issue
Block a user