mirror of
https://github.com/strongswan/strongswan.git
synced 2025-10-04 00:00:14 -04:00
android: Apply UI changes for edge-to-edge views in Android 15+
When targeting Android 15, edge-to-edge is the default and when targeting Android 16, apps can't opt-out from this anymore. So we update our views and enable edge-to-edge also for older versions (avoids the black bar behind the system UI at the bottom). For most views we just use automatic margins via android:fitsSystemWindows (or programmatically via setDecorFitsSystemWindows). However, for the profile lists and log views, we take some extra measures that allow the lists to go behind the bottom system UI. Appropriate padding is applied at the bottom of the lists so the last item(s) can be scrolled into full view.
This commit is contained in:
parent
216a9dbb8d
commit
2404b2bee6
@ -46,6 +46,7 @@ android {
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.appcompat:appcompat:1.7.1'
|
||||
implementation 'androidx.core:core:1.17.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-process:2.9.4'
|
||||
implementation 'androidx.preference:preference:1.2.1'
|
||||
implementation 'com.google.android.material:material:1.13.0'
|
||||
|
@ -36,6 +36,7 @@
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/ApplicationTheme"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:allowBackup="false" >
|
||||
|
@ -26,10 +26,12 @@ import android.widget.Toast;
|
||||
import org.strongswan.android.R;
|
||||
import org.strongswan.android.data.LogContentProvider;
|
||||
import org.strongswan.android.logic.CharonVpnService;
|
||||
import org.strongswan.android.utils.Utils;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.view.WindowCompat;
|
||||
|
||||
public class LogActivity extends AppCompatActivity
|
||||
{
|
||||
@ -38,6 +40,8 @@ public class LogActivity extends AppCompatActivity
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.log_activity);
|
||||
WindowCompat.enableEdgeToEdge(getWindow());
|
||||
Utils.applyWindowInsetsAsMarginsForLists(findViewById(R.id.layout));
|
||||
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ import android.widget.ListView;
|
||||
|
||||
import org.strongswan.android.R;
|
||||
import org.strongswan.android.logic.CharonVpnService;
|
||||
import org.strongswan.android.utils.Utils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
@ -81,6 +82,8 @@ public class LogFragment extends Fragment
|
||||
mLog = view.findViewById(R.id.log);
|
||||
mLog.setAdapter(mLogAdapter);
|
||||
|
||||
Utils.applyWindowInsetsAsPaddingForLists(mLog);
|
||||
|
||||
mScrollPosition = -1;
|
||||
if (savedInstanceState != null)
|
||||
{
|
||||
|
@ -34,6 +34,7 @@ import org.strongswan.android.data.VpnProfile;
|
||||
import org.strongswan.android.logic.StrongSwanApplication;
|
||||
import org.strongswan.android.logic.TrustedCertificateManager;
|
||||
import org.strongswan.android.ui.VpnProfileListFragment.OnVpnProfileSelectedListener;
|
||||
import org.strongswan.android.utils.Utils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
@ -43,6 +44,7 @@ import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.app.AppCompatDialogFragment;
|
||||
import androidx.core.view.WindowCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
@ -66,6 +68,8 @@ public class MainActivity extends AppCompatActivity implements OnVpnProfileSelec
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.main);
|
||||
WindowCompat.enableEdgeToEdge(getWindow());
|
||||
Utils.applyWindowInsetsAsMarginsForLists(findViewById(R.id.layout));
|
||||
|
||||
ActionBar bar = getSupportActionBar();
|
||||
bar.setDisplayShowHomeEnabled(true);
|
||||
|
@ -18,6 +18,8 @@ package org.strongswan.android.ui;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.view.WindowCompat;
|
||||
|
||||
import android.view.MenuItem;
|
||||
|
||||
import org.strongswan.android.R;
|
||||
@ -33,6 +35,7 @@ public class RemediationInstructionsActivity extends AppCompatActivity implement
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.remediation_instructions);
|
||||
WindowCompat.enableEdgeToEdge(getWindow());
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
if (savedInstanceState != null)
|
||||
|
@ -26,6 +26,7 @@ import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.view.WindowCompat;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
||||
public class SelectedApplicationsActivity extends AppCompatActivity
|
||||
@ -37,6 +38,8 @@ public class SelectedApplicationsActivity extends AppCompatActivity
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
WindowCompat.enableEdgeToEdge(getWindow());
|
||||
WindowCompat.setDecorFitsSystemWindows(getWindow(), true);
|
||||
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
|
@ -20,6 +20,7 @@ import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.view.WindowCompat;
|
||||
|
||||
public class SettingsActivity extends AppCompatActivity
|
||||
{
|
||||
@ -28,6 +29,8 @@ public class SettingsActivity extends AppCompatActivity
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
WindowCompat.enableEdgeToEdge(getWindow());
|
||||
WindowCompat.setDecorFitsSystemWindows(getWindow(), true);
|
||||
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
|
@ -41,6 +41,7 @@ import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.app.AppCompatDialogFragment;
|
||||
import androidx.core.view.WindowCompat;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
|
||||
public class TrustedCertificateImportActivity extends AppCompatActivity
|
||||
|
@ -33,6 +33,7 @@ import org.strongswan.android.logic.TrustedCertificateManager;
|
||||
import org.strongswan.android.logic.TrustedCertificateManager.TrustedCertificateSource;
|
||||
import org.strongswan.android.security.TrustedCertificateEntry;
|
||||
import org.strongswan.android.ui.CertificateDeleteConfirmationDialog.OnCertificateDeleteListener;
|
||||
import org.strongswan.android.utils.Utils;
|
||||
|
||||
import java.security.KeyStore;
|
||||
|
||||
@ -41,6 +42,7 @@ import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.view.WindowCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||
@ -71,6 +73,7 @@ public class TrustedCertificatesActivity extends AppCompatActivity implements Tr
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.trusted_certificates_activity);
|
||||
WindowCompat.enableEdgeToEdge(getWindow());
|
||||
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
|
@ -84,6 +84,7 @@ import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.app.AppCompatDialogFragment;
|
||||
import androidx.appcompat.widget.SwitchCompat;
|
||||
import androidx.core.text.HtmlCompat;
|
||||
import androidx.core.view.WindowCompat;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
public class VpnProfileDetailActivity extends AppCompatActivity
|
||||
@ -199,6 +200,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity
|
||||
mDataSource.open();
|
||||
|
||||
setContentView(R.layout.profile_detail_view);
|
||||
WindowCompat.enableEdgeToEdge(getWindow());
|
||||
|
||||
mManagedProfile = findViewById(R.id.managed_profile);
|
||||
|
||||
|
@ -78,6 +78,7 @@ import javax.net.ssl.SSLHandshakeException;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.view.WindowCompat;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
import androidx.loader.content.AsyncTaskLoader;
|
||||
import androidx.loader.content.Loader;
|
||||
@ -204,6 +205,7 @@ public class VpnProfileImportActivity extends AppCompatActivity
|
||||
mDataSource.open();
|
||||
|
||||
setContentView(R.layout.profile_import_view);
|
||||
WindowCompat.enableEdgeToEdge(getWindow());
|
||||
|
||||
mProgressBar = findViewById(R.id.progress_bar);
|
||||
mExistsWarning = findViewById(R.id.exists_warning);
|
||||
|
@ -48,6 +48,7 @@ import org.strongswan.android.data.VpnProfileSource;
|
||||
import org.strongswan.android.logic.StrongSwanApplication;
|
||||
import org.strongswan.android.ui.adapter.VpnProfileAdapter;
|
||||
import org.strongswan.android.utils.Constants;
|
||||
import org.strongswan.android.utils.Utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
@ -148,6 +149,8 @@ public class VpnProfileListFragment extends Fragment implements MenuProvider
|
||||
mListView.setEmptyView(view.findViewById(R.id.profile_list_empty));
|
||||
mListView.setOnItemClickListener(mVpnProfileClicked);
|
||||
|
||||
Utils.applyWindowInsetsAsPaddingForLists(mListView);
|
||||
|
||||
if (!mReadOnly)
|
||||
{
|
||||
requireActivity().addMenuProvider(this, getViewLifecycleOwner());
|
||||
|
@ -22,11 +22,13 @@ import android.os.Bundle;
|
||||
import org.strongswan.android.R;
|
||||
import org.strongswan.android.data.VpnProfile;
|
||||
import org.strongswan.android.ui.VpnProfileListFragment.OnVpnProfileSelectedListener;
|
||||
import org.strongswan.android.utils.Utils;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.content.pm.ShortcutInfoCompat;
|
||||
import androidx.core.content.pm.ShortcutManagerCompat;
|
||||
import androidx.core.graphics.drawable.IconCompat;
|
||||
import androidx.core.view.WindowCompat;
|
||||
|
||||
public class VpnProfileSelectActivity extends AppCompatActivity implements OnVpnProfileSelectedListener
|
||||
{
|
||||
@ -35,6 +37,8 @@ public class VpnProfileSelectActivity extends AppCompatActivity implements OnVpn
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.vpn_profile_select);
|
||||
WindowCompat.enableEdgeToEdge(getWindow());
|
||||
Utils.applyWindowInsetsAsMarginsForLists(findViewById(R.id.layout));
|
||||
|
||||
/* we should probably return a result also if the user clicks the back
|
||||
* button before selecting a profile */
|
||||
|
@ -17,9 +17,16 @@
|
||||
package org.strongswan.android.utils;
|
||||
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import androidx.core.graphics.Insets;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
|
||||
public class Utils
|
||||
{
|
||||
static final char[] HEXDIGITS = "0123456789abcdef".toCharArray();
|
||||
@ -75,4 +82,39 @@ public class Utils
|
||||
}
|
||||
return InetAddress.getByAddress(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply window insets for the system UI as margins except for the bottom,
|
||||
* which is useful if the view ends with a list. WindowInsetsCompat.CONSUMED
|
||||
* is not returned so padding can be applied to the list.
|
||||
*
|
||||
* @param view view to apply margins to
|
||||
*/
|
||||
public static void applyWindowInsetsAsMarginsForLists(View view)
|
||||
{
|
||||
ViewCompat.setOnApplyWindowInsetsListener(view, (v, windowInsets) -> {
|
||||
Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
|
||||
ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams)v.getLayoutParams();
|
||||
mlp.topMargin = insets.top;
|
||||
mlp.leftMargin = insets.left;
|
||||
mlp.rightMargin = insets.right;
|
||||
v.setLayoutParams(mlp);
|
||||
return windowInsets;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply bottom inset for the system UI as padding on the given (list) view
|
||||
* so the last item can be scrolled fully into view.
|
||||
*
|
||||
* @param view view to apply padding to
|
||||
*/
|
||||
public static void applyWindowInsetsAsPaddingForLists(View view)
|
||||
{
|
||||
ViewCompat.setOnApplyWindowInsetsListener(view, (v, windowInsets) -> {
|
||||
Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
|
||||
v.setPaddingRelative(0, 0, 0, insets.bottom);
|
||||
return WindowInsetsCompat.CONSUMED;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,8 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:baselineAligned="false" >
|
||||
android:baselineAligned="false"
|
||||
android:fitsSystemWindows="true" >
|
||||
|
||||
<fragment
|
||||
class="org.strongswan.android.ui.RemediationInstructionsFragment"
|
||||
|
@ -16,7 +16,8 @@
|
||||
-->
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" >
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/layout" >
|
||||
|
||||
<fragment
|
||||
class="org.strongswan.android.ui.LogFragment"
|
||||
|
@ -14,23 +14,24 @@
|
||||
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
for more details.
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical" >
|
||||
android:paddingBottom="0dp"
|
||||
android:paddingTop="5dp"
|
||||
android:paddingStart="5dp"
|
||||
android:paddingEnd="5dp" >
|
||||
|
||||
<ListView
|
||||
android:id="@+id/log"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="10dp"
|
||||
android:dividerHeight="0dp"
|
||||
android:divider="@null"
|
||||
android:fadeScrollbars="false"
|
||||
android:scrollbarFadeDuration="0"
|
||||
android:scrollbarAlwaysDrawVerticalTrack="true"
|
||||
android:transcriptMode="normal">
|
||||
android:transcriptMode="normal"
|
||||
android:clipToPadding="false" />
|
||||
|
||||
</ListView>
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
@ -17,7 +17,8 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical" >
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/layout" >
|
||||
|
||||
<fragment
|
||||
class="org.strongswan.android.ui.VpnStateFragment"
|
||||
|
@ -20,7 +20,8 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/managed_profile"
|
||||
|
@ -15,9 +15,10 @@
|
||||
for more details.
|
||||
-->
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" >
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true" >
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
@ -17,7 +17,7 @@
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="10dp"
|
||||
android:paddingBottom="0dp"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingStart="5dp"
|
||||
android:paddingEnd="5dp" >
|
||||
@ -28,9 +28,11 @@
|
||||
android:layout_height="match_parent"
|
||||
android:dividerHeight="1dp"
|
||||
android:divider="?android:attr/listDivider"
|
||||
android:scrollbarAlwaysDrawVerticalTrack="true" />
|
||||
android:overScrollFooter="@android:color/transparent"
|
||||
android:scrollbarAlwaysDrawVerticalTrack="true"
|
||||
android:clipToPadding="false" />
|
||||
|
||||
<TextView android:id="@+id/profile_list_empty"
|
||||
<TextView android:id="@+id/profile_list_empty"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="15dp"
|
||||
|
@ -17,6 +17,7 @@
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
android:id="@+id/fragment_container">
|
||||
|
||||
</FrameLayout>
|
||||
</FrameLayout>
|
||||
|
@ -15,10 +15,11 @@
|
||||
for more details.
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tabs"
|
||||
|
@ -18,7 +18,8 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical" >
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/layout" >
|
||||
|
||||
<fragment
|
||||
class="org.strongswan.android.ui.VpnProfileListFragment"
|
||||
@ -28,4 +29,4 @@
|
||||
android:layout_weight="1"
|
||||
app:read_only="true" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
Loading…
x
Reference in New Issue
Block a user