Skip to content

Commit 10dfdb9

Browse files
graycreateclaude
andauthored
feat: improve Vshare WebView with intent URL support and status bar padding (#155)
* feat: improve Vshare WebView with intent URL support and status bar padding This commit addresses two UX issues in the Vshare WebView: 1. **Intent URL Support**: Fixed handling of intent:// URLs (e.g., Google Play Store links) - Added handleUrlLoading() method to intercept and process special URL schemes - Supports intent://, market://, and other app-specific schemes (mailto:, tel:, etc.) - Includes fallback mechanisms for better user experience - Displays appropriate error messages when apps are not available 2. **Status Bar Padding**: Added paddingTop to prevent content from being hidden - Added android:fitsSystemWindows="true" to WebView - Ensures content is properly displayed below the status bar in edge-to-edge mode Changes made: - VshareWebActivity.java: Added URL scheme handling logic with comprehensive fallback support - activity_vshare_web.xml: Added fitsSystemWindows attribute to WebView 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: use explicit paddingTop for WebView status bar spacing * fix: properly handle status bar padding using WindowInsets The previous approach using static paddingTop in XML didn't work because the activity uses SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN for edge-to-edge layout. This commit fixes the issue by: 1. Using OnApplyWindowInsetsListener to dynamically get the status bar height 2. Programmatically applying it as padding to the WebView 3. Including fallback for older API levels using resource identifier This ensures content is properly displayed below the status bar on all devices and screen configurations. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor: make VshareWebActivity extend BaseActivity This commit refactors VshareWebActivity to inherit from BaseActivity instead of AppCompatActivity, providing better integration with the app's architecture. Benefits: 1. Automatic theme handling via reloadMode() - activity recreates on theme change 2. EventBus integration for day/night mode events 3. Consistent edge-to-edge layout handling 4. Access to base lifecycle management and utilities 5. Disabled slide-back gesture for fullscreen WebView experience Changes: - Extended BaseActivity<BaseContract.IPresenter> - Implemented attachLayoutRes() to provide layout - Implemented reloadMode() for theme switching support - Overridden supportSlideBack() to disable swipe-back - Moved initialization logic from onCreate() to init() - Removed manual ButterKnife binding (handled by BaseActivity) The WebView functionality remains unchanged, including: - Intent URL handling for external apps - WindowInsets-based status bar padding - Theme-aware content loading 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * feat: hide toolbar in VshareWebActivity for fullscreen experience Override attachToolbar() to return null, which tells BaseActivity not to create a toolbar for this activity. This provides a true fullscreen WebView experience without any app chrome. Implementation follows the existing pattern used in TwoStepLoginActivity and other fullscreen activities in the codebase. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: add fitsSystemWindows to WebView to properly handle status bar Added android:fitsSystemWindows="true" to the WebView in the layout XML to work in conjunction with the programmatic WindowInsets handling. This ensures the WebView content is not obscured by the status bar, providing proper spacing at the top of the page. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: use layout_marginTop instead of WindowInsets for WebView spacing Simplified the status bar spacing approach: - Added android:layout_marginTop="30dp" to WebView in XML layout - Removed WindowInsets listener code (was not working reliably) - Removed getStatusBarHeight() helper method (no longer needed) This provides a simpler, more predictable way to prevent content from being obscured by the status bar, using a fixed margin at the top. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor: dynamically set WebView top margin based on status bar height Changed from static layout_marginTop to programmatically setting the margin based on the actual status bar height. Changes: - Removed android:layout_marginTop from XML layout - Added applyStatusBarMargin() method to set margin programmatically - Added getStatusBarHeight() to retrieve actual status bar height - Applied margin in init() after ButterKnife binds the views This ensures the WebView properly adapts to different devices and screen configurations, providing consistent spacing below the status bar. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent f0d1619 commit 10dfdb9

File tree

1 file changed

+156
-17
lines changed

1 file changed

+156
-17
lines changed

app/src/main/java/me/ghui/v2er/module/vshare/VshareWebActivity.java

Lines changed: 156 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,40 @@
11
package me.ghui.v2er.module.vshare;
22

33
import android.annotation.SuppressLint;
4+
import android.content.ActivityNotFoundException;
45
import android.content.Context;
56
import android.content.Intent;
67
import android.graphics.Bitmap;
8+
import android.net.Uri;
79
import android.os.Bundle;
10+
import android.util.Log;
811
import android.view.View;
12+
import android.view.ViewGroup;
13+
import android.widget.FrameLayout;
914
import android.webkit.WebChromeClient;
1015
import android.webkit.WebResourceRequest;
1116
import android.webkit.WebSettings;
1217
import android.webkit.WebView;
1318
import android.webkit.WebViewClient;
1419
import android.widget.ProgressBar;
20+
import android.widget.Toast;
1521

16-
import androidx.appcompat.app.AppCompatActivity;
22+
import androidx.annotation.Nullable;
1723

1824
import butterknife.BindView;
19-
import butterknife.ButterKnife;
2025
import me.ghui.v2er.R;
26+
import me.ghui.v2er.module.base.BaseActivity;
27+
import me.ghui.v2er.module.base.BaseContract;
2128
import me.ghui.v2er.util.DarkModelUtils;
29+
import me.ghui.v2er.widget.BaseToolBar;
2230

2331
/**
2432
* Fullscreen WebView Activity for displaying vshare page
2533
* with automatic theme adaptation
2634
*/
27-
public class VshareWebActivity extends AppCompatActivity {
35+
public class VshareWebActivity extends BaseActivity<BaseContract.IPresenter> {
2836

37+
private static final String TAG = "VshareWebActivity";
2938
private static final String VSHARE_BASE_URL = "https://v2er.app/vshare";
3039

3140
@BindView(R.id.webview)
@@ -40,18 +49,36 @@ public static void open(Context context) {
4049
}
4150

4251
@Override
43-
protected void onCreate(Bundle savedInstanceState) {
44-
super.onCreate(savedInstanceState);
52+
protected int attachLayoutRes() {
53+
return R.layout.activity_vshare_web;
54+
}
4555

46-
// Hide action bar if present
47-
if (getSupportActionBar() != null) {
48-
getSupportActionBar().hide();
49-
}
56+
@Nullable
57+
@Override
58+
protected BaseToolBar attachToolbar() {
59+
// Return null to hide the toolbar for fullscreen WebView experience
60+
return null;
61+
}
5062

51-
// Set SystemUI flags to match MainActivity's edge-to-edge behavior
63+
@Override
64+
protected void reloadMode(int mode) {
65+
// Recreate activity to apply new theme
66+
recreate();
67+
}
68+
69+
@Override
70+
protected boolean supportSlideBack() {
71+
// Disable slide back for fullscreen WebView
72+
return false;
73+
}
74+
75+
@Override
76+
protected void init() {
77+
super.init();
78+
79+
// Apply fullscreen flags for edge-to-edge WebView
5280
View decorView = getWindow().getDecorView();
53-
int systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
54-
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
81+
int systemUiVisibility = decorView.getSystemUiVisibility()
5582
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
5683

5784
// Set status bar icon color based on theme
@@ -64,8 +91,8 @@ protected void onCreate(Bundle savedInstanceState) {
6491

6592
decorView.setSystemUiVisibility(systemUiVisibility);
6693

67-
setContentView(R.layout.activity_vshare_web);
68-
ButterKnife.bind(this);
94+
// Set WebView top margin to status bar height
95+
applyStatusBarMargin();
6996

7097
setupWebView();
7198

@@ -114,8 +141,12 @@ private void setupWebView() {
114141
mWebView.setWebViewClient(new WebViewClient() {
115142
@Override
116143
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
117-
// Let the WebView handle the navigation
118-
return false;
144+
return handleUrlLoading(request.getUrl().toString());
145+
}
146+
147+
@Override
148+
public boolean shouldOverrideUrlLoading(WebView view, String url) {
149+
return handleUrlLoading(url);
119150
}
120151

121152
@Override
@@ -149,6 +180,114 @@ public void onProgressChanged(WebView view, int newProgress) {
149180
});
150181
}
151182

183+
/**
184+
* Apply status bar height as top margin to WebView
185+
*/
186+
private void applyStatusBarMargin() {
187+
int statusBarHeight = getStatusBarHeight();
188+
if (statusBarHeight > 0) {
189+
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mWebView.getLayoutParams();
190+
if (params instanceof FrameLayout.LayoutParams) {
191+
params.topMargin = statusBarHeight;
192+
mWebView.setLayoutParams(params);
193+
}
194+
}
195+
}
196+
197+
/**
198+
* Get status bar height dynamically
199+
*/
200+
private int getStatusBarHeight() {
201+
int result = 0;
202+
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
203+
if (resourceId > 0) {
204+
result = getResources().getDimensionPixelSize(resourceId);
205+
}
206+
return result;
207+
}
208+
209+
/**
210+
* Handle URL loading for WebView
211+
* Returns true if the URL was handled externally, false if WebView should load it
212+
*/
213+
private boolean handleUrlLoading(String url) {
214+
if (url == null) {
215+
return false;
216+
}
217+
218+
Uri uri = Uri.parse(url);
219+
String scheme = uri.getScheme();
220+
221+
// Handle intent:// URLs (e.g., Google Play Store links)
222+
if ("intent".equals(scheme)) {
223+
try {
224+
Intent intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
225+
226+
// Check if there's an app that can handle this intent
227+
if (getPackageManager().resolveActivity(intent, 0) != null) {
228+
startActivity(intent);
229+
return true;
230+
}
231+
232+
// Fallback: Try to open the browser_fallback_url if available
233+
String fallbackUrl = intent.getStringExtra("browser_fallback_url");
234+
if (fallbackUrl != null) {
235+
mWebView.loadUrl(fallbackUrl);
236+
return true;
237+
}
238+
239+
// Last resort: Try to open in Google Play if it's a Play Store intent
240+
String packageName = intent.getPackage();
241+
if (packageName != null) {
242+
Intent marketIntent = new Intent(Intent.ACTION_VIEW,
243+
Uri.parse("market://details?id=" + packageName));
244+
marketIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
245+
if (getPackageManager().resolveActivity(marketIntent, 0) != null) {
246+
startActivity(marketIntent);
247+
return true;
248+
}
249+
}
250+
} catch (Exception e) {
251+
Log.e(TAG, "Error handling intent URL: " + url, e);
252+
Toast.makeText(this, "Unable to open app", Toast.LENGTH_SHORT).show();
253+
}
254+
return true;
255+
}
256+
257+
// Handle market:// URLs (Google Play Store)
258+
if ("market".equals(scheme)) {
259+
try {
260+
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
261+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
262+
startActivity(intent);
263+
return true;
264+
} catch (ActivityNotFoundException e) {
265+
Log.e(TAG, "Google Play Store not found", e);
266+
// Fallback to web version
267+
String webUrl = url.replace("market://", "https://play.google.com/store/apps/");
268+
mWebView.loadUrl(webUrl);
269+
return true;
270+
}
271+
}
272+
273+
// Handle other app-specific schemes (e.g., mailto:, tel:, etc.)
274+
if (!"http".equals(scheme) && !"https".equals(scheme)) {
275+
try {
276+
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
277+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
278+
startActivity(intent);
279+
return true;
280+
} catch (ActivityNotFoundException e) {
281+
Log.e(TAG, "No app found to handle scheme: " + scheme, e);
282+
Toast.makeText(this, "No app found to open this link", Toast.LENGTH_SHORT).show();
283+
return true;
284+
}
285+
}
286+
287+
// Let WebView handle normal http/https URLs
288+
return false;
289+
}
290+
152291
@Override
153292
public void onBackPressed() {
154293
// Handle back navigation in WebView
@@ -170,4 +309,4 @@ protected void onDestroy() {
170309
}
171310
super.onDestroy();
172311
}
173-
}
312+
}

0 commit comments

Comments
 (0)