Skip to content

Commit 0d9678f

Browse files
authored
[CLX-2771][Horizon] Report a problem (#3224)
refs: CLX-2771 affects: Horizon release note: none
1 parent 08f30e3 commit 0d9678f

File tree

8 files changed

+161
-8
lines changed

8 files changed

+161
-8
lines changed

libs/horizon/src/main/java/com/instructure/horizon/features/account/AccountScreen.kt

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ import androidx.compose.ui.Alignment
3838
import androidx.compose.ui.Modifier
3939
import androidx.compose.ui.draw.clip
4040
import androidx.compose.ui.platform.LocalContext
41-
import androidx.compose.ui.platform.LocalUriHandler
4241
import androidx.compose.ui.res.painterResource
4342
import androidx.compose.ui.unit.dp
4443
import androidx.navigation.NavController
@@ -158,7 +157,6 @@ private fun AccountContentScreen(state: AccountUiState, navController: NavContro
158157

159158
@Composable
160159
private fun AccountItem(item: AccountItemState, navController: NavController, onLogout: () -> Unit, switchExperience: () -> Unit, modifier: Modifier = Modifier) {
161-
val uriHandler = LocalUriHandler.current
162160
Row(
163161
verticalAlignment = Alignment.CenterVertically,
164162
modifier = modifier
@@ -167,9 +165,8 @@ private fun AccountItem(item: AccountItemState, navController: NavController, on
167165
.clickable {
168166
when (item.type) {
169167
is AccountItemType.Open -> navController.navigate(item.type.route.route)
170-
is AccountItemType.OpenInNew -> {
171-
uriHandler.openUri(item.type.url)
172-
}
168+
169+
is AccountItemType.OpenExternal -> navController.navigate(item.type.route.route)
173170

174171
is AccountItemType.LogOut -> {
175172
onLogout()

libs/horizon/src/main/java/com/instructure/horizon/features/account/AccountUiState.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ data class AccountItemState(
4444

4545
sealed class AccountItemType(@DrawableRes val icon: Int) {
4646
data class Open(val route: AccountRoute) : AccountItemType(R.drawable.arrow_forward)
47-
data class OpenInNew(val url: String) : AccountItemType(R.drawable.open_in_new)
47+
data class OpenExternal(val route: AccountRoute) : AccountItemType(R.drawable.open_in_new)
4848
data object LogOut : AccountItemType(R.drawable.logout)
4949
data object SwitchExperience : AccountItemType(R.drawable.swap_horiz)
5050
}

libs/horizon/src/main/java/com/instructure/horizon/features/account/AccountViewModel.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,8 @@ class AccountViewModel @Inject constructor(
118118
title = context.getString(R.string.accountSupportHeading),
119119
items = listOf(
120120
AccountItemState(
121-
title = context.getString(R.string.accountGiveFeedbackLabel),
122-
type = AccountItemType.OpenInNew("https://forms.gle/R2cqoowDEUs5CWwy8"),
121+
title = context.getString(R.string.accountReportABug),
122+
type = AccountItemType.OpenExternal(AccountRoute.BugReportWebView)
123123
)
124124
)
125125
)

libs/horizon/src/main/java/com/instructure/horizon/features/account/navigation/AccountNavigation.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import com.instructure.horizon.features.account.notifications.AccountNotificatio
3636
import com.instructure.horizon.features.account.password.AccountPasswordScreen
3737
import com.instructure.horizon.features.account.profile.AccountProfileScreen
3838
import com.instructure.horizon.features.account.profile.AccountProfileViewModel
39+
import com.instructure.horizon.features.account.reportabug.ReportABugWebView
3940
import com.instructure.horizon.horizonui.animation.enterTransition
4041
import com.instructure.horizon.horizonui.animation.exitTransition
4142
import com.instructure.horizon.horizonui.animation.popEnterTransition
@@ -90,5 +91,9 @@ fun AccountNavigation(
9091
val uiState by viewModel.uiState.collectAsState()
9192
AccountAdvancedScreen(uiState, navController)
9293
}
94+
95+
composable(AccountRoute.BugReportWebView.route) {
96+
ReportABugWebView(navController)
97+
}
9398
}
9499
}

libs/horizon/src/main/java/com/instructure/horizon/features/account/navigation/AccountRoute.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@ sealed class AccountRoute(val route: String) {
2323
data object Notifications : AccountRoute("notifications")
2424
data object CalendarFeed : AccountRoute("calendar_feed")
2525
data object Advanced : AccountRoute("advanced")
26+
data object BugReportWebView : AccountRoute("report_a_bug")
2627
}
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
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+
}

libs/horizon/src/main/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
<string name="accountAdvancedLabel">Advanced</string>
121121
<string name="accountSupportHeading">Support</string>
122122
<string name="accountGiveFeedbackLabel">Give feedback</string>
123+
<string name="accountReportABug">Report a bug</string>
123124
<string name="accountBetaCommunityLabel">Beta community</string>
124125
<string name="accountFullNameLabel">Full name</string>
125126
<string name="a11yNavigateBack">Navigate back</string>

libs/pandautils/src/main/java/com/instructure/pandautils/compose/composables/ComposeCanvasWebViewWrapper.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ fun ComposeCanvasWebViewWrapper(
4242
title: String? = null,
4343
onLtiButtonPressed: ((ltiUrl: String) -> Unit)? = null,
4444
applyOnWebView: (CanvasWebView.() -> Unit)? = null,
45+
applyOnUpdate: (CanvasWebViewWrapper.() -> Unit)? = null,
4546
webViewCallbacks: ComposeWebViewCallbacks? = null,
4647
embeddedWebViewCallbacks: ComposeEmbeddedWebViewCallbacks? = null,
4748
) {
@@ -101,6 +102,7 @@ fun ComposeCanvasWebViewWrapper(
101102
} else {
102103
it.webView.restoreState(webViewState)
103104
}
105+
applyOnUpdate?.let { applyOnUpdate -> it.applyOnUpdate() }
104106
},
105107
onRelease = {
106108
it.webView.saveState(webViewState)

0 commit comments

Comments
 (0)