diff options
| author | cyberta <cyberta@riseup.net> | 2026-04-13 07:42:42 +0200 |
|---|---|---|
| committer | cyberta <cyberta@riseup.net> | 2026-04-15 23:56:05 +0000 |
| commit | d33310afc0dfa85a95419babc2f0f6c852074b70 (patch) | |
| tree | aceaba2a526a997df2828a967aa810ee6873cf11 | |
| parent | ba4e1500eb9813c2e88eea1d23caa432a21c019c (diff) | |
replace ListView with RecyclerView
3 files changed, 70 insertions, 98 deletions
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/ExcludeAppsFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/ExcludeAppsFragment.java index 57217a4f..06349cf2 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/ExcludeAppsFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/ExcludeAppsFragment.java @@ -5,6 +5,7 @@ package se.leap.bitmaskclient.base.fragments; +import static android.view.View.VISIBLE; import static se.leap.bitmaskclient.R.string.exclude_apps_fragment_title; import android.app.Activity; @@ -18,16 +19,20 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.BaseAdapter; import android.widget.Filter; import android.widget.Filterable; import android.widget.ImageView; -import android.widget.ListView; +import android.widget.LinearLayout; import android.widget.SearchView; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.WorkerThread; import androidx.appcompat.widget.AppCompatTextView; +import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import java.util.Locale; import java.util.Set; @@ -43,54 +48,41 @@ import se.leap.bitmaskclient.base.views.SimpleCheckBox; /** * Created by arne on 16.11.14. */ -public class ExcludeAppsFragment extends Fragment implements AdapterView.OnItemClickListener, SimpleCheckBox.OnCheckedChangeListener, View.OnClickListener { - private ListView mListView; +public class ExcludeAppsFragment extends Fragment implements SimpleCheckBox.OnCheckedChangeListener { + private static final String TAG = ExcludeAppsFragment.class.getSimpleName(); + private RecyclerView mRecyclerView; + private LinearLayout emptyView; private VpnProfile mProfile; private PackageAdapter mListAdapter; private Set<String> apps; - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - AppViewHolder avh = (AppViewHolder) view.getTag(); - avh.checkBox.toggle(); - } - - @Override - public void onClick(View v) { - - } - - static class AppViewHolder { + static class AppViewHolder extends RecyclerView.ViewHolder { public ApplicationInfo mInfo; - public View rootView; public AppCompatTextView appName; public ImageView appIcon; - //public AppCompatTextView appSize; - //public AppCompatTextView disabled; public SimpleCheckBox checkBox; - static public AppViewHolder createOrRecycle(LayoutInflater inflater, View convertView, ViewGroup parent) { - if (convertView == null) { - convertView = inflater.inflate(R.layout.allowed_application_layout, parent, false); - - // Creates a ViewHolder and store references to the two children views - // we want to bind data to. - AppViewHolder holder = new AppViewHolder(); - holder.rootView = convertView; - holder.appName = convertView.findViewById(R.id.app_name); - holder.appIcon = convertView.findViewById(R.id.app_icon); - holder.checkBox = convertView.findViewById(R.id.app_selected); - convertView.setTag(holder); - - return holder; - } else { - // Get the ViewHolder back to get fast access to the TextView - // and the ImageView. - return (AppViewHolder) convertView.getTag(); - } + public AppViewHolder(@NonNull View itemView) { + super(itemView); + appName = itemView.findViewById(R.id.app_name); + appIcon = itemView.findViewById(R.id.app_icon); + checkBox = itemView.findViewById(R.id.app_selected); + itemView.setOnClickListener(v -> checkBox.toggle()); } + public void bind(ApplicationInfo info, ApplicationInfoManager appInfoManager, Set<String> apps, SimpleCheckBox.OnCheckedChangeListener listener) { + mInfo = info; + CharSequence appLabelText = appInfoManager.loadLabel(info); + if (TextUtils.isEmpty(appLabelText)) { + appLabelText = info.packageName; + } + appName.setText(appLabelText); + appIcon.setImageDrawable(appInfoManager.loadDrawable(info)); + checkBox.setTag(info.packageName); + checkBox.setOnCheckedChangeListener(listener); + checkBox.setChecked(apps.contains(info.packageName)); + } } @Override @@ -107,7 +99,7 @@ public class ExcludeAppsFragment extends Fragment implements AdapterView.OnItemC } } - class PackageAdapter extends BaseAdapter implements Filterable { + class PackageAdapter extends RecyclerView.Adapter<AppViewHolder> implements Filterable { private Vector<ApplicationInfo> mPackages; private final LayoutInflater mInflater; private final ApplicationInfoManager mAppInfoManager; @@ -123,7 +115,6 @@ public class ExcludeAppsFragment extends Fragment implements AdapterView.OnItemC FilterResults results = new FilterResults(); - int count = mPackages.size(); final Vector<ApplicationInfo> nlist = new Vector<>(count); @@ -134,9 +125,9 @@ public class ExcludeAppsFragment extends Fragment implements AdapterView.OnItemC if (TextUtils.isEmpty(appName)) appName = pInfo.packageName; - if (appName instanceof String) { + if (appName instanceof String) { if (((String) appName).toLowerCase(Locale.getDefault()).contains(filterString)) - nlist.add(pInfo); + nlist.add(pInfo); } else { if (appName.toString().toLowerCase(Locale.getDefault()).contains(filterString)) nlist.add(pInfo); @@ -163,48 +154,35 @@ public class ExcludeAppsFragment extends Fragment implements AdapterView.OnItemC mPackages = new Vector<>(); mFilteredData = mPackages; } - + @WorkerThread private void populateList(Activity c) { - Vector<ApplicationInfo> apps = mAppInfoManager.getApplicationInfos(); - mPackages = apps; - mFilteredData = apps; - c.runOnUiThread(this::notifyDataSetChanged); + Vector<ApplicationInfo> appsList = mAppInfoManager.getApplicationInfos(); + mPackages = appsList; + mFilteredData = appsList; + c.runOnUiThread(() -> { + if (!appsList.isEmpty()) { + mRecyclerView.setVisibility(VISIBLE); + } + this.notifyDataSetChanged(); + }); } @Override - public int getCount() { + public int getItemCount() { return mFilteredData.size(); } + @NonNull @Override - public Object getItem(int position) { - return mFilteredData.get(position); + public AppViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View itemView = mInflater.inflate(R.layout.allowed_application_layout, parent, false); + return new AppViewHolder(itemView); } @Override - public long getItemId(int position) { - return mFilteredData.get(position).packageName.hashCode(); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - AppViewHolder viewHolder = AppViewHolder.createOrRecycle(mInflater, convertView, parent); - - viewHolder.mInfo = mFilteredData.get(position); - final ApplicationInfo mInfo = mFilteredData.get(position); - - - CharSequence appName = mAppInfoManager.loadLabel(mInfo); - - if (TextUtils.isEmpty(appName)) - appName = mInfo.packageName; - viewHolder.appName.setText(appName); - viewHolder.appIcon.setImageDrawable(mAppInfoManager.loadDrawable(mInfo)); - viewHolder.checkBox.setTag(mInfo.packageName); - viewHolder.checkBox.setOnCheckedChangeListener(ExcludeAppsFragment.this); - viewHolder.checkBox.setChecked(apps.contains(mInfo.packageName)); - - return viewHolder.rootView; + public void onBindViewHolder(@NonNull AppViewHolder holder, int position) { + ApplicationInfo mInfo = mFilteredData.get(position); + holder.bind(mInfo, mAppInfoManager, apps, ExcludeAppsFragment.this); } @Override @@ -236,29 +214,26 @@ public class ExcludeAppsFragment extends Fragment implements AdapterView.OnItemC @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.allowed_apps, menu); - SearchView searchView = (SearchView) menu.findItem( R.id.app_search_widget ).getActionView(); + SearchView searchView = (SearchView) menu.findItem(R.id.app_search_widget).getActionView(); if (searchView != null) { searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String query) { - mListView.setFilterText(query); - mListView.setTextFilterEnabled(true); + mListAdapter.getFilter().filter(query); return true; } @Override public boolean onQueryTextChange(String newText) { - mListView.setFilterText(newText); - if (TextUtils.isEmpty(newText)) - mListView.setTextFilterEnabled(false); - else - mListView.setTextFilterEnabled(true); - + if (TextUtils.isEmpty(newText)) { + mListAdapter.getFilter().filter(""); + } else { + mListAdapter.getFilter().filter(newText); + } return true; } }); searchView.setOnCloseListener(() -> { - mListView.clearTextFilter(); mListAdapter.getFilter().filter(""); return false; }); @@ -285,13 +260,14 @@ public class ExcludeAppsFragment extends Fragment implements AdapterView.OnItemC public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.allowed_vpn_apps, container, false); - mListView = v.findViewById(android.R.id.list); + mRecyclerView = v.findViewById(R.id.list); + mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); + emptyView = v.findViewById(R.id.loading_container); + emptyView.setVisibility(VISIBLE); mListAdapter = new PackageAdapter(getActivity(), mProfile); - mListView.setAdapter(mListAdapter); - mListView.setOnItemClickListener(this); + mRecyclerView.setAdapter(mListAdapter); - mListView.setEmptyView(v.findViewById(R.id.loading_container)); ViewHelper.setActionBarSubtitle(this, exclude_apps_fragment_title); new Thread(() -> mListAdapter.populateList(getActivity())).start(); @@ -301,8 +277,9 @@ public class ExcludeAppsFragment extends Fragment implements AdapterView.OnItemC @Override public void onDestroyView() { - mListView.setOnItemClickListener(null); + Log.d(TAG, "onDestroyView"); + mRecyclerView.setAdapter(null); mListAdapter = null; super.onDestroyView(); } -} +}
\ No newline at end of file diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/ApplicationInfoManager.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/ApplicationInfoManager.java index 1cf688fa..4ddbc92f 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/utils/ApplicationInfoManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/ApplicationInfoManager.java @@ -5,16 +5,13 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; -import android.util.Log; import androidx.annotation.WorkerThread; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.Vector; -import java.util.function.Predicate; public class ApplicationInfoManager { @@ -65,21 +62,19 @@ public class ApplicationInfoManager { public void onApplicationRemoved(String appPackage) { Set<String> excludedApps = PreferenceHelper.getExcludedApps(); - Log.d(TAG, "remove appPackage: " + appPackage); excludedApps.remove(appPackage); PreferenceHelper.setExcludedApps(excludedApps); } + @WorkerThread public void updateExcludedApps() { Set<String> excludedApps = PreferenceHelper.getExcludedApps(); - Log.d(TAG, "update excluded apps before: " + excludedApps.toString()); List<ApplicationInfo> appInfos = getApplicationInfos(); excludedApps.removeIf(s -> appInfos.stream().noneMatch( applicationInfo -> s.equals(applicationInfo.packageName) ) ); - Log.d(TAG, "update excluded apps after: " + excludedApps.toString()); PreferenceHelper.setExcludedApps(excludedApps); } } diff --git a/app/src/main/res/layout/allowed_vpn_apps.xml b/app/src/main/res/layout/allowed_vpn_apps.xml index 1930adf2..c9e74cf3 100644 --- a/app/src/main/res/layout/allowed_vpn_apps.xml +++ b/app/src/main/res/layout/allowed_vpn_apps.xml @@ -30,9 +30,9 @@ android:layout_height="1dp" android:background="?android:attr/listDivider"/> - <ListView + <androidx.recyclerview.widget.RecyclerView android:visibility="gone" - android:id="@android:id/list" + android:id="@+id/list" android:drawSelectorOnTop="false" android:layout_width="match_parent" android:layout_height="match_parent" |
