generated from JetBrains/compose-multiplatform-template
-
Notifications
You must be signed in to change notification settings - Fork 41
/
WebViewState.kt
224 lines (205 loc) · 7.78 KB
/
WebViewState.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
package com.multiplatform.webview.web
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.mapSaver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshots.SnapshotStateList
import com.multiplatform.webview.cookie.CookieManager
import com.multiplatform.webview.cookie.WebViewCookieManager
import com.multiplatform.webview.setting.WebSettings
import com.multiplatform.webview.util.KLogger
import com.multiplatform.webview.util.getPlatform
import com.multiplatform.webview.util.isZero
/**
* Created By Kevin Zou On 2023/9/5
*/
/**
* A state holder to hold the state for the WebView. In most cases this will be remembered
* using the rememberWebViewState(uri) function.
*/
class WebViewState(webContent: WebContent) {
/**
* The last loaded url. This is updated when a new page is loaded.
*/
var lastLoadedUrl: String? by mutableStateOf(null)
internal set
/**
* The content being loaded by the WebView
*/
var content: WebContent by mutableStateOf(webContent)
/**
* Whether the WebView is currently [LoadingState.Loading] data in its main frame (along with
* progress) or the data loading has [LoadingState.Finished]. See [LoadingState]
*/
var loadingState: LoadingState by mutableStateOf(LoadingState.Initializing)
internal set
/**
* Whether the webview is currently loading data in its main frame
*/
val isLoading: Boolean
get() = loadingState !is LoadingState.Finished
/**
* The title received from the loaded content of the current page
*/
var pageTitle: String? by mutableStateOf(null)
internal set
/**
* A list for errors captured in the last load. Reset when a new page is loaded.
* Errors could be from any resource (iframe, image, etc.), not just for the main page.
* For more fine grained control use the OnError callback of the WebView.
*/
val errorsForCurrentRequest: SnapshotStateList<WebViewError> = mutableStateListOf()
/**
* Custom Settings for WebView.
*/
val webSettings: WebSettings by mutableStateOf(WebSettings())
/**
* Whether the WebView should capture back presses and navigate back.
* We need access to this in the state saver. An internal DisposableEffect or AndroidView
* onDestroy is called after the state saver and so can't be used.
*/
internal var webView by mutableStateOf<IWebView?>(null)
/**
* The saved view state from when the view was destroyed last. To restore state,
* use the navigator and only call loadUrl if the bundle is null.
* See WebViewSaveStateSample.
*/
var viewState: WebViewBundle? = null
internal set
var scrollOffset: Pair<Int, Int> = 0 to 0
internal set
/**
* CookieManager for WebView.
* Exposes access to the cookie manager for webView
*/
val cookieManager: CookieManager by mutableStateOf(WebViewCookieManager())
}
/**
* Creates a WebView state that is remembered across Compositions.
*
* @param url The url to load in the WebView
* @param additionalHttpHeaders Optional, additional HTTP headers that are passed to [AccompanistWebView.loadUrl].
* Note that these headers are used for all subsequent requests of the WebView.
*/
@Composable
fun rememberWebViewState(
url: String,
additionalHttpHeaders: Map<String, String> = emptyMap(),
): WebViewState =
// Rather than using .apply {} here we will recreate the state, this prevents
// a recomposition loop when the webview updates the url itself.
remember {
WebViewState(
WebContent.Url(
url = url,
additionalHttpHeaders = additionalHttpHeaders,
),
)
}.apply {
this.content =
WebContent.Url(
url = url,
additionalHttpHeaders = additionalHttpHeaders,
)
}
/**
* Creates a WebView state that is remembered across Compositions and saved
* across activity recreation.
* When using saved state, you cannot change the URL via recomposition. The only way to load
* a URL is via a WebViewNavigator.
*
* @param data The uri to load in the WebView
* @sample com.google.accompanist.sample.webview.WebViewSaveStateSample
*/
@Composable
fun rememberSaveableWebViewState(
url: String,
additionalHttpHeaders: Map<String, String> = emptyMap(),
): WebViewState =
if (getPlatform().isDesktop()) {
rememberWebViewState(url, additionalHttpHeaders)
} else {
rememberSaveable(saver = WebStateSaver) {
WebViewState(WebContent.NavigatorOnly)
}
}
val WebStateSaver: Saver<WebViewState, Any> =
run {
val pageTitleKey = "pagetitle"
val lastLoadedUrlKey = "lastloaded"
val stateBundleKey = "bundle"
val scrollOffsetKey = "scrollOffset"
mapSaver(
save = {
val viewState = it.webView?.saveState()
KLogger.info {
"WebViewStateSaver Save: ${it.pageTitle}, ${it.lastLoadedUrl}, ${it.webView?.scrollOffset()}, $viewState"
}
mapOf(
pageTitleKey to it.pageTitle,
lastLoadedUrlKey to it.lastLoadedUrl,
stateBundleKey to viewState,
scrollOffsetKey to it.webView?.scrollOffset(),
)
},
restore = {
KLogger.info {
"WebViewStateSaver Restore: ${it[pageTitleKey]}, ${it[lastLoadedUrlKey]}, ${it["scrollOffset"]}, ${it[stateBundleKey]}"
}
val scrollOffset = it[scrollOffsetKey] as Pair<Int, Int>? ?: (0 to 0)
val bundle = it[stateBundleKey] as WebViewBundle?
WebViewState(WebContent.NavigatorOnly).apply {
this.pageTitle = it[pageTitleKey] as String?
this.lastLoadedUrl = it[lastLoadedUrlKey] as String?
bundle?.let { this.viewState = it }
if (!scrollOffset.isZero()) {
this.scrollOffset = scrollOffset
}
}
},
)
}
/**
* Creates a WebView state that is remembered across Compositions.
*
* @param data The uri to load in the WebView
* @param baseUrl The URL to use as the page's base URL.
* @param encoding The encoding of the data in the string.
* @param mimeType The MIME type of the data in the string.
* @param historyUrl The history URL for the loaded HTML. Leave null to use about:blank.
*/
@Composable
fun rememberWebViewStateWithHTMLData(
data: String,
baseUrl: String? = null,
encoding: String = "utf-8",
mimeType: String? = null,
historyUrl: String? = null,
): WebViewState =
remember {
WebViewState(WebContent.Data(data, baseUrl, encoding, mimeType, historyUrl))
}.apply {
this.content =
WebContent.Data(
data, baseUrl, encoding, mimeType, historyUrl,
)
}
/**
* Creates a WebView state for HTML file loading that is remembered across Compositions.
*
* @param fileName The file to load in the WebView
* Please note that the file should be placed in the commonMain/resources/assets folder.
* The fileName just need to be the relative path to the assets folder.
*/
@Composable
fun rememberWebViewStateWithHTMLFile(fileName: String): WebViewState =
remember {
WebViewState(WebContent.File(fileName))
}.apply {
this.content = WebContent.File(fileName)
}