1+ /*
2+ * Copyright (C) 2025 - present Instructure, Inc.
3+ *
4+ * This program is free software: you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation, version 3 of the License.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ */
17+ package com.instructure.horizon.features.account.reportabug
18+
19+ import android.content.Intent
20+ import android.webkit.JavascriptInterface
21+ import android.widget.LinearLayout
22+ import androidx.activity.compose.rememberLauncherForActivityResult
23+ import androidx.activity.result.contract.ActivityResultContracts
24+ import androidx.compose.runtime.Composable
25+ import androidx.compose.runtime.getValue
26+ import androidx.compose.runtime.mutableIntStateOf
27+ import androidx.compose.runtime.mutableStateOf
28+ import androidx.compose.runtime.remember
29+ import androidx.compose.runtime.setValue
30+ import androidx.compose.ui.platform.LocalContext
31+ import androidx.compose.ui.res.stringResource
32+ import androidx.lifecycle.compose.LocalLifecycleOwner
33+ import androidx.lifecycle.lifecycleScope
34+ import androidx.navigation.NavController
35+ import com.instructure.horizon.R
36+ import com.instructure.horizon.horizonui.organisms.scaffolds.HorizonScaffold
37+ import com.instructure.pandautils.compose.composables.ComposeCanvasWebViewWrapper
38+ import com.instructure.pandautils.compose.composables.ComposeWebViewCallbacks
39+ import com.instructure.pandautils.utils.getActivityOrNull
40+ import com.instructure.pandautils.views.CanvasWebView
41+ import kotlinx.coroutines.launch
42+
43+ @Composable
44+ fun ReportABugWebView (
45+ navController : NavController ,
46+ ) {
47+ HorizonScaffold (
48+ title = stringResource(R .string.accountReportABug),
49+ onBackPressed = { navController.popBackStack() },
50+ ) { modifier ->
51+ val scope = LocalLifecycleOwner .current.lifecycleScope
52+ val activity = LocalContext .current.getActivityOrNull()
53+ var webViewReference: CanvasWebView ? by remember { mutableStateOf(null ) }
54+ var request by remember { mutableIntStateOf(0 ) }
55+ val launchPicker = rememberLauncherForActivityResult(
56+ contract = ActivityResultContracts .StartActivityForResult ()
57+ ) { result ->
58+ webViewReference?.handleOnActivityResult(request, result.resultCode, result.data)
59+ }
60+
61+ ComposeCanvasWebViewWrapper (
62+ applyOnWebView = {
63+ settings.javaScriptEnabled = true
64+
65+ layoutParams = LinearLayout .LayoutParams (
66+ LinearLayout .LayoutParams .MATCH_PARENT ,
67+ LinearLayout .LayoutParams .MATCH_PARENT
68+ )
69+
70+ activity?.let { addVideoClient(activity) }
71+ setCanvasWebChromeClientShowFilePickerCallback(object : CanvasWebView .VideoPickerCallback {
72+ override fun requestStartActivityForResult (
73+ intent : Intent ,
74+ requestCode : Int
75+ ) {
76+ request = requestCode
77+ launchPicker.launch(intent)
78+ }
79+
80+ override fun permissionsGranted (): Boolean = true
81+ })
82+ webViewReference = this
83+ },
84+ content = """
85+ <!DOCTYPE html>
86+ <html lang="en">
87+ <head>
88+ <style>
89+ body {
90+ height: 100%;
91+ }
92+ </style>
93+ <meta name="viewport" content="width=device-width initial-scale=1">
94+ </head>
95+ <body>
96+ </body>
97+ </html>
98+ """ .trimIndent(),
99+ applyOnUpdate = {
100+ webView.addJavascriptInterface(
101+ JsReportABugInterface (
102+ { scope.launch { navController.popBackStack() } }
103+ ),
104+ JsReportABugInterface .INTERFACE_NAME
105+ )
106+ },
107+ webViewCallbacks = ComposeWebViewCallbacks (onPageFinished = { webView, url ->
108+ webView.evaluateJavascript("""
109+ const SCRIPT_ID = "jira-issue-collector"
110+ const script = document.createElement("script")
111+ script.id = SCRIPT_ID
112+ script.src =
113+ "https://instructure.atlassian.net/s/d41d8cd98f00b204e9800998ecf8427e-T/vf1kch/b/0/c95134bc67d3a521bb3f4331beb9b804/_/download/batch/com.atlassian.jira.collector.plugin.jira-issue-collector-plugin:issuecollector/com.atlassian.jira.collector.plugin.jira-issue-collector-plugin:issuecollector.js?locale=en-US&collectorId=e6b73300"
114+ script.addEventListener("load", function(){
115+ window.ATL_JQ_PAGE_PROPS = {
116+ triggerFunction: function (showCollectorDialog) {
117+ setTimeout(function() {
118+ showCollectorDialog();
119+ }, 100);
120+ }
121+ };
122+ });
123+
124+ document.body.appendChild(script)
125+
126+ window.addEventListener('message', (event) => {
127+ if (event.data === 'cancelFeedbackDialog') {
128+ ${JsReportABugInterface .INTERFACE_NAME } .close()
129+ }
130+ });
131+ """ .trimIndent(), null )
132+ }),
133+ modifier = modifier
134+ )
135+ }
136+ }
137+
138+ private class JsReportABugInterface (val onNavigateBack : () -> Unit ) {
139+ @JavascriptInterface
140+ fun close () {
141+ onNavigateBack()
142+ }
143+
144+ companion object {
145+ const val INTERFACE_NAME = " ReportABugInterface"
146+ }
147+ }
0 commit comments