Skip to content

Commit 9e83492

Browse files
authored
Merge pull request #148 from PatilShreyas/version-2.1.0
Release v2.1.0
2 parents 0d330f4 + 371caac commit 9e83492

File tree

9 files changed

+179
-34
lines changed

9 files changed

+179
-34
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ In `build.gradle` of app module, include this dependency
2222

2323
```gradle
2424
dependencies {
25-
implementation "dev.shreyaspatil:capturable:2.0.0"
25+
implementation "dev.shreyaspatil:capturable:2.1.0"
2626
}
2727
```
2828

app/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ android {
4949
dependencies {
5050

5151
// Capturable library
52-
// implementation "dev.shreyaspatil:capturable:2.0.0"
52+
// implementation "dev.shreyaspatil:capturable:2.1.0"
5353
implementation(project(":capturable"))
5454

5555
// Android

capturable/gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ POM_PACKAGING=aar
99
POM_INCEPTION_YEAR=2022
1010

1111
GROUP=dev.shreyaspatil
12-
VERSION_NAME=2.0.0
12+
VERSION_NAME=2.1.0
1313
VERSION_CODE=3
1414

1515
POM_URL=https://github.com/PatilShreyas/Capturable/

capturable/src/main/java/dev/shreyaspatil/capturable/Capturable.kt

Lines changed: 74 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,11 @@ import androidx.compose.ui.node.DelegatableNode
4545
import androidx.compose.ui.node.DelegatingNode
4646
import androidx.compose.ui.node.ModifierNodeElement
4747
import dev.shreyaspatil.capturable.controller.CaptureController
48+
import kotlinx.coroutines.CompletableDeferred
4849
import kotlinx.coroutines.Dispatchers
50+
import kotlinx.coroutines.ExperimentalCoroutinesApi
51+
import kotlinx.coroutines.flow.MutableStateFlow
52+
import kotlinx.coroutines.flow.flatMapLatest
4953
import kotlinx.coroutines.launch
5054
import kotlinx.coroutines.withContext
5155

@@ -157,51 +161,51 @@ private data class CapturableModifierNodeElement(
157161
}
158162

159163
override fun update(node: CapturableModifierNode) {
160-
node.controller = controller
164+
node.updateController(controller)
161165
}
162166
}
163167

164168
/**
165-
* Capturable Modifier node which delegates task to the [CacheDrawModifierNode] for drawing
166-
* Composable UI to the Picture and then helping it to converting picture into a Bitmap.
169+
* Capturable Modifier node which delegates task to the [CacheDrawModifierNode] for drawing in
170+
* runtime when content capture is requested
171+
* [CacheDrawModifierNode] is used for drawing Composable UI from Canvas to the Picture and then
172+
* this node converts picture into a Bitmap.
173+
*
174+
* @param controller A [CaptureController] which gives control to capture the Composable content.
167175
*/
168176
@Suppress("unused")
169177
private class CapturableModifierNode(
170-
var controller: CaptureController
178+
controller: CaptureController
171179
) : DelegatingNode(), DelegatableNode {
172180

173-
val picture = Picture()
174-
175181
/**
176-
* Delegates the drawing to [CacheDrawModifierNode] in order to draw content rendered on the
177-
* canvas directly to the [picture].
182+
* State to hold the current [CaptureController] instance.
183+
* This can be updated via [updateController] method.
178184
*/
179-
val drawModifierNode = delegate(
180-
CacheDrawModifierNode {
181-
// Example that shows how to redirect rendering to an Android Picture and then
182-
// draw the picture into the original destination
183-
val width = this.size.width.toInt()
184-
val height = this.size.height.toInt()
185-
186-
onDrawWithContent {
187-
val pictureCanvas = Canvas(picture.beginRecording(width, height))
188-
189-
draw(this, this.layoutDirection, pictureCanvas, this.size) {
190-
this@onDrawWithContent.drawContent()
191-
}
192-
picture.endRecording()
193-
194-
drawIntoCanvas { canvas -> canvas.nativeCanvas.drawPicture(picture) }
195-
}
196-
}
197-
)
185+
private val currentController = MutableStateFlow(controller)
198186

199187
override fun onAttach() {
200188
super.onAttach()
201189
coroutineScope.launch {
202-
controller.captureRequests.collect { request ->
190+
observeCaptureRequestsAndServe()
191+
}
192+
}
193+
194+
/**
195+
* Sets new [CaptureController]
196+
*/
197+
fun updateController(newController: CaptureController) {
198+
currentController.value = newController
199+
}
200+
201+
@OptIn(ExperimentalCoroutinesApi::class)
202+
private suspend fun observeCaptureRequestsAndServe() {
203+
currentController
204+
.flatMapLatest { it.captureRequests }
205+
.collect { request ->
203206
val completable = request.imageBitmapDeferred
204207
try {
208+
val picture = getCurrentContentAsPicture()
205209
val bitmap = withContext(Dispatchers.Default) {
206210
picture.asBitmap(request.config)
207211
}
@@ -210,7 +214,48 @@ private class CapturableModifierNode(
210214
completable.completeExceptionally(error)
211215
}
212216
}
213-
}
217+
}
218+
219+
private suspend fun getCurrentContentAsPicture(): Picture {
220+
return Picture().apply { drawCanvasIntoPicture(this) }
221+
}
222+
223+
/**
224+
* Draws the current content into the provided [picture]
225+
*/
226+
private suspend fun drawCanvasIntoPicture(picture: Picture) {
227+
// CompletableDeferred to wait until picture is drawn from the Canvas content
228+
val pictureDrawn = CompletableDeferred<Unit>()
229+
230+
// Delegate the task to draw the content into the picture
231+
val delegatedNode = delegate(
232+
CacheDrawModifierNode {
233+
val width = this.size.width.toInt()
234+
val height = this.size.height.toInt()
235+
236+
onDrawWithContent {
237+
val pictureCanvas = Canvas(picture.beginRecording(width, height))
238+
239+
draw(this, this.layoutDirection, pictureCanvas, this.size) {
240+
this@onDrawWithContent.drawContent()
241+
}
242+
picture.endRecording()
243+
244+
drawIntoCanvas { canvas ->
245+
canvas.nativeCanvas.drawPicture(picture)
246+
247+
// Notify that picture is drawn
248+
pictureDrawn.complete(Unit)
249+
}
250+
}
251+
}
252+
)
253+
// Wait until picture is drawn
254+
pictureDrawn.await()
255+
256+
// As task is accomplished, remove the delegation of node to prevent draw operations on UI
257+
// updates or recompositions.
258+
undelegate(delegatedNode)
214259
}
215260
}
216261

capturable/src/main/java/dev/shreyaspatil/capturable/controller/CaptureController.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import kotlinx.coroutines.flow.asSharedFlow
3838
* Controller for capturing [Composable] content.
3939
* @see dev.shreyaspatil.capturable.Capturable for implementation details.
4040
*/
41-
class CaptureController internal constructor() {
41+
class CaptureController {
4242

4343
/**
4444
* Medium for providing capture requests
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<!DOCTYPE html>
2+
<html class="no-js">
3+
<head>
4+
<meta name="viewport" content="width=device-width, initial-scale=1" charset="UTF-8">
5+
<title>CaptureController</title>
6+
<link href="../../../images/logo-icon.svg" rel="icon" type="image/svg">
7+
<script>var pathToRoot = "../../../";</script>
8+
<script>document.documentElement.classList.replace("no-js","js");</script>
9+
<script>const storage = localStorage.getItem("dokka-dark-mode")
10+
if (storage == null) {
11+
const osDarkSchemePreferred = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
12+
if (osDarkSchemePreferred === true) {
13+
document.getElementsByTagName("html")[0].classList.add("theme-dark")
14+
}
15+
} else {
16+
const savedDarkMode = JSON.parse(storage)
17+
if(savedDarkMode === true) {
18+
document.getElementsByTagName("html")[0].classList.add("theme-dark")
19+
}
20+
}
21+
</script>
22+
<script type="text/javascript" src="https://unpkg.com/kotlin-playground@1/dist/playground.min.js" async="async"></script>
23+
<script type="text/javascript" src="../../../scripts/sourceset_dependencies.js" async="async"></script>
24+
<link href="../../../styles/style.css" rel="Stylesheet">
25+
<link href="../../../styles/main.css" rel="Stylesheet">
26+
<link href="../../../styles/prism.css" rel="Stylesheet">
27+
<link href="../../../styles/logo-styles.css" rel="Stylesheet">
28+
<link href="../../../styles/font-jb-sans-auto.css" rel="Stylesheet">
29+
<script type="text/javascript" src="../../../scripts/clipboard.js" async="async"></script>
30+
<script type="text/javascript" src="../../../scripts/navigation-loader.js" async="async"></script>
31+
<script type="text/javascript" src="../../../scripts/platform-content-handler.js" async="async"></script>
32+
<script type="text/javascript" src="../../../scripts/main.js" defer="defer"></script>
33+
<script type="text/javascript" src="../../../scripts/prism.js" async="async"></script>
34+
<script type="text/javascript" src="../../../scripts/symbol-parameters-wrapper_deferred.js" defer="defer"></script>
35+
</head>
36+
<body>
37+
<div class="root">
38+
<nav class="navigation" id="navigation-wrapper">
39+
<div class="navigation--inner">
40+
<div class="navigation-title">
41+
<button class="menu-toggle" id="menu-toggle" type="button">toggle menu</button>
42+
<div class="library-name">
43+
<a class="library-name--link" href="../../../index.html">
44+
capturable
45+
</a>
46+
</div>
47+
<div class="library-version">
48+
</div>
49+
</div>
50+
<div class="filter-section" id="filter-section">
51+
<button class="platform-tag platform-selector jvm-like" data-active="" data-filter=":capturable:dokkaHtml/release">androidJvm</button>
52+
</div>
53+
</div>
54+
<div class="navigation-controls">
55+
<button class="navigation-controls--btn navigation-controls--theme" id="theme-toggle-button" type="button">switch theme</button>
56+
<div class="navigation-controls--btn navigation-controls--search" id="searchBar" role="button">search in API</div>
57+
</div>
58+
</nav>
59+
<div id="container">
60+
<div class="sidebar" id="leftColumn">
61+
<div class="sidebar--inner" id="sideMenu"></div>
62+
</div>
63+
<div id="main">
64+
<div class="main-content" data-page-type="member" id="content" pageIds="capturable::dev.shreyaspatil.capturable.controller/CaptureController/CaptureController/#/PointingToDeclaration//936064587">
65+
<div class="breadcrumbs"><a href="../../../index.html">capturable</a><span class="delimiter">/</span><a href="../index.html">dev.shreyaspatil.capturable.controller</a><span class="delimiter">/</span><a href="index.html">CaptureController</a><span class="delimiter">/</span><span class="current">CaptureController</span></div>
66+
<div class="cover ">
67+
<h1 class="cover"><span>Capture</span><wbr></wbr><span><span>Controller</span></span></h1>
68+
</div>
69+
<div class="platform-hinted " data-platform-hinted="data-platform-hinted"><div class="content sourceset-dependent-content" data-active="" data-togglable=":capturable:dokkaHtml/release"><div class="symbol monospace"><span class="token keyword">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span></div></div></div>
70+
</div>
71+
<div class="footer">
72+
<span class="go-to-top-icon"><a href="#content" id="go-to-top-link"></a></span><span>© 2024 Copyright</span><span
73+
class="pull-right"><span>Generated by </span><a
74+
href="https://github.com/Kotlin/dokka"><span>dokka</span><span class="padded-icon"></span></a></span>
75+
</div>
76+
</div>
77+
</div>
78+
</div>
79+
</body>
80+
</html>

docs/capturable/dev.shreyaspatil.capturable.controller/-capture-controller/index.html

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,25 @@ <h1 class="cover"><span>Capture</span><wbr></wbr><span><span>Controller</span></
7070
<div class="tabbedcontent">
7171
<div class="tabs-section" tabs-section="tabs-section"><button class="section-tab" data-active="" data-togglable="CONSTRUCTOR,TYPE,PROPERTY,FUNCTION">Members</button></div>
7272
<div class="tabs-section-body">
73+
<div data-togglable="CONSTRUCTOR">
74+
<h2 class="">Constructors</h2>
75+
<div class="table"><a data-name="-854098920%2FConstructors%2F936064587" anchor-label="CaptureController" id="-854098920%2FConstructors%2F936064587" data-filterable-set=":capturable:dokkaHtml/release"></a>
76+
<div class="table-row" data-togglable="CONSTRUCTOR" data-filterable-current=":capturable:dokkaHtml/release" data-filterable-set=":capturable:dokkaHtml/release">
77+
<div class="main-subrow keyValue ">
78+
<div class=""><span class="inline-flex">
79+
<div><a href="-capture-controller.html"><span>Capture</span><wbr></wbr><span><span>Controller</span></span></a></div>
80+
<span class="anchor-wrapper"><span class="anchor-icon" pointing-to="-854098920%2FConstructors%2F936064587"></span>
81+
<div class="copy-popup-wrapper "><span class="copy-popup-icon"></span><span>Link copied to clipboard</span></div>
82+
</span></span></div>
83+
<div>
84+
<div class="title">
85+
<div class="platform-hinted " data-platform-hinted="data-platform-hinted"><div class="content sourceset-dependent-content" data-active="" data-togglable=":capturable:dokkaHtml/release"><div class="symbol monospace"><span class="token keyword">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span></div></div></div>
86+
</div>
87+
</div>
88+
</div>
89+
</div>
90+
</div>
91+
</div>
7392
<div data-togglable="FUNCTION">
7493
<h2 class="">Functions</h2>
7594
<div class="table"><a data-name="158162935%2FFunctions%2F936064587" anchor-label="capture" id="158162935%2FFunctions%2F936064587" data-filterable-set=":capturable:dokkaHtml/release"></a>

docs/capturable/package-list

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ $dokka.linkExtension:html
33
$dokka.location:dev.shreyaspatil.capturable.controller////PointingToDeclaration/capturable/dev.shreyaspatil.capturable.controller/index.html
44
$dokka.location:dev.shreyaspatil.capturable.controller//rememberCaptureController/#/PointingToDeclaration/capturable/dev.shreyaspatil.capturable.controller/remember-capture-controller.html
55
$dokka.location:dev.shreyaspatil.capturable.controller/CaptureController///PointingToDeclaration/capturable/dev.shreyaspatil.capturable.controller/-capture-controller/index.html
6+
$dokka.location:dev.shreyaspatil.capturable.controller/CaptureController/CaptureController/#/PointingToDeclaration/capturable/dev.shreyaspatil.capturable.controller/-capture-controller/-capture-controller.html
67
$dokka.location:dev.shreyaspatil.capturable.controller/CaptureController/capture/#android.graphics.Bitmap.Config/PointingToDeclaration/capturable/dev.shreyaspatil.capturable.controller/-capture-controller/capture.html
78
$dokka.location:dev.shreyaspatil.capturable.controller/CaptureController/captureAsync/#android.graphics.Bitmap.Config/PointingToDeclaration/capturable/dev.shreyaspatil.capturable.controller/-capture-controller/capture-async.html
89
$dokka.location:dev.shreyaspatil.capturable////PointingToDeclaration/capturable/dev.shreyaspatil.capturable/index.html

docs/scripts/pages.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
[{"name":"class CaptureController","description":"dev.shreyaspatil.capturable.controller.CaptureController","location":"capturable/dev.shreyaspatil.capturable.controller/-capture-controller/index.html","searchKeys":["CaptureController","class CaptureController","dev.shreyaspatil.capturable.controller.CaptureController"]},{"name":"fun Capturable(controller: CaptureController, modifier: Modifier = Modifier, onCaptured: (ImageBitmap?, Throwable?) -> Unit, content: () -> Unit)","description":"dev.shreyaspatil.capturable.Capturable","location":"capturable/dev.shreyaspatil.capturable/-capturable.html","searchKeys":["Capturable","fun Capturable(controller: CaptureController, modifier: Modifier = Modifier, onCaptured: (ImageBitmap?, Throwable?) -> Unit, content: () -> Unit)","dev.shreyaspatil.capturable.Capturable"]},{"name":"fun Modifier.capturable(controller: CaptureController): Modifier","description":"dev.shreyaspatil.capturable.capturable","location":"capturable/dev.shreyaspatil.capturable/capturable.html","searchKeys":["capturable","fun Modifier.capturable(controller: CaptureController): Modifier","dev.shreyaspatil.capturable.capturable"]},{"name":"fun capture(config: Bitmap.Config = Bitmap.Config.ARGB_8888)","description":"dev.shreyaspatil.capturable.controller.CaptureController.capture","location":"capturable/dev.shreyaspatil.capturable.controller/-capture-controller/capture.html","searchKeys":["capture","fun capture(config: Bitmap.Config = Bitmap.Config.ARGB_8888)","dev.shreyaspatil.capturable.controller.CaptureController.capture"]},{"name":"fun captureAsync(config: Bitmap.Config = Bitmap.Config.ARGB_8888): Deferred<ImageBitmap>","description":"dev.shreyaspatil.capturable.controller.CaptureController.captureAsync","location":"capturable/dev.shreyaspatil.capturable.controller/-capture-controller/capture-async.html","searchKeys":["captureAsync","fun captureAsync(config: Bitmap.Config = Bitmap.Config.ARGB_8888): Deferred<ImageBitmap>","dev.shreyaspatil.capturable.controller.CaptureController.captureAsync"]},{"name":"fun rememberCaptureController(): CaptureController","description":"dev.shreyaspatil.capturable.controller.rememberCaptureController","location":"capturable/dev.shreyaspatil.capturable.controller/remember-capture-controller.html","searchKeys":["rememberCaptureController","fun rememberCaptureController(): CaptureController","dev.shreyaspatil.capturable.controller.rememberCaptureController"]}]
1+
[{"name":"class CaptureController","description":"dev.shreyaspatil.capturable.controller.CaptureController","location":"capturable/dev.shreyaspatil.capturable.controller/-capture-controller/index.html","searchKeys":["CaptureController","class CaptureController","dev.shreyaspatil.capturable.controller.CaptureController"]},{"name":"constructor()","description":"dev.shreyaspatil.capturable.controller.CaptureController.CaptureController","location":"capturable/dev.shreyaspatil.capturable.controller/-capture-controller/-capture-controller.html","searchKeys":["CaptureController","constructor()","dev.shreyaspatil.capturable.controller.CaptureController.CaptureController"]},{"name":"fun Capturable(controller: CaptureController, modifier: Modifier = Modifier, onCaptured: (ImageBitmap?, Throwable?) -> Unit, content: () -> Unit)","description":"dev.shreyaspatil.capturable.Capturable","location":"capturable/dev.shreyaspatil.capturable/-capturable.html","searchKeys":["Capturable","fun Capturable(controller: CaptureController, modifier: Modifier = Modifier, onCaptured: (ImageBitmap?, Throwable?) -> Unit, content: () -> Unit)","dev.shreyaspatil.capturable.Capturable"]},{"name":"fun Modifier.capturable(controller: CaptureController): Modifier","description":"dev.shreyaspatil.capturable.capturable","location":"capturable/dev.shreyaspatil.capturable/capturable.html","searchKeys":["capturable","fun Modifier.capturable(controller: CaptureController): Modifier","dev.shreyaspatil.capturable.capturable"]},{"name":"fun capture(config: Bitmap.Config = Bitmap.Config.ARGB_8888)","description":"dev.shreyaspatil.capturable.controller.CaptureController.capture","location":"capturable/dev.shreyaspatil.capturable.controller/-capture-controller/capture.html","searchKeys":["capture","fun capture(config: Bitmap.Config = Bitmap.Config.ARGB_8888)","dev.shreyaspatil.capturable.controller.CaptureController.capture"]},{"name":"fun captureAsync(config: Bitmap.Config = Bitmap.Config.ARGB_8888): Deferred<ImageBitmap>","description":"dev.shreyaspatil.capturable.controller.CaptureController.captureAsync","location":"capturable/dev.shreyaspatil.capturable.controller/-capture-controller/capture-async.html","searchKeys":["captureAsync","fun captureAsync(config: Bitmap.Config = Bitmap.Config.ARGB_8888): Deferred<ImageBitmap>","dev.shreyaspatil.capturable.controller.CaptureController.captureAsync"]},{"name":"fun rememberCaptureController(): CaptureController","description":"dev.shreyaspatil.capturable.controller.rememberCaptureController","location":"capturable/dev.shreyaspatil.capturable.controller/remember-capture-controller.html","searchKeys":["rememberCaptureController","fun rememberCaptureController(): CaptureController","dev.shreyaspatil.capturable.controller.rememberCaptureController"]}]

0 commit comments

Comments
 (0)