WSC 48th MAD 2nd
THE PREPARE NOTE
Draggable text
Section titled “Draggable text”Text can be drag and drop to external app like messenger
AndroidView(modifier = modifier, factory = { context -> TextView(context).apply { this.text = "Hello World!!!" textSize = 30f setTypeface(typeface, android.graphics.Typeface.BOLD) setTextColor(android.graphics.Color.BLACK)
setOnLongClickListener { v: View -> val item = ClipData.Item(this.text) val mimeTypes = arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN) val dragData = ClipData("text", mimeTypes, item)
val shadow = View.DragShadowBuilder(v) v.startDragAndDrop(dragData, shadow, null, View.DRAG_FLAG_GLOBAL) true }
}})Transform
Section titled “Transform”change pivot in graphicsLayer
transformOrigin = TransformOrigin(0.5f, 1f)Device Sensor
Section titled “Device Sensor”DisposableEffect(Unit) { val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
val listener = object : SensorEventListener { override fun onSensorChanged(event: SensorEvent) { val x = event.values[0] tiledLeft = x in 3f..15f }
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {} }
val observer = LifecycleEventObserver { _, event -> when (event) { Lifecycle.Event.ON_RESUME -> { sensorManager.registerListener( listener, accelerometer, SensorManager.SENSOR_DELAY_GAME ) }
Lifecycle.Event.ON_PAUSE -> { sensorManager.unregisterListener(listener) }
else -> {} } }
lifecycleOwner.lifecycle.addObserver(observer)
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) sensorManager.unregisterListener(listener) }}Save window area to image
Section titled “Save window area to image”use
onGloballyPositionedto getLayoutCoordinatesof the target composable
val view = LocalView.currentval window = (view.context as Activity).windowvar captureArea: LayoutCoordinates? = null
suspend fun captureMap() { delay(100L) val areaRect = captureArea?.boundsInWindow()?.toAndroidRect()
if (areaRect != null) { val bitmap = Bitmap.createBitmap(areaRect.width(), areaRect.height(), Bitmap.Config.ARGB_8888)
PixelCopy.request(window, areaRect, bitmap, { result -> if (result == PixelCopy.SUCCESS) { val contentValue = ContentValues().apply { put( MediaStore.MediaColumns.DISPLAY_NAME, "${System.currentTimeMillis()}_map.png" ) put(MediaStore.MediaColumns.MIME_TYPE, "image/png") }
val resolver = context.contentResolver val contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI val uri = resolver.insert(contentUri, contentValue)!!
resolver.openOutputStream(uri)?.let { bitmap.compress(Bitmap.CompressFormat.PNG, 100, it) it.flush() it.close() } } }, Handler(Looper.getMainLooper()))
}}Drawing canvas
Section titled “Drawing canvas”Modifier.pointerInput(Unit) { awaitPointerEventScope { while (true) { val event = awaitPointerEvent() val position = event.changes.first().position val liftUp = event.changes.all { !it.pressed }
when { liftUp -> { draw.add(Path().apply { currentDraw .toList() .forEachIndexed { index, offset -> if (index == 0) moveTo(offset.x, offset.y) lineTo(offset.x, offset.y) } }) currentDraw.clear() }
event.changes.first().pressed -> { currentDraw.add(position) } } } }}Resizeable map demo
Section titled “Resizeable map demo”@Composablefun TravelScreen() { val context = LocalContext.current val data = remember { mutableStateListOf<Location>() }
var mapScaleFactor by remember { mutableFloatStateOf(0f) } val mapBitmap = remember { BitmapFactory.decodeResource(context.resources, R.drawable.map_paris_city) } var scale by remember { mutableFloatStateOf(1.2f) } var pan by remember { mutableStateOf(Offset.Zero) }
Box( modifier = Modifier .clipToBounds() .fillMaxSize() .graphicsLayer { translationX = pan.x * scale translationY = pan.y * scale scaleX = scale scaleY = scale } .pointerInput(Unit) { detectTransformGestures { _, panLevel, zoom, _ -> scale *= zoom pan += panLevel } } ) { Box(modifier = Modifier.align(Alignment.Center)) { Image( bitmap = mapBitmap.asImageBitmap(), contentDescription = null, modifier = Modifier .fillMaxWidth() .onGloballyPositioned { mapScaleFactor = it.size.width.toFloat() / mapBitmap.width } .align(Alignment.Center), contentScale = ContentScale.Fit ) data.forEach { location -> Box(modifier = Modifier .offset( ((location.markOffset().first * mapScaleFactor) - 20).dp, ((location.markOffset().second * mapScaleFactor) - 40).dp ) .graphicsLayer { scaleX /= scale scaleY /= scale transformOrigin = TransformOrigin(0.5f, 1f) } .size(40.dp) .clip(CircleShape) .clickable {
}) { Image( painter = painterResource(R.drawable.icon_map_marker), contentDescription = null ) } } } }}Open location with map
Section titled “Open location with map”val uri = Uri.parse("geo:0,0?q=${detail.locationName}")
val mapIntent = Intent( Intent.ACTION_VIEW, uri) .setPackage("com.google.android.apps.maps")
context.startActivity(mapIntent)Native Json Parsing
Section titled “Native Json Parsing”- Map parsing
"ratings_categories" : [ {"Staff":8.7},{"Facilities":8.4},{"Cleanliness":8.6}]parentJsonObj.getJSONArray("ratings_categories") .let { catArray -> val map = mutableMapOf<String, Float>() repeat(catArray.length()) { index -> val catObj = catArray.getJSONObject(index) val key = catObj.keys().next() val value = catObj.getDouble(key).toFloat() map[key] = value } map }Date Parsing
Section titled “Date Parsing”Convert formated date text
- with
SimpleDateFormat
SimpleDateFormat( "MMM dd, yyyy, hh:mm a", Locale.US).format(diary.uploadDatetime.let { formatedDate -> SimpleDateFormat("yyyy-MM-dd HH:mm:ss",Locale.US).parse(formatedDate)?.time})- with
DateTimeFormatter
fun convertDate(string: String): Long { val formats = listOf( DateTimeFormatter.ofPattern("MM/dd/yyyy"), DateTimeFormatter.ofPattern("MM-dd-yyyy"), DateTimeFormatter.ofPattern("MMM dd yyyy") ) formats.forEach { try { val date = LocalDate.parse(string.trim(), it) return date.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli() } catch (_: Exception) { } } throw Exception("Not the correct format for check-in or check-out date")}Java standard date format syntax
Section titled “Java standard date format syntax”| Code | Meaning |
|---|---|
| yyyy | year(2025) |
| MMMM | month(August) |
| MMM | month(Aug) |
| MM | month(08) |
| EEEE | day of week(Wednesday) |
| EEE | day of week(Wed) |
| dd | day of month |
| HH | 24hr clock(18) |
| hh | 12hr clock(08) |
| mm | minute |
| ss | second |
| a | AM/PM marker |
Testing
Section titled “Testing”Use espresso to close on screen keyboard
Espresso.closeSoftKeyboard()Play audio from raw resource
Section titled “Play audio from raw resource”val coinSound = remember { MediaPlayer.create(context, R.raw.coin) }Bitmap
Section titled “Bitmap”Load bitmap by drawable
Section titled “Load bitmap by drawable”val playerBitmapOriginal = remember { BitmapFactory.decodeResource(context.resources, R.drawable.skiing_person)}Scale bitmap
Section titled “Scale bitmap”Bitmap.createScaledBitmap( playerBitmapOriginal, (width).toInt(), (height).toInt(), true)Replace pixel color
Section titled “Replace pixel color”fun adjustPlayerColor(originalBitmap: Bitmap, hue: Float, black: Boolean = false): Bitmap { val bitmap = originalBitmap.copy(Bitmap.Config.ARGB_8888, true) val targetColor = Color.hsv(hue, 0.54f, 0.97f) for (width in 0 until bitmap.width) { for (height in 0 until bitmap.height) { val pixelColor = bitmap.getPixel(width, height) if (pixelColor == android.graphics.Color.parseColor("#f87373")) { bitmap.setPixel( width, height, if (black) android.graphics.Color.BLACK else targetColor.toArgb() ) } } } return bitmap}screenOrientation
Section titled “screenOrientation”add in <activity
android:screenOrientation="portrait"Sensor
Section titled “Sensor”DisposableEffect(Unit) { val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
val listener = object : SensorEventListener { override fun onSensorChanged(event: SensorEvent) { val x = event.values[0] tiledLeft = x in 3f..15f }
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {} }
val observer = LifecycleEventObserver { _, event -> when (event) { Lifecycle.Event.ON_RESUME -> { sensorManager.registerListener( listener, accelerometer, SensorManager.SENSOR_DELAY_GAME ) }
Lifecycle.Event.ON_PAUSE -> { sensorManager.unregisterListener(listener) }
else -> {} } }
lifecycleOwner.lifecycle.addObserver(observer)
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) sensorManager.unregisterListener(listener) }}Native URL
Section titled “Native URL”fun getJson(path: String): String { return URL(path).openConnection().let { BufferedReader(InputStreamReader(it.getInputStream())).readText() }}with json payload
Section titled “with json payload”suspend fun auth(email: String, password: String): Pair<Boolean, String> { return withContext(Dispatchers.IO) { try { val endpoint = (URL("$host/api/users/signin").openConnection() as HttpURLConnection).apply { requestMethod = "POST" setRequestProperty("Content-Type", "application/json") doOutput = true }
val body = JSONObject().apply { put("userEmailAddress", email) put("userPassword", password) }.toString()
endpoint.outputStream.use { req -> req.write(body.toByteArray()) req.flush() }
val ok = endpoint.responseCode == HttpURLConnection.HTTP_OK
val msg = (if (ok) endpoint.inputStream else endpoint.errorStream).bufferedReader() .readText().let { authToken = JSONObject(it).getJSONObject("data").getString("auth_token") JSONObject(it).getString("msg") }
Pair(ok, msg) } catch (e: Exception) { Log.e("Auth Api", "Error: $e") Pair(false, "internal error") } }}Implementation
Section titled “Implementation”Repeated background
Section titled “Repeated background”val bgBitmap = remember { BitmapFactory.decodeResource(context.resources, R.drawable.background) }val bgShader = ImageShader(bgBitmap.asImageBitmap(), TileMode.Repeated, TileMode.Repeated)val bgBrush = ShaderBrush(bgShader)Font form .ttc .ttf
Section titled “Font form .ttc .ttf”.ttf may required manually adding config to font
Remember to Rebuild the project after adding new font to the font resource
Create fontFamily
val playFair = FontFamily( Font(R.font.playfair_display_regular, FontWeight.Normal))Get weekdays
Section titled “Get weekdays”val weekDays = DateFormatSymbols(Locale.US).shortWeekdays.drop(1)Better petal shaped pager rotation calculation
Section titled “Better petal shaped pager rotation calculation”val rotation = -(pagerState.currentPage - index + pagerState.currentPageOffsetFraction) * 10f// currentPage - thisPage == relative page (ex: current -> 0 next -> -1 last -> 1