diff --git a/CHANGELOG.md b/CHANGELOG.md index 333b761..1709a07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ ## Unreleased +Configurable native WebView loading indicators. + +- Preserves the current top loading bar as the default for existing apps. +- Adds per-app modes for no indicator, top bar, bottom bar, centered spinner and fullscreen spinner. +- Adds configurable indicator color, bar thickness, spinner size and fullscreen overlay dimming. +- Loading UI remains separate from the mandatory STU production splash and follows the existing WebView navigation/progress callbacks. +- Implementation remains compatible with the Android 6 / API 23 baseline. + Dynamic image recovery through Quantum Asset Store. - Trusted image requests below `/assets/` that miss the native store are now warmed immediately in parallel while WebView keeps its ordinary live request path. diff --git a/app/src/main/java/de/starlightunit/wrapper/MainActivity.java b/app/src/main/java/de/starlightunit/wrapper/MainActivity.java index cb25daf..1d41b81 100644 --- a/app/src/main/java/de/starlightunit/wrapper/MainActivity.java +++ b/app/src/main/java/de/starlightunit/wrapper/MainActivity.java @@ -26,6 +26,7 @@ import de.starlightunit.wrapper.media.QuantumNativeMediaPlayer; import de.starlightunit.wrapper.navigation.NavigationPolicy; import de.starlightunit.wrapper.session.QuantumSessionCookieStore; +import de.starlightunit.wrapper.ui.LoadingIndicatorController; import de.starlightunit.wrapper.web.GameWebChromeClient; import de.starlightunit.wrapper.web.GameWebViewClient; import de.starlightunit.wrapper.web.WebViewConfigurator; @@ -40,7 +41,7 @@ public final class MainActivity extends Activity private static final int FILE_CHOOSER_REQUEST = 7001; private WebView webView; - private ProgressBar progressBar; + private LoadingIndicatorController loadingIndicator; private View errorPanel; private GameWebChromeClient chromeClient; private GameWebViewClient webViewClient; @@ -60,9 +61,22 @@ protected void onCreate(Bundle savedInstanceState) { enterImmersiveMode(); webView = findViewById(R.id.web_view); - progressBar = findViewById(R.id.progress); + ProgressBar horizontalProgress = findViewById(R.id.progress); + FrameLayout loadingOverlay = findViewById(R.id.loading_overlay); + ProgressBar loadingSpinner = findViewById(R.id.loading_spinner); errorPanel = findViewById(R.id.error_panel); FrameLayout fullscreenContainer = findViewById(R.id.fullscreen_container); + loadingIndicator = new LoadingIndicatorController( + this, + horizontalProgress, + loadingOverlay, + loadingSpinner, + AppConfig.LOADING_INDICATOR_STYLE, + AppConfig.LOADING_INDICATOR_COLOR, + AppConfig.LOADING_BAR_THICKNESS_DP, + AppConfig.LOADING_SPINNER_SIZE_DP, + AppConfig.LOADING_OVERLAY_DIM_PERCENT + ); ImageView introOverlay = findViewById(R.id.intro_overlay); Button retryButton = findViewById(R.id.retry_button); @@ -172,12 +186,12 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { public void onPageLoading() { mainFrameFailed = false; errorPanel.setVisibility(View.GONE); - progressBar.setVisibility(View.VISIBLE); + loadingIndicator.show(); } @Override public void onPageReady() { - progressBar.setVisibility(View.GONE); + loadingIndicator.hide(); if (!mainFrameFailed) { errorPanel.setVisibility(View.GONE); } @@ -189,14 +203,13 @@ public void onPageReady() { @Override public void onMainFrameError() { mainFrameFailed = true; - progressBar.setVisibility(View.GONE); + loadingIndicator.hide(); errorPanel.setVisibility(View.VISIBLE); } @Override public void onProgress(int progress) { - progressBar.setProgress(progress); - progressBar.setVisibility(progress >= 100 ? View.GONE : View.VISIBLE); + loadingIndicator.setProgress(progress); } @Override diff --git a/app/src/main/java/de/starlightunit/wrapper/config/AppConfig.java b/app/src/main/java/de/starlightunit/wrapper/config/AppConfig.java index 0ce5494..ee98294 100644 --- a/app/src/main/java/de/starlightunit/wrapper/config/AppConfig.java +++ b/app/src/main/java/de/starlightunit/wrapper/config/AppConfig.java @@ -26,6 +26,12 @@ public final class AppConfig { public static final String APP_HEADER_VALUE = "starlight-unit-game"; public static final String CUSTOM_REQUEST_HEADERS_JSON = "{}"; + public static final String LOADING_INDICATOR_STYLE = "top-bar"; + public static final String LOADING_INDICATOR_COLOR = "#6fc7ff"; + public static final int LOADING_BAR_THICKNESS_DP = 3; + public static final int LOADING_SPINNER_SIZE_DP = 56; + public static final int LOADING_OVERLAY_DIM_PERCENT = 35; + public static final boolean KEEP_SCREEN_ON = true; public static final boolean ALLOW_AUTOPLAY_MEDIA = true; public static final boolean ALLOW_THIRD_PARTY_COOKIES = true; diff --git a/app/src/main/java/de/starlightunit/wrapper/ui/LoadingIndicatorController.java b/app/src/main/java/de/starlightunit/wrapper/ui/LoadingIndicatorController.java new file mode 100644 index 0000000..68baca3 --- /dev/null +++ b/app/src/main/java/de/starlightunit/wrapper/ui/LoadingIndicatorController.java @@ -0,0 +1,148 @@ +package de.starlightunit.wrapper.ui; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.view.Gravity; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ProgressBar; + +import java.util.Locale; + +public final class LoadingIndicatorController { + + public static final String STYLE_NONE = "none"; + public static final String STYLE_TOP_BAR = "top-bar"; + public static final String STYLE_BOTTOM_BAR = "bottom-bar"; + public static final String STYLE_CENTER_SPINNER = "center-spinner"; + public static final String STYLE_FULLSCREEN_SPINNER = "fullscreen-spinner"; + + private final ProgressBar horizontalBar; + private final FrameLayout spinnerOverlay; + private final ProgressBar spinner; + private final String style; + + public LoadingIndicatorController( + Context context, + ProgressBar horizontalBar, + FrameLayout spinnerOverlay, + ProgressBar spinner, + String configuredStyle, + String configuredColor, + int barThicknessDp, + int spinnerSizeDp, + int overlayDimPercent + ) { + this.horizontalBar = horizontalBar; + this.spinnerOverlay = spinnerOverlay; + this.spinner = spinner; + this.style = normalizeStyle(configuredStyle); + + int color = parseColor(configuredColor); + ColorStateList tint = ColorStateList.valueOf(color); + horizontalBar.setProgressTintList(tint); + spinner.setIndeterminateTintList(tint); + + float density = context.getResources().getDisplayMetrics().density; + configureHorizontalBar( + Math.max(1, Math.min(12, barThicknessDp)), + density + ); + configureSpinner( + Math.max(24, Math.min(128, spinnerSizeDp)), + density + ); + + int dimPercent = Math.max(0, Math.min(90, overlayDimPercent)); + int alpha = Math.round(255f * (dimPercent / 100f)); + spinnerOverlay.setBackgroundColor( + STYLE_FULLSCREEN_SPINNER.equals(style) + ? Color.argb(alpha, 0, 0, 0) + : Color.TRANSPARENT + ); + hide(); + } + + public void show() { + if (STYLE_NONE.equals(style)) { + hide(); + return; + } + + if (isBarStyle()) { + spinnerOverlay.setVisibility(View.GONE); + spinner.setVisibility(View.GONE); + horizontalBar.setVisibility(View.VISIBLE); + return; + } + + horizontalBar.setVisibility(View.GONE); + spinnerOverlay.setVisibility(View.VISIBLE); + spinner.setVisibility(View.VISIBLE); + } + + public void hide() { + horizontalBar.setVisibility(View.GONE); + spinner.setVisibility(View.GONE); + spinnerOverlay.setVisibility(View.GONE); + } + + public void setProgress(int progress) { + int bounded = Math.max(0, Math.min(100, progress)); + horizontalBar.setProgress(bounded); + if (bounded >= 100) { + hide(); + } else { + show(); + } + } + + static String normalizeStyle(String value) { + String normalized = value == null ? "" : value.trim().toLowerCase(Locale.ROOT); + switch (normalized) { + case STYLE_NONE: + case STYLE_TOP_BAR: + case STYLE_BOTTOM_BAR: + case STYLE_CENTER_SPINNER: + case STYLE_FULLSCREEN_SPINNER: + return normalized; + default: + return STYLE_TOP_BAR; + } + } + + private boolean isBarStyle() { + return STYLE_TOP_BAR.equals(style) || STYLE_BOTTOM_BAR.equals(style); + } + + private void configureHorizontalBar(int thicknessDp, float density) { + FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) horizontalBar.getLayoutParams(); + params.width = FrameLayout.LayoutParams.MATCH_PARENT; + params.height = Math.max(1, Math.round(thicknessDp * density)); + params.gravity = STYLE_BOTTOM_BAR.equals(style) ? Gravity.BOTTOM : Gravity.TOP; + horizontalBar.setLayoutParams(params); + horizontalBar.setMax(100); + horizontalBar.setIndeterminate(false); + } + + private void configureSpinner(int spinnerSizeDp, float density) { + FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) spinner.getLayoutParams(); + int pixels = Math.max(1, Math.round(spinnerSizeDp * density)); + params.width = pixels; + params.height = pixels; + params.gravity = Gravity.CENTER; + spinner.setLayoutParams(params); + spinner.setIndeterminate(true); + } + + private static int parseColor(String configuredColor) { + if (configuredColor != null) { + try { + return Color.parseColor(configuredColor.trim()); + } catch (IllegalArgumentException ignored) { + } + } + return Color.parseColor("#6fc7ff"); + } +} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 4cce85b..f71ac23 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -20,6 +20,24 @@ android:max="100" android:visibility="gone" /> + + + + +