diff --git a/TODO.md b/TODO.md index 146e152d..3163aa49 100644 --- a/TODO.md +++ b/TODO.md @@ -105,25 +105,3 @@ If revisited later, start by deciding whether the desired goal is: - just to reduce cross-app cache bleed, or - to redesign tracker attribution so "per-app blocking" is backed by per-app evidence. - -## Remaining Material 3 migration items - -The core M3 migration is complete (themes, switches, settings with MaterialToolbar, FABs, tabs). The following are cosmetic items that still reference AppCompat/MaterialComponents but work correctly: - -### TextAppearance references (~16 occurrences) -- `traffic.xml` — 8 uses of `Base.TextAppearance.AppCompat.Small` → should be `TextAppearance.Material3.BodySmall` -- `item_onboarding.xml` / `item_onboarding_blocking_mode.xml` — uses of `TextAppearance.AppCompat.Large`, `.Body1`, `.Caption` → should be `TextAppearance.Material3.HeadlineSmall`, `.BodyLarge`, `.LabelSmall` - -### Button styles (4 buttons) -- `activity_onboarding.xml:28` — `Widget.AppCompat.Button.Borderless` → `Widget.Material3.Button.TextButton` -- `troubleshooting.xml:56,90,156` — `Widget.AppCompat.Button.Borderless.Colored` → `Widget.Material3.Button.TextButton` - -### Layout-level theme references -- `traffic.xml:5` — `Theme.AppCompat.Light.DarkActionBar` on a RelativeLayout -- `list_item_trackers_header.xml:158-159` — `Theme.MaterialComponents.DayNight` and `Widget.MaterialComponents.Button.OutlinedButton` → `Theme.Material3.DayNight` and `Widget.Material3.Button.OutlinedButton` - -### AlertDialog → MaterialAlertDialogBuilder (12 occurrences) -Uses of `androidx.appcompat.app.AlertDialog.Builder` across ActivityMain, ActivityLog, ActivitySettings, ActivityForwarding, ActivityBlocklists, ActionsFragment, and Util. Migrating to `com.google.android.material.dialog.MaterialAlertDialogBuilder` would give M3-styled dialogs (rounded corners, M3 color tokens). - -### Action bar styles in styles.xml -`ActionBar.Red`, `ActionBarTheme.Red`, `Toolbar.Red`, `ActionBarTitleText` still reference AppCompat parents (`Widget.AppCompat.ActionBar.Solid`, `TextAppearance.AppCompat.Widget.ActionBar.Title`). These are used by the main screen's legacy action bar and work correctly but aren't pure M3. diff --git a/app/build.gradle b/app/build.gradle index e88829c6..9a285d3b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -137,6 +137,7 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) // https://developer.android.com/jetpack/androidx/releases/ + implementation 'androidx.activity:activity:1.9.3' implementation 'androidx.appcompat:appcompat:1.7.1' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0' implementation 'androidx.recyclerview:recyclerview:1.4.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b312699b..40eaa693 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -172,7 +172,7 @@ android:exported="true" android:label="@string/title_activity_detail" android:parentActivityName="eu.faircode.netguard.ActivityMain" - android:theme="@style/AppTheme.EdgeToEdge"> + android:theme="@style/AppTheme.NoActionBar"> diff --git a/app/src/main/java/eu/faircode/netguard/ActivityForwarding.java b/app/src/main/java/eu/faircode/netguard/ActivityForwarding.java index 0ebfee88..37bf8da4 100644 --- a/app/src/main/java/eu/faircode/netguard/ActivityForwarding.java +++ b/app/src/main/java/eu/faircode/netguard/ActivityForwarding.java @@ -39,6 +39,8 @@ import android.widget.Toast; import androidx.appcompat.app.AlertDialog; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; import androidx.appcompat.app.AppCompatActivity; import net.kollnig.missioncontrol.R; @@ -183,7 +185,7 @@ protected void onPostExecute(List rules) { }; task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - dialog = new AlertDialog.Builder(this) + dialog = new MaterialAlertDialogBuilder(this) .setView(view) .setCancelable(true) .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { diff --git a/app/src/main/java/eu/faircode/netguard/ActivityLog.java b/app/src/main/java/eu/faircode/netguard/ActivityLog.java index dfa7d9c5..7607b16b 100644 --- a/app/src/main/java/eu/faircode/netguard/ActivityLog.java +++ b/app/src/main/java/eu/faircode/netguard/ActivityLog.java @@ -41,6 +41,8 @@ import android.widget.Toast; import androidx.appcompat.app.AlertDialog; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.SearchView; import com.google.android.material.materialswitch.MaterialSwitch; @@ -405,7 +407,7 @@ public boolean onOptionsItemSelected(MenuItem item) { } // warn that enabling will send data to ipinfo.io - AlertDialog.Builder builder = new AlertDialog.Builder(this) + AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this) .setTitle(R.string.confirm_ipinfo_title) .setMessage(R.string.confirm_ipinfo) .setPositiveButton(R.string.yes, (dialog, id2) -> { diff --git a/app/src/main/java/eu/faircode/netguard/ActivityMain.java b/app/src/main/java/eu/faircode/netguard/ActivityMain.java index a04704a0..b4e10a7f 100644 --- a/app/src/main/java/eu/faircode/netguard/ActivityMain.java +++ b/app/src/main/java/eu/faircode/netguard/ActivityMain.java @@ -60,6 +60,8 @@ import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.SearchView; import com.google.android.material.materialswitch.MaterialSwitch; @@ -182,7 +184,7 @@ protected void onCreate(Bundle savedInstanceState) { // Check for filtering if (!Util.canFilter(this)) { - AlertDialog.Builder builder = new AlertDialog.Builder(this) + AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this) .setTitle(R.string.device_not_supported_title) .setMessage(R.string.device_not_supported_msg) .setPositiveButton(R.string.ok, (dialog, id) -> { @@ -302,7 +304,7 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { LayoutInflater inflater = LayoutInflater.from(ActivityMain.this); final View view = inflater.inflate(R.layout.vpn, null, false); - dialogVpn = new AlertDialog.Builder(ActivityMain.this) + dialogVpn = new MaterialAlertDialogBuilder(ActivityMain.this) .setView(view) .setCancelable(false) .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { @@ -1197,7 +1199,7 @@ public void onClick(View v) { } }); - dialogTroubleshooting = new AlertDialog.Builder(this) + dialogTroubleshooting = new MaterialAlertDialogBuilder(this) .setView(view) .setCancelable(true) .setPositiveButton(android.R.string.ok, null) @@ -1247,7 +1249,7 @@ private void menu_legend() { } // Show dialog - dialogLegend = new AlertDialog.Builder(this) + dialogLegend = new MaterialAlertDialogBuilder(this) .setView(view) .setCancelable(true) .setOnDismissListener(new DialogInterface.OnDismissListener() { @@ -1289,7 +1291,7 @@ public void onClick(View view) { }); // Show dialog - dialogAbout = new AlertDialog.Builder(this) + dialogAbout = new MaterialAlertDialogBuilder(this) .setView(view) .setCancelable(true) .setOnDismissListener(new DialogInterface.OnDismissListener() { diff --git a/app/src/main/java/eu/faircode/netguard/ActivitySettings.java b/app/src/main/java/eu/faircode/netguard/ActivitySettings.java index f22968db..31e99c5a 100644 --- a/app/src/main/java/eu/faircode/netguard/ActivitySettings.java +++ b/app/src/main/java/eu/faircode/netguard/ActivitySettings.java @@ -52,6 +52,8 @@ import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.NavUtils; import androidx.core.util.PatternsCompat; @@ -624,7 +626,7 @@ else if ("filter".equals(name)) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && prefs.getBoolean(name, true)) { LayoutInflater inflater = LayoutInflater.from(ActivitySettings.this); View view = inflater.inflate(R.layout.filter, null, false); - dialogFilter = new AlertDialog.Builder(ActivitySettings.this) + dialogFilter = new MaterialAlertDialogBuilder(ActivitySettings.this) .setView(view) .setCancelable(false) .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { diff --git a/app/src/main/java/eu/faircode/netguard/ApplicationEx.java b/app/src/main/java/eu/faircode/netguard/ApplicationEx.java index 4134d18c..d1f000fd 100644 --- a/app/src/main/java/eu/faircode/netguard/ApplicationEx.java +++ b/app/src/main/java/eu/faircode/netguard/ApplicationEx.java @@ -31,25 +31,19 @@ import android.content.Context; import android.content.SharedPreferences; import android.graphics.Color; -import android.graphics.drawable.ColorDrawable; import android.os.Build; import android.os.Bundle; import android.os.StrictMode; import android.util.Log; -import android.view.View; +import androidx.activity.ComponentActivity; +import androidx.activity.EdgeToEdge; +import androidx.activity.SystemBarStyle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; -import androidx.core.graphics.Insets; -import androidx.core.view.OnApplyWindowInsetsListener; -import androidx.core.view.ViewCompat; -import androidx.core.view.WindowCompat; -import androidx.core.view.WindowInsetsCompat; import net.kollnig.missioncontrol.BuildConfig; -import net.kollnig.missioncontrol.Common; -import net.kollnig.missioncontrol.DetailsActivity; import net.kollnig.missioncontrol.R; import net.kollnig.missioncontrol.data.BlockingMode; @@ -129,79 +123,39 @@ public void onCreate() { registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() { @Override public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) { - if (activity.getClass() == DetailsActivity.class) - return; - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { - android.widget.FrameLayout content = activity.findViewById(android.R.id.content); - - // On Android 15+, setStatusBarColor is ignored - // Set window background to primary color - this shows behind the status bar + if (activity instanceof ComponentActivity) { int statusBarColor = ContextCompat.getColor(activity, R.color.colorPrimaryDark); - activity.getWindow().setBackgroundDrawable(new ColorDrawable(statusBarColor)); - - boolean isNight = Common.isNight(activity); - - // Set status bar icons to light (white) since our background is dark - View decor = activity.getWindow().getDecorView(); - WindowCompat.getInsetsController(activity.getWindow(), decor).setAppearanceLightStatusBars(false); - WindowCompat.getInsetsController(activity.getWindow(), decor) - .setAppearanceLightNavigationBars(!isNight); - - ViewCompat.setOnApplyWindowInsetsListener(content, new OnApplyWindowInsetsListener() { - @NonNull - @Override - public WindowInsetsCompat onApplyWindowInsets(@NonNull View v, - @NonNull WindowInsetsCompat insets) { - Insets bars = insets.getInsets(WindowInsetsCompat.Type.systemBars() - | WindowInsetsCompat.Type.displayCutout() | WindowInsetsCompat.Type.ime()); - - // Apply padding to android.R.id.content for system bars - // We do NOT apply bottom padding here, so the content background (White/Black) - // extends to the bottom - v.setPadding(bars.left, bars.top, bars.right, 0); - - // Set background on the actual layout (first child), not on the content frame - // This way the padding area shows the window background (primary color) - if (content.getChildCount() > 0) { - View child = content.getChildAt(0); - child.setBackgroundColor(Common.isNight(activity) ? Color.BLACK : Color.WHITE); - } - - return insets; - } - }); + // SystemBarStyle.dark() is critical: without it, EdgeToEdge defaults to + // transparent, and M3's light theme produces white-on-white status bar icons. + EdgeToEdge.enable( + (ComponentActivity) activity, + SystemBarStyle.dark(statusBarColor), + SystemBarStyle.auto(Color.TRANSPARENT, Color.TRANSPARENT)); } } @Override public void onActivityStarted(@NonNull Activity activity) { - } @Override public void onActivityResumed(@NonNull Activity activity) { - } @Override public void onActivityPaused(@NonNull Activity activity) { - } @Override public void onActivityStopped(@NonNull Activity activity) { - } @Override public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) { - } @Override public void onActivityDestroyed(@NonNull Activity activity) { - } }); } diff --git a/app/src/main/java/eu/faircode/netguard/Util.java b/app/src/main/java/eu/faircode/netguard/Util.java index 4a89443f..52690fe5 100644 --- a/app/src/main/java/eu/faircode/netguard/Util.java +++ b/app/src/main/java/eu/faircode/netguard/Util.java @@ -50,7 +50,7 @@ import android.view.View; import android.widget.TextView; -import androidx.appcompat.app.AlertDialog; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; import androidx.appcompat.app.AppCompatDelegate; import androidx.core.app.ActivityCompat; import androidx.core.net.ConnectivityManagerCompat; @@ -560,7 +560,7 @@ public static void areYouSure(Context context, int explanation, final DoubtListe View view = inflater.inflate(R.layout.sure, null, false); TextView tvExplanation = view.findViewById(R.id.tvExplanation); tvExplanation.setText(explanation); - new AlertDialog.Builder(context) + new MaterialAlertDialogBuilder(context) .setView(view) .setCancelable(true) .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { diff --git a/app/src/main/java/net/kollnig/missioncontrol/ActivityBlocklists.java b/app/src/main/java/net/kollnig/missioncontrol/ActivityBlocklists.java index 07827798..6d2697b6 100644 --- a/app/src/main/java/net/kollnig/missioncontrol/ActivityBlocklists.java +++ b/app/src/main/java/net/kollnig/missioncontrol/ActivityBlocklists.java @@ -14,6 +14,8 @@ import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; import androidx.appcompat.app.AppCompatActivity; import com.google.android.material.materialswitch.MaterialSwitch; import androidx.recyclerview.widget.LinearLayoutManager; @@ -68,7 +70,7 @@ public boolean onOptionsItemSelected(MenuItem item) { } private void showAddDialog(Blocklist item) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); + AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this); builder.setTitle(item == null ? R.string.title_add_blocklist : R.string.title_blocklists); final EditText input = new EditText(this); @@ -162,7 +164,7 @@ public void onBindViewHolder(@NonNull ViewHolder holder, int position) { holder.itemView.setOnClickListener(v -> showAddDialog(item)); holder.btnDelete.setOnClickListener(v -> { - new AlertDialog.Builder(context) + new MaterialAlertDialogBuilder(context) .setTitle(R.string.title_delete_blocklist) .setMessage(R.string.msg_delete_blocklist_confirm) .setPositiveButton(android.R.string.yes, (dialog, which) -> { diff --git a/app/src/main/java/net/kollnig/missioncontrol/DetailsActivity.java b/app/src/main/java/net/kollnig/missioncontrol/DetailsActivity.java index 06e242f7..39ec95cc 100644 --- a/app/src/main/java/net/kollnig/missioncontrol/DetailsActivity.java +++ b/app/src/main/java/net/kollnig/missioncontrol/DetailsActivity.java @@ -26,7 +26,6 @@ import android.content.Intent; import android.content.SharedPreferences; import android.database.Cursor; -import android.graphics.drawable.ColorDrawable; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; @@ -40,12 +39,9 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import androidx.core.app.NavUtils; -import androidx.core.content.ContextCompat; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; -import androidx.core.view.WindowCompat; import androidx.core.view.WindowInsetsCompat; -import androidx.core.view.WindowInsetsControllerCompat; import androidx.viewpager2.widget.ViewPager2; import com.google.android.material.appbar.AppBarLayout; @@ -122,22 +118,9 @@ protected void onActivityResult(int requestCode, int resultCode, final Intent da @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // Enable edge-to-edge content - WindowCompat.setDecorFitsSystemWindows(getWindow(), false); + // Edge-to-edge is enabled globally by ApplicationEx via EdgeToEdge.enable() setContentView(R.layout.activity_details); - // Status bar appearance - WindowInsetsControllerCompat insetsController = new WindowInsetsControllerCompat(getWindow(), - getWindow().getDecorView()); - insetsController.setAppearanceLightStatusBars(false); - if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) { - getWindow().setStatusBarColor(ContextCompat.getColor(this, R.color.colorPrimaryDark)); - } - - // Set window background to primary dark color to show behind the transparent - // status bar - getWindow().setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.colorPrimaryDark))); - running = true; // Receive about details diff --git a/app/src/main/java/net/kollnig/missioncontrol/details/ActionsFragment.java b/app/src/main/java/net/kollnig/missioncontrol/details/ActionsFragment.java index bd11eca6..949cad3f 100644 --- a/app/src/main/java/net/kollnig/missioncontrol/details/ActionsFragment.java +++ b/app/src/main/java/net/kollnig/missioncontrol/details/ActionsFragment.java @@ -30,6 +30,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; import androidx.fragment.app.Fragment; import com.google.android.material.snackbar.Snackbar; @@ -136,7 +138,7 @@ public void onClick(View v) { Context c = getContext(); if (c == null) return; - AlertDialog.Builder builder = new AlertDialog.Builder(c) + AlertDialog.Builder builder = new MaterialAlertDialogBuilder(c) .setTitle(R.string.external_servers) .setMessage(R.string.confirm_google_info) .setPositiveButton(R.string.yes, (dialog, id2) -> { diff --git a/app/src/main/res/layout/activity_onboarding.xml b/app/src/main/res/layout/activity_onboarding.xml index 95556582..dc4a4312 100644 --- a/app/src/main/res/layout/activity_onboarding.xml +++ b/app/src/main/res/layout/activity_onboarding.xml @@ -25,7 +25,7 @@