Skip to content

Commit 50a0071

Browse files
committed
WebView optimization and code refactoring
1 parent 4a3893e commit 50a0071

4 files changed

Lines changed: 230 additions & 77 deletions

File tree

app/src/main/java/com/example/deeplviewer/activity/FloatingTextSelection.kt

Lines changed: 77 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@ import android.os.Handler
99
import android.os.Looper
1010
import android.view.View
1111
import android.view.animation.AlphaAnimation
12+
import android.webkit.WebView
1213
import androidx.appcompat.app.AppCompatActivity
1314
import com.example.deeplviewer.R
15+
import com.example.deeplviewer.config.WebViewConfig
16+
import com.example.deeplviewer.helper.WebViewUrlHelper
1417
import com.example.deeplviewer.webview.MyWebViewClient
1518
import com.example.deeplviewer.webview.NestedScrollWebView
1619
import com.example.deeplviewer.webview.WebAppInterface
@@ -19,6 +22,9 @@ import com.google.android.material.bottomsheet.BottomSheetDialog
1922

2023

2124
class FloatingTextSelection : AppCompatActivity() {
25+
private lateinit var webView: WebView
26+
private lateinit var webViewClient: MyWebViewClient
27+
private lateinit var layout: View
2228

2329
private val startUrl by lazy {
2430
val urlParam = getSharedPreferences("config", Context.MODE_PRIVATE).getString(
@@ -66,6 +72,9 @@ class FloatingTextSelection : AppCompatActivity() {
6672
overridePendingTransition(0, 0)
6773
}
6874

75+
/**
76+
* Launches the DeepL WebView in fullscreen mode with the selected text
77+
*/
6978
private fun launchFullscreen(initialText: String) {
7079
val intent = Intent(this, MainActivity::class.java)
7180
intent.putExtra("FLOATING_TEXT", initialText)
@@ -75,58 +84,85 @@ class FloatingTextSelection : AppCompatActivity() {
7584
finish()
7685
}
7786

78-
@SuppressLint("SetJavaScriptEnabled", "RestrictedApi", "VisibleForTests")
87+
/**
88+
* Launches a popup with the DeepL WebView and the selected text
89+
*/
7990
private fun launchPopup(initialText: String) {
80-
val layout = layoutInflater.inflate(R.layout.popup_layout, null)
81-
val webView = layout.findViewById<NestedScrollWebView>(R.id.webview)
91+
initializeWebView()
92+
setupWebViewClient()
8293

83-
webView.settings.javaScriptEnabled = true
84-
webView.settings.domStorageEnabled = true
94+
// Add JavaScript interface for communication
95+
webView.addJavascriptInterface(WebAppInterface(this), "Android")
96+
97+
webViewClient.loadFinishedListener = {
98+
setWebViewHeight()
99+
showWebViewWithAnimation()
100+
}
101+
102+
showBottomSheetDialog()
103+
104+
// Load the initial URL
105+
val targetUrl = WebViewUrlHelper.buildUrl(startUrl, initialText)
106+
webView.loadUrl(targetUrl)
107+
}
108+
109+
/**
110+
* Initializes the WebView
111+
*/
112+
private fun initializeWebView() {
113+
layout = layoutInflater.inflate(R.layout.popup_layout, null)
114+
webView = layout.findViewById<NestedScrollWebView>(R.id.webview)
115+
116+
WebViewConfig.applyBasicSettings(webView)
117+
}
85118

86-
val webViewClient = MyWebViewClient(this)
119+
/**
120+
* Sets up the WebViewClient
121+
*/
122+
private fun setupWebViewClient() {
123+
webViewClient = MyWebViewClient(this)
87124
webView.webViewClient = webViewClient
88-
webView.addJavascriptInterface(WebAppInterface(this), "Android")
125+
}
89126

127+
/**
128+
* Shows the BottomSheetDialog with the WebView
129+
*/
130+
@SuppressLint("RestrictedApi", "VisibleForTests")
131+
private fun showBottomSheetDialog() {
90132
val dialog = BottomSheetDialog(this)
91133
dialog.setContentView(layout)
92134
dialog.setOnDismissListener { finish() }
93135
dialog.behavior.disableShapeAnimations()
94136
dialog.show()
137+
}
95138

96-
webViewClient.loadFinishedListener = {
97-
// wait a bit, cause the WebView will change it's height multiple times caused by some lazy-loaded elements
98-
Handler(Looper.getMainLooper()).postDelayed({
99-
// Get the screen height and set the WebView height to 70% of the screen height
100-
val displayMetrics = resources.displayMetrics
101-
val screenHeight = displayMetrics.heightPixels
102-
val webViewHeight = (screenHeight * 0.7).toInt()
103-
104-
val layoutParams = webView.layoutParams
105-
layoutParams.height = webViewHeight
106-
webView.layoutParams = layoutParams
107-
108-
webView.measure(
109-
View.MeasureSpec.makeMeasureSpec(webView.width, View.MeasureSpec.EXACTLY),
110-
View.MeasureSpec.makeMeasureSpec(webViewHeight, View.MeasureSpec.EXACTLY)
111-
)
112-
webView.layout(0, 0, webView.width, webViewHeight)
113-
114-
// Fade in the WebView and hide the shimmer effect
115-
val animation = AlphaAnimation(0.0F, 1.0F)
116-
animation.duration = 250
117-
webView.visibility = View.VISIBLE
118-
webView.startAnimation(animation)
119-
layout.findViewById<ShimmerFrameLayout>(R.id.shimmer_view_container).hideShimmer()
120-
}, 750)
121-
}
122-
123-
webView.loadUrl(
124-
startUrl + Uri.encode(
125-
initialText.replace(
126-
"/",
127-
"\\/"
128-
)
129-
)
139+
/**
140+
* Sets the height of the WebView to 70% of the screen height
141+
*/
142+
private fun setWebViewHeight() {
143+
val displayMetrics = resources.displayMetrics
144+
val screenHeight = displayMetrics.heightPixels
145+
val webViewHeight = (screenHeight * 0.7).toInt()
146+
147+
val layoutParams = webView.layoutParams
148+
layoutParams.height = webViewHeight
149+
webView.layoutParams = layoutParams
150+
151+
webView.measure(
152+
View.MeasureSpec.makeMeasureSpec(webView.width, View.MeasureSpec.EXACTLY),
153+
View.MeasureSpec.makeMeasureSpec(webViewHeight, View.MeasureSpec.EXACTLY)
130154
)
155+
webView.layout(0, 0, webView.width, webViewHeight)
156+
}
157+
158+
/**
159+
* Shows the WebView with a fade-in animation
160+
*/
161+
private fun showWebViewWithAnimation() {
162+
val animation = AlphaAnimation(0.0F, 1.0F)
163+
animation.duration = 250
164+
webView.visibility = View.VISIBLE
165+
webView.startAnimation(animation)
166+
layout.findViewById<ShimmerFrameLayout>(R.id.shimmer_view_container).hideShimmer()
131167
}
132168
}
Lines changed: 96 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,27 @@
11
package com.example.deeplviewer.activity
22

3-
import android.annotation.SuppressLint
43
import android.content.Context
54
import android.content.Intent
65
import android.net.Uri
76
import android.os.Bundle
7+
import android.util.Log
88
import android.view.animation.AlphaAnimation
9-
import android.webkit.WebSettings
109
import android.webkit.WebView
1110
import android.widget.ImageButton
1211
import androidx.appcompat.app.AppCompatActivity
1312
import com.example.deeplviewer.helper.CookieManagerHelper
1413
import com.example.deeplviewer.webview.MyWebViewClient
1514
import com.example.deeplviewer.R
15+
import com.example.deeplviewer.config.WebViewConfig
16+
import com.example.deeplviewer.helper.WebViewUrlHelper
1617
import com.example.deeplviewer.webview.WebAppInterface
1718

1819
class MainActivity : AppCompatActivity() {
20+
private lateinit var webView: WebView
1921
private lateinit var webViewClient: MyWebViewClient
22+
2023
private val startUrl by lazy {
21-
return@lazy ORIGIN_URL + getSharedPreferences("config", Context.MODE_PRIVATE).getString(
24+
ORIGIN_URL + getSharedPreferences("config", Context.MODE_PRIVATE).getString(
2225
"urlParam",
2326
DEFAULT_PARAM
2427
)
@@ -32,56 +35,109 @@ class MainActivity : AppCompatActivity() {
3235
override fun onCreate(savedInstanceState: Bundle?) {
3336
super.onCreate(savedInstanceState)
3437
setContentView(R.layout.activity_main)
35-
createWebView(intent, savedInstanceState)
3638

37-
findViewById<ImageButton>(R.id.settingButton).setOnClickListener {
38-
val intent = Intent(this, SettingsActivity::class.java)
39-
startActivity(intent)
40-
}
39+
initializeWebView()
40+
createWebView(intent, savedInstanceState)
41+
setupSettingsButton()
4142
}
4243

4344
override fun onNewIntent(intent: Intent?) {
4445
super.onNewIntent(intent)
4546
createWebView(intent)
4647
}
4748

48-
@SuppressLint("SetJavaScriptEnabled", "AddJavascriptInterface")
49+
override fun onSaveInstanceState(outState: Bundle) {
50+
super.onSaveInstanceState(outState)
51+
saveWebViewState(outState)
52+
}
53+
54+
override fun onDestroy() {
55+
if (::webView.isInitialized) {
56+
// Remove the WebView from its parent view and destroy it
57+
(webView.parent as? android.view.ViewGroup)?.removeView(webView)
58+
webView.destroy()
59+
}
60+
super.onDestroy()
61+
}
62+
63+
/**
64+
* Creates and configures the WebView with the provided intent or saved instance state
65+
*/
4966
private fun createWebView(intent: Intent?, savedInstanceState: Bundle? = null) {
50-
val floatingText = intent?.getStringExtra("FLOATING_TEXT")
51-
val shareText = intent?.getStringExtra(Intent.EXTRA_TEXT)
52-
val savedText = savedInstanceState?.getString("SavedText")
53-
val receivedText = savedText ?: (floatingText ?: (shareText ?: ""))
67+
val receivedText = extractReceivedText(intent, savedInstanceState)
5468

55-
val webView: WebView = findViewById(R.id.webview)
69+
setupWebViewClient()
70+
setupCookies()
71+
72+
// Add JavaScript interface for communication
73+
webView.addJavascriptInterface(WebAppInterface(this), "Android")
74+
75+
// Load the initial URL
76+
val targetUrl = WebViewUrlHelper.buildUrl(startUrl, receivedText)
77+
webView.loadUrl(targetUrl)
78+
}
79+
80+
/**
81+
* Initializes the WebView
82+
*/
83+
private fun initializeWebView() {
84+
webView = findViewById(R.id.webview)
85+
webView.alpha = 0.0F
86+
WebViewConfig.applyBasicSettings(webView)
87+
WebViewConfig.applyOptimizedSettings(webView)
88+
}
89+
90+
/**
91+
* Extracts the text from the intent or saved instance state
92+
*/
93+
private fun extractReceivedText(intent: Intent?, savedInstanceState: Bundle?): String {
94+
return savedInstanceState?.getString("SavedText")
95+
?: intent?.getStringExtra("FLOATING_TEXT")
96+
?: intent?.getStringExtra(Intent.EXTRA_TEXT)
97+
?: ""
98+
}
99+
100+
/**
101+
* Sets up the WebViewClient to handle page loading and animations
102+
*/
103+
private fun setupWebViewClient() {
56104
webViewClient = MyWebViewClient(this)
57105
webViewClient.loadFinishedListener = {
58-
val animation = AlphaAnimation(0.0F, 1.0F)
59-
animation.duration = 100
106+
val animation = AlphaAnimation(0.0F, 1.0F).apply { duration = 100 }
60107
webView.startAnimation(animation)
61108
webView.alpha = 1.0F
62109
}
110+
webView.webViewClient = webViewClient
111+
}
63112

64-
CookieManagerHelper().migrateCookie(this)
65-
CookieManagerHelper().addPrivacyCookie()
66-
67-
webView.settings.javaScriptEnabled = true
68-
webView.settings.domStorageEnabled = true
69-
webView.settings.cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK
113+
/**
114+
* Sets up cookies for the WebView
115+
*/
116+
private fun setupCookies() {
117+
try {
118+
CookieManagerHelper().apply {
119+
migrateCookie(this@MainActivity)
120+
addPrivacyCookie()
121+
}
122+
} catch (e: Exception) {
123+
Log.w("MainActivity", "Cookie setup failed", e)
124+
}
125+
}
70126

71-
webView.webViewClient = webViewClient
72-
webView.addJavascriptInterface(WebAppInterface(this), "Android")
73-
webView.loadUrl(
74-
startUrl + Uri.encode(
75-
receivedText.replace(
76-
"/",
77-
"\\/"
78-
)
79-
)
80-
)
127+
/**
128+
* Sets up the settings button to open the SettingsActivity
129+
*/
130+
private fun setupSettingsButton() {
131+
findViewById<ImageButton>(R.id.settingButton).setOnClickListener {
132+
val intent = Intent(this, SettingsActivity::class.java)
133+
startActivity(intent)
134+
}
81135
}
82136

83-
override fun onSaveInstanceState(outState: Bundle) {
84-
super.onSaveInstanceState(outState)
137+
/**
138+
* Saves the current state of the WebView to the outState bundle
139+
*/
140+
private fun saveWebViewState(outState: Bundle) {
85141
val webView: WebView = findViewById(R.id.webview)
86142
val url = webView.url ?: ""
87143

@@ -95,6 +151,10 @@ class MainActivity : AppCompatActivity() {
95151
outState.putString("SavedText", inputText)
96152
}
97153

98-
CookieManagerHelper().saveCookies(this, webView)
154+
try {
155+
CookieManagerHelper().saveCookies(this, webView)
156+
} catch (e: Exception) {
157+
Log.w("MainActivity", "Cookie save failed", e)
158+
}
99159
}
100-
}
160+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.example.deeplviewer.config
2+
3+
import android.annotation.SuppressLint
4+
import android.webkit.WebSettings
5+
import android.webkit.WebView
6+
7+
object WebViewConfig {
8+
@SuppressLint("SetJavaScriptEnabled")
9+
fun applyBasicSettings(webView: WebView) {
10+
webView.settings.apply {
11+
javaScriptEnabled = true
12+
domStorageEnabled = true
13+
14+
// cache settings
15+
cacheMode = WebSettings.LOAD_DEFAULT
16+
}
17+
}
18+
19+
@SuppressLint("SetJavaScriptEnabled")
20+
fun applyOptimizedSettings(webView: WebView) {
21+
webView.settings.apply {
22+
// security settings
23+
allowFileAccess = false
24+
allowContentAccess = false
25+
setGeolocationEnabled(false)
26+
27+
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
28+
// off screen pre-rasterization (API 23+)
29+
offscreenPreRaster = true
30+
}
31+
32+
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
33+
// safe browsing (API 26+)
34+
safeBrowsingEnabled = true
35+
}
36+
37+
// mixed content mode
38+
mixedContentMode = WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE
39+
}
40+
}
41+
}

0 commit comments

Comments
 (0)