Skip to content

Commit ce68272

Browse files
Add an example demonstrating how to implement OpenExtensionPopupCallback in Compose (#154)
... and update DefaultOpenPopupCallback to use the exposed classes instead of its local copies. Issue: TeamDev-IP/JxBrowser-Docs#1507
1 parent c30315e commit ce68272

File tree

4 files changed

+155
-230
lines changed

4 files changed

+155
-230
lines changed
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*
2+
* Copyright 2024, TeamDev. All rights reserved.
3+
*
4+
* Redistribution and use in source and/or binary forms, with or without
5+
* modification, must retain the above copyright notice and the following
6+
* disclaimer.
7+
*
8+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
9+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
10+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
11+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
12+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
13+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
14+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
15+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
16+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
17+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
18+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
19+
*/
20+
21+
package com.teamdev.jxbrowser.examples.compose
22+
23+
import androidx.compose.foundation.layout.Column
24+
import androidx.compose.material.Button
25+
import androidx.compose.material.Text
26+
import androidx.compose.runtime.*
27+
import androidx.compose.runtime.snapshots.SnapshotStateList
28+
import androidx.compose.ui.unit.DpSize
29+
import androidx.compose.ui.unit.dp
30+
import androidx.compose.ui.window.WindowState
31+
import androidx.compose.ui.window.singleWindowApplication
32+
import com.teamdev.jxbrowser.dsl.Engine
33+
import com.teamdev.jxbrowser.dsl.register
34+
import com.teamdev.jxbrowser.dsl.removeCallback
35+
import com.teamdev.jxbrowser.dsl.subscribe
36+
import com.teamdev.jxbrowser.engine.RenderingMode
37+
import com.teamdev.jxbrowser.extensions.ExtensionAction
38+
import com.teamdev.jxbrowser.extensions.callback.InstallExtensionCallback
39+
import com.teamdev.jxbrowser.extensions.callback.OpenExtensionPopupCallback
40+
import com.teamdev.jxbrowser.extensions.event.ExtensionInstalled
41+
import com.teamdev.jxbrowser.view.compose.BrowserView
42+
import com.teamdev.jxbrowser.view.compose.popup.PopupWindow
43+
import com.teamdev.jxbrowser.view.compose.popup.PopupWindowState
44+
import kotlinx.coroutines.CoroutineScope
45+
import kotlinx.coroutines.launch
46+
47+
/**
48+
* This example demonstrates how to implement [OpenExtensionPopupCallback]
49+
* in Compose using [PopupWindow].
50+
*
51+
* It creates and shows a new window with the embedded pop-up browser when
52+
* the extension wants to show a popup.
53+
*
54+
* Note that the default implementation of [OpenExtensionPopupCallback] cannot
55+
* be provided for Compose, so this example guides you through the necessary
56+
* steps to achieve this functionality.
57+
*/
58+
fun main() = singleWindowApplication(
59+
title = "Default `OpenExtensionPopupCallback`",
60+
state = WindowState(size = DpSize(1280.dp, 900.dp))
61+
) {
62+
val engine = remember { Engine(RenderingMode.OFF_SCREEN) }
63+
val browser = remember { engine.newBrowser() }
64+
val extensions = remember { browser.profile().extensions() }
65+
66+
// Store pop-up windows in Compose observable list.
67+
val popups = remember { mutableStateListOf<PopupWindowState>() }
68+
69+
// Remember the coroutine scope to update the list of pop-up windows.
70+
val scope = rememberCoroutineScope()
71+
72+
Column {
73+
// Add a button to open the extension action popup.
74+
Button(onClick = {
75+
extensions.list()
76+
.firstOrNull()
77+
?.action(browser)
78+
?.ifPresent(ExtensionAction::click)
79+
}) {
80+
Text("Extension")
81+
}
82+
83+
// Add browser view.
84+
BrowserView(browser)
85+
}
86+
87+
// Add pop-up windows, if any.
88+
for (popup in popups) {
89+
// We associate each `PopupWindow` with a unique key. This helps Compose
90+
// efficiently manage the lifecycle of pop-up components when the list
91+
// of pop-ups changes.
92+
key(popup) {
93+
PopupWindow(popup)
94+
}
95+
}
96+
97+
DisposableEffect(Unit) {
98+
99+
// Allow installing extensions from the Chrome Web Store.
100+
extensions.register(InstallExtensionCallback { _, tell ->
101+
tell.install()
102+
})
103+
104+
val subscription = extensions.subscribe<ExtensionInstalled> {
105+
// When the extension is installed, allow it to open pop-ups.
106+
it.extension().register(OpenExtensionPopupCallback { params, tell ->
107+
scope.launch {
108+
popups.addNewPopup(params, scope) // Adds a new pop-up to the list.
109+
}
110+
tell.proceed()
111+
})
112+
}
113+
114+
browser.navigation().loadUrl("https://chromewebstore.google.com/")
115+
116+
onDispose {
117+
extensions.removeCallback<InstallExtensionCallback>()
118+
subscription.unsubscribe()
119+
engine.close()
120+
}
121+
}
122+
}
123+
124+
/**
125+
* Creates a new instance of [PopupWindowState] and adds it to
126+
* this [SnapshotStateList].
127+
*
128+
* The created pop-up will be removed from the list automatically
129+
* when it is closed via [PopupWindowState.onClose] callback.
130+
*/
131+
fun SnapshotStateList<PopupWindowState>.addNewPopup(
132+
params: OpenExtensionPopupCallback.Params,
133+
scope: CoroutineScope,
134+
) = add(
135+
PopupWindowState(
136+
browser = params.popupBrowser(),
137+
scope = scope,
138+
onClose = { remove(it) }
139+
)
140+
)

examples/src/main/kotlin/com/teamdev/jxbrowser/examples/compose/popup/DefaultOpenPopupCallback.kt renamed to examples/src/main/kotlin/com/teamdev/jxbrowser/examples/compose/DefaultOpenPopupCallback.kt

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1919
*/
2020

21-
package com.teamdev.jxbrowser.examples.compose.popup
21+
package com.teamdev.jxbrowser.examples.compose
2222

2323
import androidx.compose.runtime.*
2424
import androidx.compose.runtime.snapshots.SnapshotStateList
@@ -31,7 +31,10 @@ import com.teamdev.jxbrowser.dsl.register
3131
import com.teamdev.jxbrowser.dsl.removeCallback
3232
import com.teamdev.jxbrowser.engine.RenderingMode
3333
import com.teamdev.jxbrowser.view.compose.BrowserView
34+
import com.teamdev.jxbrowser.view.compose.popup.PopupWindow
35+
import com.teamdev.jxbrowser.view.compose.popup.PopupWindowState
3436
import kotlinx.coroutines.CoroutineScope
37+
import kotlinx.coroutines.launch
3538

3639
/**
3740
* This example demonstrates the default [OpenPopupCallback] implementation
@@ -75,7 +78,9 @@ fun main() = singleWindowApplication(title = "Default `OpenPopupCallback`") {
7578

7679
// `OpenPopupCallback` is responsible for the pop-up window creation.
7780
browser.register(OpenPopupCallback { params ->
78-
popups.addNewPopup(params, scope) // Adds a new pop-up to the list.
81+
scope.launch {
82+
popups.addNewPopup(params, scope) // Adds a new pop-up to the list.
83+
}
7984
OpenPopupCallback.Response.proceed()
8085
})
8186

@@ -102,4 +107,11 @@ fun main() = singleWindowApplication(title = "Default `OpenPopupCallback`") {
102107
fun SnapshotStateList<PopupWindowState>.addNewPopup(
103108
params: OpenPopupCallback.Params,
104109
scope: CoroutineScope,
105-
) = add(PopupWindowState(params, scope, onClose = { remove(it) }))
110+
) = add(
111+
PopupWindowState(
112+
browser = params.popupBrowser(),
113+
bounds = params.initialBounds(),
114+
scope = scope,
115+
onClose = { remove(it) }
116+
)
117+
)

examples/src/main/kotlin/com/teamdev/jxbrowser/examples/compose/popup/PopupWindow.kt

Lines changed: 0 additions & 46 deletions
This file was deleted.

0 commit comments

Comments
 (0)