From addf8d89962bf3de6d70330f9264d0e4d866613e Mon Sep 17 00:00:00 2001 From: cyberta Date: Sat, 29 Jul 2023 17:15:05 +0200 Subject: update design and UX for provider setup --- .../fragments/CircumventionSetupFragment.java | 46 ++++++ .../fragments/ConfigureProviderFragment.java | 163 +++++++++++++++++++++ .../fragments/ProviderSelectionFragment.java | 138 +++++++++++++++++ .../viewmodel/ProviderSelectionViewModel.java | 81 ++++++++++ .../ProviderSelectionViewModelFactory.java | 28 ++++ 5 files changed, 456 insertions(+) create mode 100644 app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModel.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModelFactory.java (limited to 'app/src/main/java/se/leap/bitmaskclient/providersetup/fragments') diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java new file mode 100644 index 00000000..606de943 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java @@ -0,0 +1,46 @@ +package se.leap.bitmaskclient.providersetup.fragments; + +import android.graphics.Typeface; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import se.leap.bitmaskclient.base.utils.PreferenceHelper; +import se.leap.bitmaskclient.databinding.FCircumventionSetupBinding; + +public class CircumventionSetupFragment extends Fragment { + + public static CircumventionSetupFragment newInstance() { + return new CircumventionSetupFragment(); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + FCircumventionSetupBinding binding = FCircumventionSetupBinding.inflate(inflater, container, false); + + binding.circumventionRadioGroup.setOnCheckedChangeListener((group, checkedId) -> { + if (binding.rbCircumvention.getId() == checkedId) { + PreferenceHelper.useBridges(getContext(), true); + PreferenceHelper.useSnowflake(getContext(), true); + binding.tvCircumventionDetailDescription.setVisibility(View.VISIBLE); + binding.rbCircumvention.setTypeface(Typeface.DEFAULT, Typeface.BOLD); + binding.rbPlainVpn.setTypeface(Typeface.DEFAULT, Typeface.NORMAL); + return; + } + + PreferenceHelper.useBridges(getContext(), false); + PreferenceHelper.useSnowflake(getContext(), false); + binding.tvCircumventionDetailDescription.setVisibility(View.GONE); + binding.rbPlainVpn.setTypeface(Typeface.DEFAULT, Typeface.BOLD); + binding.rbCircumvention.setTypeface(Typeface.DEFAULT, Typeface.NORMAL); + }); + binding.circumventionRadioGroup.check(binding.rbPlainVpn.getId()); + return binding.getRoot(); + } +} \ No newline at end of file diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java new file mode 100644 index 00000000..ceed2c3c --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java @@ -0,0 +1,163 @@ +package se.leap.bitmaskclient.providersetup.fragments; + +import static android.view.View.GONE; +import static android.view.View.VISIBLE; +import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE; +import static se.leap.bitmaskclient.base.utils.ViewHelper.animateContainerVisibility; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.SET_UP_PROVIDER; +import static se.leap.bitmaskclient.tor.TorStatusObservable.getBootstrapProgress; +import static se.leap.bitmaskclient.tor.TorStatusObservable.getLastLogs; +import static se.leap.bitmaskclient.tor.TorStatusObservable.getLastSnowflakeLog; +import static se.leap.bitmaskclient.tor.TorStatusObservable.getLastTorLog; + +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.viewpager2.widget.ViewPager2; + +import java.util.List; +import java.util.Observable; +import java.util.Observer; + +import se.leap.bitmaskclient.base.utils.PreferenceHelper; +import se.leap.bitmaskclient.databinding.FConfigureProviderBinding; +import se.leap.bitmaskclient.providersetup.ProviderAPICommand; +import se.leap.bitmaskclient.providersetup.TorLogAdapter; +import se.leap.bitmaskclient.providersetup.activities.SetupInterface; +import se.leap.bitmaskclient.tor.TorStatusObservable; + +public class ConfigureProviderFragment extends Fragment implements Observer { + + public static ConfigureProviderFragment newInstance(int position) { + return new ConfigureProviderFragment(position); + } + + FConfigureProviderBinding binding; + private SetupInterface setupInterface; + private boolean isExpanded = false; + private final int position; + private ViewPager2.OnPageChangeCallback viewPagerCallback; + private TorLogAdapter torLogAdapter; + + + public ConfigureProviderFragment(int position) { + this.position = position; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + binding = FConfigureProviderBinding.inflate(inflater, container, false); + binding.detailContainer.setVisibility(PreferenceHelper.getUseSnowflake(getContext()) ? VISIBLE : GONE); + binding.detailHeaderContainer.setOnClickListener(v -> { + binding.ivExpand.animate().setDuration(250).rotation(isExpanded ? 0 : 270); + showConnectionDetails(); + animateContainerVisibility(binding.expandableDetailContainer, isExpanded); + isExpanded = !isExpanded; + }); + + binding.ivExpand.animate().setDuration(0).rotation(270); + return binding.getRoot(); + } + + + + @Override + public void onDestroyView() { + super.onDestroyView(); + binding = null; + } + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + setupInterface = (SetupInterface) getActivity(); + viewPagerCallback = new ViewPager2.OnPageChangeCallback() { + @Override + public void onPageSelected(int position) { + super.onPageSelected(position); + if (position == ConfigureProviderFragment.this.position) { + binding.detailContainer.setVisibility(PreferenceHelper.getUseSnowflake(getContext()) ? VISIBLE : GONE); + setupInterface.setNavigationButtonHidden(true); + ProviderAPICommand.execute(context, SET_UP_PROVIDER, setupInterface.getSelectedProvider()); + } + } + }; + setupInterface.registerOnPageChangeCallback(viewPagerCallback); + TorStatusObservable.getInstance().addObserver(this); + } + + @Override + public void onDetach() { + super.onDetach(); + TorStatusObservable.getInstance().deleteObserver(this); + setupInterface.removeOnPageChangeCallback(viewPagerCallback); + setupInterface = null; + } + + protected void showConnectionDetails() { + LinearLayoutManager layoutManager = new LinearLayoutManager(this.getContext()); + binding.connectionDetailLogs.setLayoutManager(layoutManager); + torLogAdapter = new TorLogAdapter(getLastLogs()); + binding.connectionDetailLogs.setAdapter(torLogAdapter); + + binding.connectionDetailLogs.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { + super.onScrollStateChanged(recyclerView, newState); + if (newState != SCROLL_STATE_IDLE) { + torLogAdapter.postponeUpdate = true; + } else if (newState == SCROLL_STATE_IDLE && getFirstVisibleItemPosion() == 0) { + torLogAdapter.postponeUpdate = false; + } + } + }); + + binding.snowflakeState.setText(getLastSnowflakeLog()); + binding.torState.setText(getLastTorLog()); + } + + private int getFirstVisibleItemPosion() { + return ((LinearLayoutManager) binding.connectionDetailLogs.getLayoutManager()).findFirstVisibleItemPosition(); + } + + @Override + public void update(Observable o, Object arg) { + if (o instanceof TorStatusObservable) { + Activity activity = getActivity(); + if (activity == null) { + return; + } + activity.runOnUiThread(() -> { + if (TorStatusObservable.getStatus() != TorStatusObservable.TorStatus.OFF) { + if (binding.connectionDetailContainer.getVisibility() == GONE) { + showConnectionDetails(); + } else { + setLogs(getLastTorLog(), getLastSnowflakeLog(), getLastLogs()); + } + } + binding.progressSpinner.update(getBootstrapProgress()); + }); + } + } + + protected void setLogs(String torLog, String snowflakeLog, List lastLogs) { + torLogAdapter.updateData(lastLogs); + binding.torState.setText(torLog); + binding.snowflakeState.setText(snowflakeLog); + } +} \ No newline at end of file diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java new file mode 100644 index 00000000..45ba73dc --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java @@ -0,0 +1,138 @@ +package se.leap.bitmaskclient.providersetup.fragments; + +import static se.leap.bitmaskclient.providersetup.fragments.viewmodel.ProviderSelectionViewModel.ADD_PROVIDER; + +import android.content.Context; +import android.graphics.Typeface; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.RadioButton; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; + +import java.util.ArrayList; + +import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.base.models.Provider; +import se.leap.bitmaskclient.databinding.FProviderSelectionBinding; +import se.leap.bitmaskclient.providersetup.activities.SetupInterface; +import se.leap.bitmaskclient.providersetup.fragments.viewmodel.ProviderSelectionViewModel; +import se.leap.bitmaskclient.providersetup.fragments.viewmodel.ProviderSelectionViewModelFactory; + +public class ProviderSelectionFragment extends Fragment { + + private ProviderSelectionViewModel viewModel; + private ArrayList radioButtons; + private SetupInterface setupCallback; + + public static ProviderSelectionFragment newInstance() { + return new ProviderSelectionFragment(); + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + viewModel = new ViewModelProvider(this, + new ProviderSelectionViewModelFactory( + getContext().getApplicationContext().getAssets(), + getContext().getExternalFilesDir(null))). + get(ProviderSelectionViewModel.class); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + FProviderSelectionBinding binding = FProviderSelectionBinding.inflate(inflater, container, false); + + radioButtons = new ArrayList<>(); + for (int i = 0; i < viewModel.size(); i++) { + Provider provider = viewModel.getProvider(i); + RadioButton radioButton = new RadioButton(binding.getRoot().getContext()); + radioButton.setText(provider.getDomain()); + radioButton.setId(i); + binding.providerRadioGroup.addView(radioButton); + radioButtons.add(radioButton); + } + RadioButton radioButton = new RadioButton(binding.getRoot().getContext()); + radioButton.setText(getText(R.string.add_provider)); + radioButton.setId(ADD_PROVIDER); + binding.providerRadioGroup.addView(radioButton); + radioButtons.add(radioButton); + + binding.editCustomProvider.setVisibility(viewModel.getEditProviderVisibility()); + binding.providerRadioGroup.setOnCheckedChangeListener((group, checkedId) -> { + viewModel.setSelected(checkedId); + for (RadioButton rb : radioButtons) { + rb.setTypeface(Typeface.DEFAULT, rb.getId() == checkedId ? Typeface.BOLD : Typeface.NORMAL); + } + binding.providerDescription.setText(viewModel.getProviderDescription(getContext())); + binding.editCustomProvider.setVisibility(viewModel.getEditProviderVisibility()); + if (setupCallback != null) { + setupCallback.onSetupStepValidationChanged(viewModel.isValidConfig()); + if (checkedId != ADD_PROVIDER) { + setupCallback.onProviderSelected(viewModel.getProvider(checkedId)); + } else if (viewModel.isValidConfig()) { + setupCallback.onProviderSelected(new Provider(binding.editCustomProvider.getText().toString())); + } + } + }); + binding.providerRadioGroup.check(viewModel.getSelected()); + + binding.editCustomProvider.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + viewModel.setCustomUrl(s.toString()); + if (setupCallback == null) return; + setupCallback.onSetupStepValidationChanged(viewModel.isValidConfig()); + if (viewModel.isValidConfig()) { + setupCallback.onProviderSelected(new Provider(s.toString())); + } + } + + @Override + public void afterTextChanged(Editable s) {} + }); + return binding.getRoot(); + } + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + if (getActivity() instanceof SetupInterface) { + setupCallback = (SetupInterface) getActivity(); + } + } + + @Override + public void onDetach() { + super.onDetach(); + setupCallback = null; + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + radioButtons = null; + } + + @Override + public void onPause() { + super.onPause(); + } + + @Override + public void onResume() { + super.onResume(); + setupCallback.onSetupStepValidationChanged(viewModel.isValidConfig()); + } +} \ No newline at end of file diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModel.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModel.java new file mode 100644 index 00000000..e3880181 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModel.java @@ -0,0 +1,81 @@ +package se.leap.bitmaskclient.providersetup.fragments.viewmodel; + +import android.content.Context; +import android.content.res.AssetManager; +import android.util.Patterns; +import android.view.View; +import android.webkit.URLUtil; + +import androidx.lifecycle.ViewModel; + +import java.io.File; +import java.util.List; + +import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.base.models.Provider; +import se.leap.bitmaskclient.providersetup.ProviderManager; + +public class ProviderSelectionViewModel extends ViewModel { + private final ProviderManager providerManager; + public static int ADD_PROVIDER = 100100100; + + private int selected = 0; + private String customUrl; + + public ProviderSelectionViewModel(AssetManager assetManager, File externalFilesDir) { + providerManager = ProviderManager.getInstance(assetManager, externalFilesDir); + providerManager.setAddDummyEntry(false); + } + + public int size() { + return providerManager.size(); + } + + public List providers() { + return providerManager.providers(); + } + + public Provider getProvider(int pos) { + return providerManager.get(pos); + } + + public void setSelected(int checkedId) { + selected = checkedId; + } + + public int getSelected() { + return selected; + } + + public boolean isValidConfig() { + if (selected == ADD_PROVIDER) { + return URLUtil.isValidUrl(customUrl) && Patterns.WEB_URL.matcher(customUrl).matches(); + } + return true; + } + + public CharSequence getProviderDescription(Context context) { + if (selected == ADD_PROVIDER) { + return context.getText(R.string.add_provider_description); + } + Provider provider = getProvider(selected); + if ("riseup.net".equals(provider.getDomain())) { + return context.getText(R.string.provider_description_riseup); + } + if ("calyx.net".equals(provider.getDomain())) { + return context.getText(R.string.provider_description_calyx); + } + return provider.getDescription(); + } + + public int getEditProviderVisibility() { + if (selected == ADD_PROVIDER) { + return View.VISIBLE; + } + return View.GONE; + } + + public void setCustomUrl(String url) { + customUrl = url; + } +} \ No newline at end of file diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModelFactory.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModelFactory.java new file mode 100644 index 00000000..a21e4924 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModelFactory.java @@ -0,0 +1,28 @@ +package se.leap.bitmaskclient.providersetup.fragments.viewmodel; + +import android.content.res.AssetManager; + +import androidx.annotation.NonNull; +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; + +import java.io.File; + +public class ProviderSelectionViewModelFactory implements ViewModelProvider.Factory { + private final AssetManager assetManager; + private final File externalFilesDir; + + public ProviderSelectionViewModelFactory(AssetManager assetManager, File externalFilesDir) { + this.assetManager = assetManager; + this.externalFilesDir = externalFilesDir; + } + + @NonNull + @Override + public T create(@NonNull Class modelClass) { + if (modelClass.isAssignableFrom(ProviderSelectionViewModel.class)) { + return (T) new ProviderSelectionViewModel(assetManager, externalFilesDir); + } + throw new IllegalArgumentException("Unknown ViewModel class"); + } +} \ No newline at end of file -- cgit v1.2.3 From 4d9018316fa5a9af714de44224a440e0fa78be57 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Mon, 31 Jul 2023 17:50:04 +0200 Subject: add cancel button, call provider stetup command in ConfigureProviderFragment and listen to the result, implementing happy path --- .../fragments/ConfigureProviderFragment.java | 88 ++++++++++++++++------ .../fragments/ProviderSelectionFragment.java | 42 +++++++++-- 2 files changed, 102 insertions(+), 28 deletions(-) (limited to 'app/src/main/java/se/leap/bitmaskclient/providersetup/fragments') diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java index ceed2c3c..42d516a0 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java @@ -1,9 +1,14 @@ package se.leap.bitmaskclient.providersetup.fragments; +import static android.app.Activity.RESULT_CANCELED; import static android.view.View.GONE; import static android.view.View.VISIBLE; import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE; +import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_CODE; +import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY; +import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY; import static se.leap.bitmaskclient.base.utils.ViewHelper.animateContainerVisibility; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.PROVIDER_OK; import static se.leap.bitmaskclient.providersetup.ProviderAPI.SET_UP_PROVIDER; import static se.leap.bitmaskclient.tor.TorStatusObservable.getBootstrapProgress; import static se.leap.bitmaskclient.tor.TorStatusObservable.getLastLogs; @@ -12,6 +17,7 @@ import static se.leap.bitmaskclient.tor.TorStatusObservable.getLastTorLog; import android.app.Activity; import android.content.Context; +import android.content.Intent; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -28,21 +34,27 @@ import java.util.List; import java.util.Observable; import java.util.Observer; +import se.leap.bitmaskclient.base.models.Provider; import se.leap.bitmaskclient.base.utils.PreferenceHelper; import se.leap.bitmaskclient.databinding.FConfigureProviderBinding; +import se.leap.bitmaskclient.eip.EipSetupListener; +import se.leap.bitmaskclient.eip.EipSetupObserver; import se.leap.bitmaskclient.providersetup.ProviderAPICommand; import se.leap.bitmaskclient.providersetup.TorLogAdapter; -import se.leap.bitmaskclient.providersetup.activities.SetupInterface; +import se.leap.bitmaskclient.providersetup.activities.CancelCallback; +import se.leap.bitmaskclient.providersetup.activities.SetupActivityCallback; import se.leap.bitmaskclient.tor.TorStatusObservable; -public class ConfigureProviderFragment extends Fragment implements Observer { +public class ConfigureProviderFragment extends Fragment implements Observer, CancelCallback, EipSetupListener { + + private static final String TAG = ConfigureProviderFragment.class.getSimpleName(); public static ConfigureProviderFragment newInstance(int position) { return new ConfigureProviderFragment(position); } FConfigureProviderBinding binding; - private SetupInterface setupInterface; + private SetupActivityCallback setupActivityCallback; private boolean isExpanded = false; private final int position; private ViewPager2.OnPageChangeCallback viewPagerCallback; @@ -56,6 +68,7 @@ public class ConfigureProviderFragment extends Fragment implements Observer { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); + torLogAdapter = new TorLogAdapter(getLastLogs()); } @Override @@ -64,13 +77,16 @@ public class ConfigureProviderFragment extends Fragment implements Observer { binding = FConfigureProviderBinding.inflate(inflater, container, false); binding.detailContainer.setVisibility(PreferenceHelper.getUseSnowflake(getContext()) ? VISIBLE : GONE); binding.detailHeaderContainer.setOnClickListener(v -> { - binding.ivExpand.animate().setDuration(250).rotation(isExpanded ? 0 : 270); + binding.ivExpand.animate().setDuration(250).rotation(isExpanded ? -90 : 0); showConnectionDetails(); animateContainerVisibility(binding.expandableDetailContainer, isExpanded); isExpanded = !isExpanded; }); binding.ivExpand.animate().setDuration(0).rotation(270); + LinearLayoutManager layoutManager = new LinearLayoutManager(this.getContext()); + binding.connectionDetailLogs.setLayoutManager(layoutManager); + binding.connectionDetailLogs.setAdapter(torLogAdapter); return binding.getRoot(); } @@ -85,35 +101,40 @@ public class ConfigureProviderFragment extends Fragment implements Observer { @Override public void onAttach(@NonNull Context context) { super.onAttach(context); - setupInterface = (SetupInterface) getActivity(); - viewPagerCallback = new ViewPager2.OnPageChangeCallback() { - @Override - public void onPageSelected(int position) { - super.onPageSelected(position); - if (position == ConfigureProviderFragment.this.position) { - binding.detailContainer.setVisibility(PreferenceHelper.getUseSnowflake(getContext()) ? VISIBLE : GONE); - setupInterface.setNavigationButtonHidden(true); - ProviderAPICommand.execute(context, SET_UP_PROVIDER, setupInterface.getSelectedProvider()); + if (getActivity() instanceof SetupActivityCallback) { + setupActivityCallback = (SetupActivityCallback) getActivity(); + viewPagerCallback = new ViewPager2.OnPageChangeCallback() { + @Override + public void onPageSelected(int position) { + super.onPageSelected(position); + if (position == ConfigureProviderFragment.this.position) { + binding.detailContainer.setVisibility(PreferenceHelper.getUseSnowflake(getContext()) ? VISIBLE : GONE); + setupActivityCallback.setNavigationButtonHidden(true); + setupActivityCallback.setCancelButtonHidden(false); + ProviderAPICommand.execute(context, SET_UP_PROVIDER, setupActivityCallback.getSelectedProvider()); + } } - } - }; - setupInterface.registerOnPageChangeCallback(viewPagerCallback); + }; + setupActivityCallback.registerOnPageChangeCallback(viewPagerCallback); + setupActivityCallback.registerCancelCallback(this); + } TorStatusObservable.getInstance().addObserver(this); + EipSetupObserver.addListener(this); } @Override public void onDetach() { super.onDetach(); TorStatusObservable.getInstance().deleteObserver(this); - setupInterface.removeOnPageChangeCallback(viewPagerCallback); - setupInterface = null; + if (setupActivityCallback != null) { + setupActivityCallback.removeOnPageChangeCallback(viewPagerCallback); + setupActivityCallback.removeCancelCallback(this); + setupActivityCallback = null; + } + EipSetupObserver.removeListener(this); } protected void showConnectionDetails() { - LinearLayoutManager layoutManager = new LinearLayoutManager(this.getContext()); - binding.connectionDetailLogs.setLayoutManager(layoutManager); - torLogAdapter = new TorLogAdapter(getLastLogs()); - binding.connectionDetailLogs.setAdapter(torLogAdapter); binding.connectionDetailLogs.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override @@ -150,6 +171,7 @@ public class ConfigureProviderFragment extends Fragment implements Observer { setLogs(getLastTorLog(), getLastSnowflakeLog(), getLastLogs()); } } + binding.tvProgressStatus.setText(TorStatusObservable.getStringForCurrentStatus(activity)); binding.progressSpinner.update(getBootstrapProgress()); }); } @@ -160,4 +182,26 @@ public class ConfigureProviderFragment extends Fragment implements Observer { binding.torState.setText(torLog); binding.snowflakeState.setText(snowflakeLog); } + + @Override + public void onCanceled() { + + } + + @Override + public void handleEipEvent(Intent intent) {} + + @Override + public void handleProviderApiEvent(Intent intent) { + int resultCode = intent.getIntExtra(BROADCAST_RESULT_CODE, RESULT_CANCELED); + Bundle resultData = intent.getParcelableExtra(BROADCAST_RESULT_KEY); + if (resultData == null) { + resultData = Bundle.EMPTY; + } + if (resultCode == PROVIDER_OK) { + Provider provider = resultData.getParcelable(PROVIDER_KEY); + setupActivityCallback.onProviderSelected(provider); + setupActivityCallback.onConfigurationSuccess(); + } + } } \ No newline at end of file diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java index 45ba73dc..6ebb149c 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java @@ -16,21 +16,39 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; +import androidx.viewpager2.widget.ViewPager2; import java.util.ArrayList; import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.base.models.Provider; +import se.leap.bitmaskclient.base.models.ProviderObservable; import se.leap.bitmaskclient.databinding.FProviderSelectionBinding; -import se.leap.bitmaskclient.providersetup.activities.SetupInterface; +import se.leap.bitmaskclient.providersetup.activities.CancelCallback; +import se.leap.bitmaskclient.providersetup.activities.SetupActivityCallback; import se.leap.bitmaskclient.providersetup.fragments.viewmodel.ProviderSelectionViewModel; import se.leap.bitmaskclient.providersetup.fragments.viewmodel.ProviderSelectionViewModelFactory; -public class ProviderSelectionFragment extends Fragment { +public class ProviderSelectionFragment extends Fragment implements CancelCallback { private ProviderSelectionViewModel viewModel; private ArrayList radioButtons; - private SetupInterface setupCallback; + private SetupActivityCallback setupCallback; + + private FProviderSelectionBinding binding; + + private final ViewPager2.OnPageChangeCallback onPageChangeCallback = new ViewPager2.OnPageChangeCallback() { + @Override + public void onPageSelected(int position) { + super.onPageSelected(position); + if (position == 0) { + if (setupCallback != null) { + setupCallback.setCancelButtonHidden(!ProviderObservable.getInstance().getCurrentProvider().isConfigured()); + setupCallback.setNavigationButtonHidden(false); + } + } + } + }; public static ProviderSelectionFragment newInstance() { return new ProviderSelectionFragment(); @@ -49,7 +67,7 @@ public class ProviderSelectionFragment extends Fragment { @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - FProviderSelectionBinding binding = FProviderSelectionBinding.inflate(inflater, container, false); + binding = FProviderSelectionBinding.inflate(inflater, container, false); radioButtons = new ArrayList<>(); for (int i = 0; i < viewModel.size(); i++) { @@ -108,20 +126,27 @@ public class ProviderSelectionFragment extends Fragment { @Override public void onAttach(@NonNull Context context) { super.onAttach(context); - if (getActivity() instanceof SetupInterface) { - setupCallback = (SetupInterface) getActivity(); + if (getActivity() instanceof SetupActivityCallback) { + setupCallback = (SetupActivityCallback) getActivity(); + setupCallback.registerOnPageChangeCallback(onPageChangeCallback); + setupCallback.registerCancelCallback(this); } } @Override public void onDetach() { super.onDetach(); + if (setupCallback != null) { + setupCallback.removeOnPageChangeCallback(onPageChangeCallback); + setupCallback.removeCancelCallback(this); + } setupCallback = null; } @Override public void onDestroyView() { super.onDestroyView(); + binding = null; radioButtons = null; } @@ -135,4 +160,9 @@ public class ProviderSelectionFragment extends Fragment { super.onResume(); setupCallback.onSetupStepValidationChanged(viewModel.isValidConfig()); } + + @Override + public void onCanceled() { + binding.providerRadioGroup.check(0); + } } \ No newline at end of file -- cgit v1.2.3 From 0fa7ae499185fefa732a7bc02a8e22ea5da92ec7 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Wed, 2 Aug 2023 12:34:45 +0200 Subject: * Implenting permissionn fragments * refactoring fragments, use of a base fragment to deduplicate code * improve SetupViewPagerAdapter by implementing a factory that hands out the reuired fragments in the correct order * very basic setup success fragment ("You're all set!") --- .../providersetup/fragments/BaseSetupFragment.java | 63 +++++++++++++++ .../fragments/CircumventionSetupFragment.java | 18 ++++- .../fragments/ConfigureProviderFragment.java | 68 ++++++++-------- .../fragments/EmptyPermissionSetupFragment.java | 92 ++++++++++++++++++++++ .../fragments/NotificationSetupFragment.java | 37 +++++++++ .../fragments/ProviderSelectionFragment.java | 63 ++++++--------- .../fragments/SetupFragmentFactory.java | 61 ++++++++++++++ .../fragments/SetupSuccessFragment.java | 40 ++++++++++ .../fragments/VpnPermissionSetupFragment.java | 44 +++++++++++ 9 files changed, 408 insertions(+), 78 deletions(-) create mode 100644 app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/BaseSetupFragment.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/EmptyPermissionSetupFragment.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/NotificationSetupFragment.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupFragmentFactory.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupSuccessFragment.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/VpnPermissionSetupFragment.java (limited to 'app/src/main/java/se/leap/bitmaskclient/providersetup/fragments') diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/BaseSetupFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/BaseSetupFragment.java new file mode 100644 index 00000000..8012fe76 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/BaseSetupFragment.java @@ -0,0 +1,63 @@ +package se.leap.bitmaskclient.providersetup.fragments; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.viewpager2.widget.ViewPager2; + +import se.leap.bitmaskclient.providersetup.activities.SetupActivityCallback; + +public class BaseSetupFragment extends Fragment { + + SetupActivityCallback setupActivityCallback; + private boolean callFragmentSelected = false; + private final ViewPager2.OnPageChangeCallback viewPagerCallback = new ViewPager2.OnPageChangeCallback() { + @Override + public void onPageSelected(int position) { + super.onPageSelected(position); + if (position == BaseSetupFragment.this.position) { + handleCallFragmentSelected(); + } else { + callFragmentSelected = false; + } + } + }; + private final int position; + + public BaseSetupFragment(int position) { + this.position = position; + } + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + if (getActivity() instanceof SetupActivityCallback) { + setupActivityCallback = (SetupActivityCallback) getActivity(); + setupActivityCallback.registerOnPageChangeCallback(viewPagerCallback); + if (setupActivityCallback.getCurrentPosition() == position) { + handleCallFragmentSelected(); + } + } else { + throw new IllegalStateException("These setup fragments are closely coupled to SetupActivityCallback interface. Activities instantiating them are required to implement the interface"); + } + } + + private void handleCallFragmentSelected() { + if (!callFragmentSelected) { + callFragmentSelected = true; + onFragmentSelected(); + } + } + + @Override + public void onDetach() { + super.onDetach(); + setupActivityCallback.removeOnPageChangeCallback(viewPagerCallback); + setupActivityCallback = null; + } + + public void onFragmentSelected() { + + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java index 606de943..1bc12f57 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java @@ -8,15 +8,18 @@ import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; import se.leap.bitmaskclient.base.utils.PreferenceHelper; import se.leap.bitmaskclient.databinding.FCircumventionSetupBinding; -public class CircumventionSetupFragment extends Fragment { +public class CircumventionSetupFragment extends BaseSetupFragment { - public static CircumventionSetupFragment newInstance() { - return new CircumventionSetupFragment(); + private CircumventionSetupFragment(int position) { + super(position); + } + + public static CircumventionSetupFragment newInstance(int position) { + return new CircumventionSetupFragment(position); } @Override @@ -43,4 +46,11 @@ public class CircumventionSetupFragment extends Fragment { binding.circumventionRadioGroup.check(binding.rbPlainVpn.getId()); return binding.getRoot(); } + + @Override + public void onFragmentSelected() { + super.onFragmentSelected(); + setupActivityCallback.setCancelButtonHidden(false); + setupActivityCallback.setNavigationButtonHidden(false); + } } \ No newline at end of file diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java index 42d516a0..41e7cb8e 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java @@ -8,6 +8,8 @@ import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_CODE; import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY; import static se.leap.bitmaskclient.base.utils.ViewHelper.animateContainerVisibility; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.CORRECTLY_DOWNLOADED_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.DOWNLOAD_VPN_CERTIFICATE; import static se.leap.bitmaskclient.providersetup.ProviderAPI.PROVIDER_OK; import static se.leap.bitmaskclient.providersetup.ProviderAPI.SET_UP_PROVIDER; import static se.leap.bitmaskclient.tor.TorStatusObservable.getBootstrapProgress; @@ -25,10 +27,8 @@ import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import androidx.viewpager2.widget.ViewPager2; import java.util.List; import java.util.Observable; @@ -42,10 +42,9 @@ import se.leap.bitmaskclient.eip.EipSetupObserver; import se.leap.bitmaskclient.providersetup.ProviderAPICommand; import se.leap.bitmaskclient.providersetup.TorLogAdapter; import se.leap.bitmaskclient.providersetup.activities.CancelCallback; -import se.leap.bitmaskclient.providersetup.activities.SetupActivityCallback; import se.leap.bitmaskclient.tor.TorStatusObservable; -public class ConfigureProviderFragment extends Fragment implements Observer, CancelCallback, EipSetupListener { +public class ConfigureProviderFragment extends BaseSetupFragment implements Observer, CancelCallback, EipSetupListener { private static final String TAG = ConfigureProviderFragment.class.getSimpleName(); @@ -54,15 +53,13 @@ public class ConfigureProviderFragment extends Fragment implements Observer, Can } FConfigureProviderBinding binding; - private SetupActivityCallback setupActivityCallback; private boolean isExpanded = false; - private final int position; - private ViewPager2.OnPageChangeCallback viewPagerCallback; + private boolean ignoreProviderAPIUpdates = false; private TorLogAdapter torLogAdapter; - public ConfigureProviderFragment(int position) { - this.position = position; + private ConfigureProviderFragment(int position) { + super(position); } @Override @@ -98,40 +95,30 @@ public class ConfigureProviderFragment extends Fragment implements Observer, Can binding = null; } + @Override + public void onFragmentSelected() { + super.onFragmentSelected(); + ignoreProviderAPIUpdates = false; + binding.detailContainer.setVisibility(PreferenceHelper.getUseSnowflake(getContext()) ? VISIBLE : GONE); + setupActivityCallback.setNavigationButtonHidden(true); + setupActivityCallback.setCancelButtonHidden(false); + ProviderAPICommand.execute(getContext(), SET_UP_PROVIDER, setupActivityCallback.getSelectedProvider()); + } + @Override public void onAttach(@NonNull Context context) { super.onAttach(context); - if (getActivity() instanceof SetupActivityCallback) { - setupActivityCallback = (SetupActivityCallback) getActivity(); - viewPagerCallback = new ViewPager2.OnPageChangeCallback() { - @Override - public void onPageSelected(int position) { - super.onPageSelected(position); - if (position == ConfigureProviderFragment.this.position) { - binding.detailContainer.setVisibility(PreferenceHelper.getUseSnowflake(getContext()) ? VISIBLE : GONE); - setupActivityCallback.setNavigationButtonHidden(true); - setupActivityCallback.setCancelButtonHidden(false); - ProviderAPICommand.execute(context, SET_UP_PROVIDER, setupActivityCallback.getSelectedProvider()); - } - } - }; - setupActivityCallback.registerOnPageChangeCallback(viewPagerCallback); - setupActivityCallback.registerCancelCallback(this); - } + setupActivityCallback.registerCancelCallback(this); TorStatusObservable.getInstance().addObserver(this); EipSetupObserver.addListener(this); } @Override public void onDetach() { - super.onDetach(); + setupActivityCallback.removeCancelCallback(this); TorStatusObservable.getInstance().deleteObserver(this); - if (setupActivityCallback != null) { - setupActivityCallback.removeOnPageChangeCallback(viewPagerCallback); - setupActivityCallback.removeCancelCallback(this); - setupActivityCallback = null; - } EipSetupObserver.removeListener(this); + super.onDetach(); } protected void showConnectionDetails() { @@ -185,7 +172,7 @@ public class ConfigureProviderFragment extends Fragment implements Observer, Can @Override public void onCanceled() { - + ignoreProviderAPIUpdates = true; } @Override @@ -198,10 +185,23 @@ public class ConfigureProviderFragment extends Fragment implements Observer, Can if (resultData == null) { resultData = Bundle.EMPTY; } + Provider provider = resultData.getParcelable(PROVIDER_KEY); + if (ignoreProviderAPIUpdates || + provider == null || + !setupActivityCallback.getSelectedProvider().getDomain().equals(provider.getDomain())) { + return; + } if (resultCode == PROVIDER_OK) { - Provider provider = resultData.getParcelable(PROVIDER_KEY); + setupActivityCallback.onProviderSelected(provider); + if (provider.allowsAnonymous()) { + ProviderAPICommand.execute(this.getContext(), DOWNLOAD_VPN_CERTIFICATE, provider); + } else { + // TODO: implement error message that this client only supports anonymous usage + } + } else if (resultCode == CORRECTLY_DOWNLOADED_VPN_CERTIFICATE) { setupActivityCallback.onProviderSelected(provider); setupActivityCallback.onConfigurationSuccess(); } } + } \ No newline at end of file diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/EmptyPermissionSetupFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/EmptyPermissionSetupFragment.java new file mode 100644 index 00000000..18751ee5 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/EmptyPermissionSetupFragment.java @@ -0,0 +1,92 @@ +package se.leap.bitmaskclient.providersetup.fragments; + +import android.Manifest; +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toast; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import se.leap.bitmaskclient.databinding.FVpnPermissionSetupBinding; + +public class EmptyPermissionSetupFragment extends BaseSetupFragment { + + private String notificationPermissionAction = null; + private Intent vpnPermissionIntent = null; + + private final ActivityResultLauncher requestNotificationPermissionLauncher = + registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { + if (isGranted) { + if (setupActivityCallback != null) { + setupActivityCallback.onConfigurationSuccess(); + } + } else { + Toast.makeText(getContext(), "Permission request failed :(", Toast.LENGTH_LONG).show(); + setupActivityCallback.setNavigationButtonHidden(false); + // TODO: implement sth. useful + } + }); + + private final ActivityResultLauncher requestVpnPermissionLauncher = + registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == Activity.RESULT_OK) { + if (setupActivityCallback != null) { + setupActivityCallback.onConfigurationSuccess(); + } + } else { + Toast.makeText(getContext(), "Permission request failed :(", Toast.LENGTH_LONG).show(); + setupActivityCallback.setNavigationButtonHidden(false); + // TODO: implement sth. useful + } + } + ); + + private EmptyPermissionSetupFragment(int position, String permissionAction) { + super(position); + this.notificationPermissionAction = permissionAction; + } + + private EmptyPermissionSetupFragment(int position, Intent vpnPermissionIntent) { + super(position); + this.vpnPermissionIntent = vpnPermissionIntent; + } + + public static EmptyPermissionSetupFragment newInstance(int position, Intent vpnPermissionIntent) { + return new EmptyPermissionSetupFragment(position, vpnPermissionIntent); + } + + public static EmptyPermissionSetupFragment newInstance(int position, String notificationPermissionAction) { + return new EmptyPermissionSetupFragment(position, notificationPermissionAction); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + FVpnPermissionSetupBinding binding = FVpnPermissionSetupBinding.inflate(inflater, container, false); + return binding.getRoot(); + } + + @Override + public void onFragmentSelected() { + super.onFragmentSelected(); + if (notificationPermissionAction != null) { + requestNotificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS); + } else if (vpnPermissionIntent != null) { + requestVpnPermissionLauncher.launch(vpnPermissionIntent); + } + + setupActivityCallback.setNavigationButtonHidden(true); + setupActivityCallback.setCancelButtonHidden(true); + } + + +} \ No newline at end of file diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/NotificationSetupFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/NotificationSetupFragment.java new file mode 100644 index 00000000..72374bb9 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/NotificationSetupFragment.java @@ -0,0 +1,37 @@ +package se.leap.bitmaskclient.providersetup.fragments; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import se.leap.bitmaskclient.databinding.FNotificationSetupBinding; + +public class NotificationSetupFragment extends BaseSetupFragment { + + private NotificationSetupFragment(int position) { + super(position); + } + + public static NotificationSetupFragment newInstance(int position) { + return new NotificationSetupFragment(position); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + FNotificationSetupBinding binding = FNotificationSetupBinding.inflate(inflater, container, false); + return binding.getRoot(); + } + + @Override + public void onFragmentSelected() { + super.onFragmentSelected(); + setupActivityCallback.setNavigationButtonHidden(false); + setupActivityCallback.setCancelButtonHidden(true); + } + +} \ No newline at end of file diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java index 6ebb149c..7f80a99d 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java @@ -14,9 +14,7 @@ import android.widget.RadioButton; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; -import androidx.viewpager2.widget.ViewPager2; import java.util.ArrayList; @@ -25,33 +23,22 @@ import se.leap.bitmaskclient.base.models.Provider; import se.leap.bitmaskclient.base.models.ProviderObservable; import se.leap.bitmaskclient.databinding.FProviderSelectionBinding; import se.leap.bitmaskclient.providersetup.activities.CancelCallback; -import se.leap.bitmaskclient.providersetup.activities.SetupActivityCallback; import se.leap.bitmaskclient.providersetup.fragments.viewmodel.ProviderSelectionViewModel; import se.leap.bitmaskclient.providersetup.fragments.viewmodel.ProviderSelectionViewModelFactory; -public class ProviderSelectionFragment extends Fragment implements CancelCallback { +public class ProviderSelectionFragment extends BaseSetupFragment implements CancelCallback { private ProviderSelectionViewModel viewModel; private ArrayList radioButtons; - private SetupActivityCallback setupCallback; private FProviderSelectionBinding binding; - private final ViewPager2.OnPageChangeCallback onPageChangeCallback = new ViewPager2.OnPageChangeCallback() { - @Override - public void onPageSelected(int position) { - super.onPageSelected(position); - if (position == 0) { - if (setupCallback != null) { - setupCallback.setCancelButtonHidden(!ProviderObservable.getInstance().getCurrentProvider().isConfigured()); - setupCallback.setNavigationButtonHidden(false); - } - } - } - }; + private ProviderSelectionFragment(int position) { + super(position); + } - public static ProviderSelectionFragment newInstance() { - return new ProviderSelectionFragment(); + public static ProviderSelectionFragment newInstance(int position) { + return new ProviderSelectionFragment(position); } @Override @@ -92,13 +79,11 @@ public class ProviderSelectionFragment extends Fragment implements CancelCallbac } binding.providerDescription.setText(viewModel.getProviderDescription(getContext())); binding.editCustomProvider.setVisibility(viewModel.getEditProviderVisibility()); - if (setupCallback != null) { - setupCallback.onSetupStepValidationChanged(viewModel.isValidConfig()); - if (checkedId != ADD_PROVIDER) { - setupCallback.onProviderSelected(viewModel.getProvider(checkedId)); - } else if (viewModel.isValidConfig()) { - setupCallback.onProviderSelected(new Provider(binding.editCustomProvider.getText().toString())); - } + setupActivityCallback.onSetupStepValidationChanged(viewModel.isValidConfig()); + if (checkedId != ADD_PROVIDER) { + setupActivityCallback.onProviderSelected(viewModel.getProvider(checkedId)); + } else if (viewModel.isValidConfig()) { + setupActivityCallback.onProviderSelected(new Provider(binding.editCustomProvider.getText().toString())); } }); binding.providerRadioGroup.check(viewModel.getSelected()); @@ -110,10 +95,9 @@ public class ProviderSelectionFragment extends Fragment implements CancelCallbac @Override public void onTextChanged(CharSequence s, int start, int before, int count) { viewModel.setCustomUrl(s.toString()); - if (setupCallback == null) return; - setupCallback.onSetupStepValidationChanged(viewModel.isValidConfig()); + setupActivityCallback.onSetupStepValidationChanged(viewModel.isValidConfig()); if (viewModel.isValidConfig()) { - setupCallback.onProviderSelected(new Provider(s.toString())); + setupActivityCallback.onProviderSelected(new Provider(s.toString())); } } @@ -123,24 +107,23 @@ public class ProviderSelectionFragment extends Fragment implements CancelCallbac return binding.getRoot(); } + @Override + public void onFragmentSelected() { + super.onFragmentSelected(); + setupActivityCallback.setCancelButtonHidden(!ProviderObservable.getInstance().getCurrentProvider().isConfigured()); + setupActivityCallback.setNavigationButtonHidden(false); + } + @Override public void onAttach(@NonNull Context context) { super.onAttach(context); - if (getActivity() instanceof SetupActivityCallback) { - setupCallback = (SetupActivityCallback) getActivity(); - setupCallback.registerOnPageChangeCallback(onPageChangeCallback); - setupCallback.registerCancelCallback(this); - } + setupActivityCallback.registerCancelCallback(this); } @Override public void onDetach() { + setupActivityCallback.removeCancelCallback(this); super.onDetach(); - if (setupCallback != null) { - setupCallback.removeOnPageChangeCallback(onPageChangeCallback); - setupCallback.removeCancelCallback(this); - } - setupCallback = null; } @Override @@ -158,7 +141,7 @@ public class ProviderSelectionFragment extends Fragment implements CancelCallbac @Override public void onResume() { super.onResume(); - setupCallback.onSetupStepValidationChanged(viewModel.isValidConfig()); + setupActivityCallback.onSetupStepValidationChanged(viewModel.isValidConfig()); } @Override diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupFragmentFactory.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupFragmentFactory.java new file mode 100644 index 00000000..fd76a841 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupFragmentFactory.java @@ -0,0 +1,61 @@ +package se.leap.bitmaskclient.providersetup.fragments; + +import android.Manifest; +import android.content.Intent; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; + +import java.util.ArrayList; + +public class SetupFragmentFactory { + public static final int PROVIDER_SELECTION_FRAGMENT = 0; + public static final int CIRCUMVENTION_SETUP_FRAGMENT = 1; + public static final int CONFIGURE_PROVIDER_FRAGMENT = 2; + public static final int VPN_PERMISSON_EDUCATIONAL_FRAGMENT = 3; + public static final int VPN_PERMISSON_FRAGMENT = 4; + public static final int NOTIFICATION_PERMISSON_EDUCATIONAL_FRAGMENT = 5; + public static final int NOTIFICATION_PERMISSON_FRAGMENT = 6; + + public static final int SUCCESS_FRAGMENT = 7; + + private final Intent vpnPermissionRequest; + + private final ArrayList fragmentTypes; + + public SetupFragmentFactory(@NonNull ArrayList fragmentTypes, Intent vpnPermissionRequest) { + this.fragmentTypes = fragmentTypes; + this.vpnPermissionRequest = vpnPermissionRequest; + } + + public Fragment createFragment(int position) { + if (position < 0 || position >= fragmentTypes.size()) { + throw new IllegalStateException("Illegal fragment position"); + } + int type = fragmentTypes.get(position); + switch (type) { + case PROVIDER_SELECTION_FRAGMENT: + return ProviderSelectionFragment.newInstance(position); + case CIRCUMVENTION_SETUP_FRAGMENT: + return CircumventionSetupFragment.newInstance(position); + case CONFIGURE_PROVIDER_FRAGMENT: + return ConfigureProviderFragment.newInstance(position); + case NOTIFICATION_PERMISSON_EDUCATIONAL_FRAGMENT: + return NotificationSetupFragment.newInstance(position); + case NOTIFICATION_PERMISSON_FRAGMENT: + return EmptyPermissionSetupFragment.newInstance(position, Manifest.permission.POST_NOTIFICATIONS); + case VPN_PERMISSON_EDUCATIONAL_FRAGMENT: + return VpnPermissionSetupFragment.newInstance(position); + case VPN_PERMISSON_FRAGMENT: + return EmptyPermissionSetupFragment.newInstance(position, vpnPermissionRequest); + case SUCCESS_FRAGMENT: + return SetupSuccessFragment.newInstance(position); + default: + throw new IllegalArgumentException("Unexpected fragment type: " + type); + } + } + + public int getItemCount() { + return fragmentTypes.size(); + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupSuccessFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupSuccessFragment.java new file mode 100644 index 00000000..3e241012 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupSuccessFragment.java @@ -0,0 +1,40 @@ +package se.leap.bitmaskclient.providersetup.fragments; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import se.leap.bitmaskclient.databinding.FSetupSuccessBinding; + +public class SetupSuccessFragment extends BaseSetupFragment { + + + private SetupSuccessFragment(int position) { + super(position); + } + + public static SetupSuccessFragment newInstance(int position) { + return new SetupSuccessFragment(position); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + FSetupSuccessBinding binding = FSetupSuccessBinding.inflate(inflater, container, false); + + binding.mainButton.setOnClickListener(v -> setupActivityCallback.onSetupFinished()); + return binding.getRoot(); + } + + @Override + public void onFragmentSelected() { + super.onFragmentSelected(); + setupActivityCallback.setNavigationButtonHidden(true); + setupActivityCallback.setCancelButtonHidden(true); + } + +} \ No newline at end of file diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/VpnPermissionSetupFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/VpnPermissionSetupFragment.java new file mode 100644 index 00000000..a6af0b36 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/VpnPermissionSetupFragment.java @@ -0,0 +1,44 @@ +package se.leap.bitmaskclient.providersetup.fragments; + +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import se.leap.bitmaskclient.databinding.FVpnPermissionSetupBinding; + +public class VpnPermissionSetupFragment extends BaseSetupFragment { + + + private VpnPermissionSetupFragment(int position) { + super(position); + } + + public static VpnPermissionSetupFragment newInstance(int position) { + return new VpnPermissionSetupFragment(position); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + FVpnPermissionSetupBinding binding = FVpnPermissionSetupBinding.inflate(inflater, container, false); + return binding.getRoot(); + } + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + } + + @Override + public void onFragmentSelected() { + super.onFragmentSelected(); + setupActivityCallback.setNavigationButtonHidden(false); + setupActivityCallback.setCancelButtonHidden(true); + } + +} \ No newline at end of file -- cgit v1.2.3 From 64449816223cb9abb6e75310c03dcc9353a42ee4 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Wed, 2 Aug 2023 22:45:26 +0200 Subject: use single instance of shared prefernces across the app, this reduces the laggyness of the UI noticably --- .../providersetup/fragments/CircumventionSetupFragment.java | 8 ++++---- .../providersetup/fragments/ConfigureProviderFragment.java | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'app/src/main/java/se/leap/bitmaskclient/providersetup/fragments') diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java index 1bc12f57..2709fd0b 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java @@ -29,16 +29,16 @@ public class CircumventionSetupFragment extends BaseSetupFragment { binding.circumventionRadioGroup.setOnCheckedChangeListener((group, checkedId) -> { if (binding.rbCircumvention.getId() == checkedId) { - PreferenceHelper.useBridges(getContext(), true); - PreferenceHelper.useSnowflake(getContext(), true); + PreferenceHelper.useBridges(true); + PreferenceHelper.useSnowflake(true); binding.tvCircumventionDetailDescription.setVisibility(View.VISIBLE); binding.rbCircumvention.setTypeface(Typeface.DEFAULT, Typeface.BOLD); binding.rbPlainVpn.setTypeface(Typeface.DEFAULT, Typeface.NORMAL); return; } - PreferenceHelper.useBridges(getContext(), false); - PreferenceHelper.useSnowflake(getContext(), false); + PreferenceHelper.useBridges(false); + PreferenceHelper.useSnowflake(false); binding.tvCircumventionDetailDescription.setVisibility(View.GONE); binding.rbPlainVpn.setTypeface(Typeface.DEFAULT, Typeface.BOLD); binding.rbCircumvention.setTypeface(Typeface.DEFAULT, Typeface.NORMAL); diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java index 41e7cb8e..26f62042 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java @@ -72,7 +72,7 @@ public class ConfigureProviderFragment extends BaseSetupFragment implements Obse public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { binding = FConfigureProviderBinding.inflate(inflater, container, false); - binding.detailContainer.setVisibility(PreferenceHelper.getUseSnowflake(getContext()) ? VISIBLE : GONE); + binding.detailContainer.setVisibility(PreferenceHelper.getUseSnowflake() ? VISIBLE : GONE); binding.detailHeaderContainer.setOnClickListener(v -> { binding.ivExpand.animate().setDuration(250).rotation(isExpanded ? -90 : 0); showConnectionDetails(); @@ -99,7 +99,7 @@ public class ConfigureProviderFragment extends BaseSetupFragment implements Obse public void onFragmentSelected() { super.onFragmentSelected(); ignoreProviderAPIUpdates = false; - binding.detailContainer.setVisibility(PreferenceHelper.getUseSnowflake(getContext()) ? VISIBLE : GONE); + binding.detailContainer.setVisibility(PreferenceHelper.getUseSnowflake() ? VISIBLE : GONE); setupActivityCallback.setNavigationButtonHidden(true); setupActivityCallback.setCancelButtonHidden(false); ProviderAPICommand.execute(getContext(), SET_UP_PROVIDER, setupActivityCallback.getSelectedProvider()); -- cgit v1.2.3 From 5ceaf7c7a2fc847c52b1764725ceabacd708a15a Mon Sep 17 00:00:00 2001 From: cyBerta Date: Thu, 3 Aug 2023 15:55:24 +0200 Subject: show error dialogs if provider setup fails --- .../fragments/ConfigureProviderFragment.java | 38 ++++++++++++++++------ .../fragments/SetupFragmentFactory.java | 4 +++ 2 files changed, 32 insertions(+), 10 deletions(-) (limited to 'app/src/main/java/se/leap/bitmaskclient/providersetup/fragments') diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java index 26f62042..8475108f 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java @@ -10,8 +10,14 @@ import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY; import static se.leap.bitmaskclient.base.utils.ViewHelper.animateContainerVisibility; import static se.leap.bitmaskclient.providersetup.ProviderAPI.CORRECTLY_DOWNLOADED_VPN_CERTIFICATE; import static se.leap.bitmaskclient.providersetup.ProviderAPI.DOWNLOAD_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORS; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.MISSING_NETWORK_CONNECTION; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.PROVIDER_NOK; import static se.leap.bitmaskclient.providersetup.ProviderAPI.PROVIDER_OK; import static se.leap.bitmaskclient.providersetup.ProviderAPI.SET_UP_PROVIDER; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.TOR_EXCEPTION; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.TOR_TIMEOUT; import static se.leap.bitmaskclient.tor.TorStatusObservable.getBootstrapProgress; import static se.leap.bitmaskclient.tor.TorStatusObservable.getLastLogs; import static se.leap.bitmaskclient.tor.TorStatusObservable.getLastSnowflakeLog; @@ -191,16 +197,28 @@ public class ConfigureProviderFragment extends BaseSetupFragment implements Obse !setupActivityCallback.getSelectedProvider().getDomain().equals(provider.getDomain())) { return; } - if (resultCode == PROVIDER_OK) { - setupActivityCallback.onProviderSelected(provider); - if (provider.allowsAnonymous()) { - ProviderAPICommand.execute(this.getContext(), DOWNLOAD_VPN_CERTIFICATE, provider); - } else { - // TODO: implement error message that this client only supports anonymous usage - } - } else if (resultCode == CORRECTLY_DOWNLOADED_VPN_CERTIFICATE) { - setupActivityCallback.onProviderSelected(provider); - setupActivityCallback.onConfigurationSuccess(); + + switch (resultCode) { + case PROVIDER_OK: + if (provider.allowsAnonymous()) { + ProviderAPICommand.execute(this.getContext(), DOWNLOAD_VPN_CERTIFICATE, provider); + } else { + // TODO: implement error message that this client only supports anonymous usage + } + break; + case CORRECTLY_DOWNLOADED_VPN_CERTIFICATE: + setupActivityCallback.onProviderSelected(provider); + setupActivityCallback.onConfigurationSuccess(); + break; + case PROVIDER_NOK: + case INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE: + case MISSING_NETWORK_CONNECTION: + case TOR_EXCEPTION: + case TOR_TIMEOUT: + String reasonToFail = resultData.getString(ERRORS); + setupActivityCallback.onError(reasonToFail); + break; + } } diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupFragmentFactory.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupFragmentFactory.java index fd76a841..eaf3fbfa 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupFragmentFactory.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupFragmentFactory.java @@ -58,4 +58,8 @@ public class SetupFragmentFactory { public int getItemCount() { return fragmentTypes.size(); } + + public int getPos(int fragmentType) { + return fragmentTypes.indexOf(fragmentType); + } } -- cgit v1.2.3 From 347070e5c0d9822f2ed2365ee6321d1d8a312c77 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Thu, 3 Aug 2023 16:56:19 +0200 Subject: fix empty permission fragment layout --- .../providersetup/fragments/EmptyPermissionSetupFragment.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'app/src/main/java/se/leap/bitmaskclient/providersetup/fragments') diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/EmptyPermissionSetupFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/EmptyPermissionSetupFragment.java index 18751ee5..1574edff 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/EmptyPermissionSetupFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/EmptyPermissionSetupFragment.java @@ -14,6 +14,7 @@ import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import se.leap.bitmaskclient.databinding.FEmptyPermissionSetupBinding; import se.leap.bitmaskclient.databinding.FVpnPermissionSetupBinding; public class EmptyPermissionSetupFragment extends BaseSetupFragment { @@ -71,7 +72,7 @@ public class EmptyPermissionSetupFragment extends BaseSetupFragment { @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - FVpnPermissionSetupBinding binding = FVpnPermissionSetupBinding.inflate(inflater, container, false); + FEmptyPermissionSetupBinding binding = FEmptyPermissionSetupBinding.inflate(inflater, container, false); return binding.getRoot(); } -- cgit v1.2.3 From 99bdde5e10222a65d43f5c9d1e3fe2cde502fd1e Mon Sep 17 00:00:00 2001 From: cyBerta Date: Thu, 3 Aug 2023 16:57:28 +0200 Subject: show immediate feedback on button click before the provider SetupActivity vanishes --- .../providersetup/fragments/SetupSuccessFragment.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'app/src/main/java/se/leap/bitmaskclient/providersetup/fragments') diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupSuccessFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupSuccessFragment.java index 3e241012..eb775e2a 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupSuccessFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupSuccessFragment.java @@ -26,7 +26,12 @@ public class SetupSuccessFragment extends BaseSetupFragment { @Nullable Bundle savedInstanceState) { FSetupSuccessBinding binding = FSetupSuccessBinding.inflate(inflater, container, false); - binding.mainButton.setOnClickListener(v -> setupActivityCallback.onSetupFinished()); + binding.mainButton.setOnClickListener(v -> { + setupActivityCallback.onSetupFinished(); + binding.mainButton.updateState(false, true); + binding.mainButton.setEnabled(false); + }); + return binding.getRoot(); } -- cgit v1.2.3 From 95588ead61742b49250fabbf2fa4d05df9068f81 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Thu, 3 Aug 2023 17:17:38 +0200 Subject: preselect plain vpn/cirucmvention selection from shared preferences --- .../providersetup/fragments/CircumventionSetupFragment.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'app/src/main/java/se/leap/bitmaskclient/providersetup/fragments') diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java index 2709fd0b..4a08bc4e 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java @@ -43,7 +43,11 @@ public class CircumventionSetupFragment extends BaseSetupFragment { binding.rbPlainVpn.setTypeface(Typeface.DEFAULT, Typeface.BOLD); binding.rbCircumvention.setTypeface(Typeface.DEFAULT, Typeface.NORMAL); }); - binding.circumventionRadioGroup.check(binding.rbPlainVpn.getId()); + + int id = (PreferenceHelper.hasSnowflakePrefs() && PreferenceHelper.getUseSnowflake()) ? + binding.rbCircumvention.getId() : + binding.rbPlainVpn.getId(); + binding.circumventionRadioGroup.check(id); return binding.getRoot(); } -- cgit v1.2.3 From 84ed5d88e7d04379af5975f3a8af76ef4010d8c7 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Fri, 4 Aug 2023 01:15:33 +0200 Subject: hard code calyx and riseup provider names ... --- .../providersetup/fragments/ProviderSelectionFragment.java | 3 +-- .../fragments/viewmodel/ProviderSelectionViewModel.java | 12 ++++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) (limited to 'app/src/main/java/se/leap/bitmaskclient/providersetup/fragments') diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java index 7f80a99d..bf508e0b 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java @@ -58,9 +58,8 @@ public class ProviderSelectionFragment extends BaseSetupFragment implements Canc radioButtons = new ArrayList<>(); for (int i = 0; i < viewModel.size(); i++) { - Provider provider = viewModel.getProvider(i); RadioButton radioButton = new RadioButton(binding.getRoot().getContext()); - radioButton.setText(provider.getDomain()); + radioButton.setText(viewModel.getProviderName(i)); radioButton.setId(i); binding.providerRadioGroup.addView(radioButton); radioButtons.add(radioButton); diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModel.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModel.java index e3880181..d1954ffb 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModel.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModel.java @@ -78,4 +78,16 @@ public class ProviderSelectionViewModel extends ViewModel { public void setCustomUrl(String url) { customUrl = url; } + + + public String getProviderName(int pos) { + String domain = getProvider(pos).getDomain(); + if ("riseup.net".equals(domain)) { + return "Riseup"; + } + if ("calyx.net".equals(domain)) { + return "The Calyx Institute"; + } + return domain; + } } \ No newline at end of file -- cgit v1.2.3 From df84c3064002bb90b8e7d840afe25b6b58a64efd Mon Sep 17 00:00:00 2001 From: cyBerta Date: Fri, 4 Aug 2023 02:10:41 +0200 Subject: hide keyboard when edit text focus was lost in provider selection screen --- .../providersetup/fragments/ProviderSelectionFragment.java | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'app/src/main/java/se/leap/bitmaskclient/providersetup/fragments') diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java index bf508e0b..cabde405 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.base.models.Provider; import se.leap.bitmaskclient.base.models.ProviderObservable; +import se.leap.bitmaskclient.base.utils.ViewHelper; import se.leap.bitmaskclient.databinding.FProviderSelectionBinding; import se.leap.bitmaskclient.providersetup.activities.CancelCallback; import se.leap.bitmaskclient.providersetup.fragments.viewmodel.ProviderSelectionViewModel; @@ -103,6 +104,12 @@ public class ProviderSelectionFragment extends BaseSetupFragment implements Canc @Override public void afterTextChanged(Editable s) {} }); + + binding.editCustomProvider.setOnFocusChangeListener((v, hasFocus) -> { + if (!hasFocus) { + ViewHelper.hideKeyboardFrom(getContext(), v); + } + }); return binding.getRoot(); } -- cgit v1.2.3 From 2ba1e6a1b48ab2408f8cfa1a184e10b7c1b018b8 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Fri, 4 Aug 2023 03:51:10 +0200 Subject: fix cancel button visibility --- .../leap/bitmaskclient/providersetup/fragments/BaseSetupFragment.java | 1 + .../providersetup/fragments/ProviderSelectionFragment.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'app/src/main/java/se/leap/bitmaskclient/providersetup/fragments') diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/BaseSetupFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/BaseSetupFragment.java index 8012fe76..0ddf8ffc 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/BaseSetupFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/BaseSetupFragment.java @@ -55,6 +55,7 @@ public class BaseSetupFragment extends Fragment { super.onDetach(); setupActivityCallback.removeOnPageChangeCallback(viewPagerCallback); setupActivityCallback = null; + callFragmentSelected = false; } public void onFragmentSelected() { diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java index cabde405..ba3ff4aa 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java @@ -116,7 +116,7 @@ public class ProviderSelectionFragment extends BaseSetupFragment implements Canc @Override public void onFragmentSelected() { super.onFragmentSelected(); - setupActivityCallback.setCancelButtonHidden(!ProviderObservable.getInstance().getCurrentProvider().isConfigured()); + setupActivityCallback.setCancelButtonHidden(true); setupActivityCallback.setNavigationButtonHidden(false); } -- cgit v1.2.3 From b0f511db4c55a4afa1ef4812dd2b88d74cf1087f Mon Sep 17 00:00:00 2001 From: cyBerta Date: Fri, 4 Aug 2023 03:52:19 +0200 Subject: clean up --- .../providersetup/fragments/ConfigureProviderFragment.java | 2 -- 1 file changed, 2 deletions(-) (limited to 'app/src/main/java/se/leap/bitmaskclient/providersetup/fragments') diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java index 8475108f..325627f3 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java @@ -93,8 +93,6 @@ public class ConfigureProviderFragment extends BaseSetupFragment implements Obse return binding.getRoot(); } - - @Override public void onDestroyView() { super.onDestroyView(); -- cgit v1.2.3 From e3cdc73429b42bdfab9b836bddab49c6b3fc9640 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Fri, 4 Aug 2023 13:11:45 +0200 Subject: implement custom 'start vpn' button design for initial provider setup --- .../bitmaskclient/providersetup/fragments/SetupSuccessFragment.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'app/src/main/java/se/leap/bitmaskclient/providersetup/fragments') diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupSuccessFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupSuccessFragment.java index eb775e2a..448e357a 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupSuccessFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupSuccessFragment.java @@ -8,6 +8,7 @@ import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.databinding.FSetupSuccessBinding; public class SetupSuccessFragment extends BaseSetupFragment { @@ -28,9 +29,10 @@ public class SetupSuccessFragment extends BaseSetupFragment { binding.mainButton.setOnClickListener(v -> { setupActivityCallback.onSetupFinished(); - binding.mainButton.updateState(false, true); binding.mainButton.setEnabled(false); + binding.mainButton.setCustomDrawable(R.drawable.button_setup_circle_progress); }); + binding.mainButton.setCustomDrawable(R.drawable.button_setup_circle_start); return binding.getRoot(); } -- cgit v1.2.3 From b7b59f8fe808f038384f3deff2cd9672507f386d Mon Sep 17 00:00:00 2001 From: cyBerta Date: Sat, 5 Aug 2023 22:45:20 +0200 Subject: distinguish plain VPN and circumvention case regarding the configuration description in configure fragment --- .../providersetup/fragments/ConfigureProviderFragment.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'app/src/main/java/se/leap/bitmaskclient/providersetup/fragments') diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java index 325627f3..c8e19055 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java @@ -4,9 +4,12 @@ import static android.app.Activity.RESULT_CANCELED; import static android.view.View.GONE; import static android.view.View.VISIBLE; import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE; +import static se.leap.bitmaskclient.R.string.description_configure_provider; +import static se.leap.bitmaskclient.R.string.description_configure_provider_circumvention; import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_CODE; import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getUseSnowflake; import static se.leap.bitmaskclient.base.utils.ViewHelper.animateContainerVisibility; import static se.leap.bitmaskclient.providersetup.ProviderAPI.CORRECTLY_DOWNLOADED_VPN_CERTIFICATE; import static se.leap.bitmaskclient.providersetup.ProviderAPI.DOWNLOAD_VPN_CERTIFICATE; @@ -40,6 +43,7 @@ import java.util.List; import java.util.Observable; import java.util.Observer; +import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.base.models.Provider; import se.leap.bitmaskclient.base.utils.PreferenceHelper; import se.leap.bitmaskclient.databinding.FConfigureProviderBinding; @@ -78,7 +82,8 @@ public class ConfigureProviderFragment extends BaseSetupFragment implements Obse public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { binding = FConfigureProviderBinding.inflate(inflater, container, false); - binding.detailContainer.setVisibility(PreferenceHelper.getUseSnowflake() ? VISIBLE : GONE); + binding.detailContainer.setVisibility(getUseSnowflake() ? VISIBLE : GONE); + binding.tvCircumventionDescription.setText(getUseSnowflake() ? description_configure_provider_circumvention : description_configure_provider); binding.detailHeaderContainer.setOnClickListener(v -> { binding.ivExpand.animate().setDuration(250).rotation(isExpanded ? -90 : 0); showConnectionDetails(); @@ -103,7 +108,8 @@ public class ConfigureProviderFragment extends BaseSetupFragment implements Obse public void onFragmentSelected() { super.onFragmentSelected(); ignoreProviderAPIUpdates = false; - binding.detailContainer.setVisibility(PreferenceHelper.getUseSnowflake() ? VISIBLE : GONE); + binding.detailContainer.setVisibility(getUseSnowflake() ? VISIBLE : GONE); + binding.tvCircumventionDescription.setText(getUseSnowflake() ? description_configure_provider_circumvention : description_configure_provider); setupActivityCallback.setNavigationButtonHidden(true); setupActivityCallback.setCancelButtonHidden(false); ProviderAPICommand.execute(getContext(), SET_UP_PROVIDER, setupActivityCallback.getSelectedProvider()); -- cgit v1.2.3 From f9faf01dc0f847e51faafbe9b4b5082719cbf3cb Mon Sep 17 00:00:00 2001 From: cyBerta Date: Mon, 7 Aug 2023 01:25:39 +0200 Subject: Fixes various bugs related to the re-creation of the SetupActivity and the loaded Fragments after an layout orientation change. * fixes the initialization of fragments via a Bundle * saves state before the Activity/Fragments get destroyed and loads the old state on re-creation * fixes onFragmentSelected() callback handling --- .../providersetup/fragments/BaseSetupFragment.java | 54 ++++++++++++++++----- .../fragments/CircumventionSetupFragment.java | 8 ++-- .../fragments/ConfigureProviderFragment.java | 42 +++++++--------- .../fragments/EmptyPermissionSetupFragment.java | 48 ++++++++++++++----- .../fragments/NotificationSetupFragment.java | 8 ++-- .../fragments/ProviderSelectionFragment.java | 56 ++++++++++------------ .../fragments/SetupSuccessFragment.java | 9 ++-- .../fragments/VpnPermissionSetupFragment.java | 14 ++---- .../viewmodel/ProviderSelectionViewModel.java | 4 ++ 9 files changed, 137 insertions(+), 106 deletions(-) (limited to 'app/src/main/java/se/leap/bitmaskclient/providersetup/fragments') diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/BaseSetupFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/BaseSetupFragment.java index 0ddf8ffc..151361ba 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/BaseSetupFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/BaseSetupFragment.java @@ -1,8 +1,11 @@ package se.leap.bitmaskclient.providersetup.fragments; import android.content.Context; +import android.os.Bundle; +import android.view.View; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.viewpager2.widget.ViewPager2; @@ -10,8 +13,10 @@ import se.leap.bitmaskclient.providersetup.activities.SetupActivityCallback; public class BaseSetupFragment extends Fragment { - SetupActivityCallback setupActivityCallback; + public static String EXTRA_POSITION = "EXTRA_POSITION"; private boolean callFragmentSelected = false; + SetupActivityCallback setupActivityCallback; + private final ViewPager2.OnPageChangeCallback viewPagerCallback = new ViewPager2.OnPageChangeCallback() { @Override public void onPageSelected(int position) { @@ -23,10 +28,21 @@ public class BaseSetupFragment extends Fragment { } } }; - private final int position; + private int position; - public BaseSetupFragment(int position) { - this.position = position; + public static Bundle initBundle(int pos) { + Bundle bundle = new Bundle(); + bundle.putInt(EXTRA_POSITION, pos); + return bundle; + } + + public BaseSetupFragment() { + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + this.position = getArguments().getInt(EXTRA_POSITION); } @Override @@ -34,15 +50,26 @@ public class BaseSetupFragment extends Fragment { super.onAttach(context); if (getActivity() instanceof SetupActivityCallback) { setupActivityCallback = (SetupActivityCallback) getActivity(); - setupActivityCallback.registerOnPageChangeCallback(viewPagerCallback); - if (setupActivityCallback.getCurrentPosition() == position) { - handleCallFragmentSelected(); - } } else { throw new IllegalStateException("These setup fragments are closely coupled to SetupActivityCallback interface. Activities instantiating them are required to implement the interface"); } } + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + outState.putInt(EXTRA_POSITION, position); + super.onSaveInstanceState(outState); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + setupActivityCallback.registerOnPageChangeCallback(viewPagerCallback); + if (setupActivityCallback.getCurrentPosition() == position) { + handleCallFragmentSelected(); + } + } + private void handleCallFragmentSelected() { if (!callFragmentSelected) { callFragmentSelected = true; @@ -51,11 +78,16 @@ public class BaseSetupFragment extends Fragment { } @Override - public void onDetach() { - super.onDetach(); + public void onDestroyView() { setupActivityCallback.removeOnPageChangeCallback(viewPagerCallback); - setupActivityCallback = null; callFragmentSelected = false; + super.onDestroyView(); + } + + @Override + public void onDetach() { + setupActivityCallback = null; + super.onDetach(); } public void onFragmentSelected() { diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java index 4a08bc4e..11fa582b 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java @@ -14,12 +14,10 @@ import se.leap.bitmaskclient.databinding.FCircumventionSetupBinding; public class CircumventionSetupFragment extends BaseSetupFragment { - private CircumventionSetupFragment(int position) { - super(position); - } - public static CircumventionSetupFragment newInstance(int position) { - return new CircumventionSetupFragment(position); + CircumventionSetupFragment fragment = new CircumventionSetupFragment(); + fragment.setArguments(initBundle(position)); + return fragment; } @Override diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java index c8e19055..01bedfa0 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java @@ -27,7 +27,6 @@ import static se.leap.bitmaskclient.tor.TorStatusObservable.getLastSnowflakeLog; import static se.leap.bitmaskclient.tor.TorStatusObservable.getLastTorLog; import android.app.Activity; -import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.view.LayoutInflater; @@ -43,9 +42,7 @@ import java.util.List; import java.util.Observable; import java.util.Observer; -import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.base.models.Provider; -import se.leap.bitmaskclient.base.utils.PreferenceHelper; import se.leap.bitmaskclient.databinding.FConfigureProviderBinding; import se.leap.bitmaskclient.eip.EipSetupListener; import se.leap.bitmaskclient.eip.EipSetupObserver; @@ -58,18 +55,16 @@ public class ConfigureProviderFragment extends BaseSetupFragment implements Obse private static final String TAG = ConfigureProviderFragment.class.getSimpleName(); - public static ConfigureProviderFragment newInstance(int position) { - return new ConfigureProviderFragment(position); - } - FConfigureProviderBinding binding; private boolean isExpanded = false; private boolean ignoreProviderAPIUpdates = false; private TorLogAdapter torLogAdapter; - private ConfigureProviderFragment(int position) { - super(position); + public static ConfigureProviderFragment newInstance(int position) { + ConfigureProviderFragment fragment = new ConfigureProviderFragment(); + fragment.setArguments(initBundle(position)); + return fragment; } @Override @@ -98,10 +93,21 @@ public class ConfigureProviderFragment extends BaseSetupFragment implements Obse return binding.getRoot(); } + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + setupActivityCallback.registerCancelCallback(this); + TorStatusObservable.getInstance().addObserver(this); + EipSetupObserver.addListener(this); + } + @Override public void onDestroyView() { - super.onDestroyView(); + setupActivityCallback.removeCancelCallback(this); + TorStatusObservable.getInstance().deleteObserver(this); + EipSetupObserver.removeListener(this); binding = null; + super.onDestroyView(); } @Override @@ -115,22 +121,6 @@ public class ConfigureProviderFragment extends BaseSetupFragment implements Obse ProviderAPICommand.execute(getContext(), SET_UP_PROVIDER, setupActivityCallback.getSelectedProvider()); } - @Override - public void onAttach(@NonNull Context context) { - super.onAttach(context); - setupActivityCallback.registerCancelCallback(this); - TorStatusObservable.getInstance().addObserver(this); - EipSetupObserver.addListener(this); - } - - @Override - public void onDetach() { - setupActivityCallback.removeCancelCallback(this); - TorStatusObservable.getInstance().deleteObserver(this); - EipSetupObserver.removeListener(this); - super.onDetach(); - } - protected void showConnectionDetails() { binding.connectionDetailLogs.addOnScrollListener(new RecyclerView.OnScrollListener() { diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/EmptyPermissionSetupFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/EmptyPermissionSetupFragment.java index 1574edff..4226d804 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/EmptyPermissionSetupFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/EmptyPermissionSetupFragment.java @@ -15,10 +15,12 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import se.leap.bitmaskclient.databinding.FEmptyPermissionSetupBinding; -import se.leap.bitmaskclient.databinding.FVpnPermissionSetupBinding; public class EmptyPermissionSetupFragment extends BaseSetupFragment { + public static String EXTRA_VPN_INTENT = "EXTRA_VPN_INTENT"; + public static String EXTRA_NOTIFICATION_PERMISSON_ACTION = "EXTRA_NOTIFICATION_PERMISSON_ACTION"; + private String notificationPermissionAction = null; private Intent vpnPermissionIntent = null; @@ -51,22 +53,28 @@ public class EmptyPermissionSetupFragment extends BaseSetupFragment { } ); - private EmptyPermissionSetupFragment(int position, String permissionAction) { - super(position); - this.notificationPermissionAction = permissionAction; - } - - private EmptyPermissionSetupFragment(int position, Intent vpnPermissionIntent) { - super(position); - this.vpnPermissionIntent = vpnPermissionIntent; - } public static EmptyPermissionSetupFragment newInstance(int position, Intent vpnPermissionIntent) { - return new EmptyPermissionSetupFragment(position, vpnPermissionIntent); + Bundle bundle = initBundle(position); + bundle.putParcelable(EXTRA_VPN_INTENT, vpnPermissionIntent); + EmptyPermissionSetupFragment fragment = new EmptyPermissionSetupFragment(); + fragment.setArguments(bundle); + return fragment; } public static EmptyPermissionSetupFragment newInstance(int position, String notificationPermissionAction) { - return new EmptyPermissionSetupFragment(position, notificationPermissionAction); + Bundle bundle = initBundle(position); + bundle.putString(EXTRA_NOTIFICATION_PERMISSON_ACTION, notificationPermissionAction); + EmptyPermissionSetupFragment fragment = new EmptyPermissionSetupFragment(); + fragment.setArguments(bundle); + return fragment; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + this.vpnPermissionIntent = getArguments().getParcelable(EXTRA_VPN_INTENT); + this.notificationPermissionAction = getArguments().getString(EXTRA_NOTIFICATION_PERMISSON_ACTION); } @Override @@ -76,6 +84,22 @@ public class EmptyPermissionSetupFragment extends BaseSetupFragment { return binding.getRoot(); } + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + if (vpnPermissionIntent != null) { + outState.putParcelable(EXTRA_VPN_INTENT, vpnPermissionIntent); + } + if (notificationPermissionAction != null) { + outState.putString(EXTRA_NOTIFICATION_PERMISSON_ACTION, notificationPermissionAction); + } + super.onSaveInstanceState(outState); + } + + @Override + public void onViewStateRestored(@Nullable Bundle savedInstanceState) { + super.onViewStateRestored(savedInstanceState); + } + @Override public void onFragmentSelected() { super.onFragmentSelected(); diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/NotificationSetupFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/NotificationSetupFragment.java index 72374bb9..a9589336 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/NotificationSetupFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/NotificationSetupFragment.java @@ -12,12 +12,10 @@ import se.leap.bitmaskclient.databinding.FNotificationSetupBinding; public class NotificationSetupFragment extends BaseSetupFragment { - private NotificationSetupFragment(int position) { - super(position); - } - public static NotificationSetupFragment newInstance(int position) { - return new NotificationSetupFragment(position); + NotificationSetupFragment fragment = new NotificationSetupFragment(); + fragment.setArguments(initBundle(position)); + return fragment; } @Override diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java index ba3ff4aa..e8f37e43 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java @@ -34,14 +34,13 @@ public class ProviderSelectionFragment extends BaseSetupFragment implements Canc private FProviderSelectionBinding binding; - private ProviderSelectionFragment(int position) { - super(position); - } - public static ProviderSelectionFragment newInstance(int position) { - return new ProviderSelectionFragment(position); + ProviderSelectionFragment fragment = new ProviderSelectionFragment(); + fragment.setArguments(initBundle(position)); + return fragment; } + @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -72,6 +71,20 @@ public class ProviderSelectionFragment extends BaseSetupFragment implements Canc radioButtons.add(radioButton); binding.editCustomProvider.setVisibility(viewModel.getEditProviderVisibility()); + return binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + setupActivityCallback.registerCancelCallback(this); + } + + @Override + public void onFragmentSelected() { + super.onFragmentSelected(); + setupActivityCallback.setCancelButtonHidden(true); + setupActivityCallback.setNavigationButtonHidden(false); binding.providerRadioGroup.setOnCheckedChangeListener((group, checkedId) -> { viewModel.setSelected(checkedId); for (RadioButton rb : radioButtons) { @@ -86,7 +99,6 @@ public class ProviderSelectionFragment extends BaseSetupFragment implements Canc setupActivityCallback.onProviderSelected(new Provider(binding.editCustomProvider.getText().toString())); } }); - binding.providerRadioGroup.check(viewModel.getSelected()); binding.editCustomProvider.addTextChangedListener(new TextWatcher() { @Override @@ -95,9 +107,11 @@ public class ProviderSelectionFragment extends BaseSetupFragment implements Canc @Override public void onTextChanged(CharSequence s, int start, int before, int count) { viewModel.setCustomUrl(s.toString()); - setupActivityCallback.onSetupStepValidationChanged(viewModel.isValidConfig()); - if (viewModel.isValidConfig()) { - setupActivityCallback.onProviderSelected(new Provider(s.toString())); + if (viewModel.isCustomProviderSelected()) { + setupActivityCallback.onSetupStepValidationChanged(viewModel.isValidConfig()); + if (viewModel.isValidConfig()) { + setupActivityCallback.onProviderSelected(new Provider(s.toString())); + } } } @@ -110,33 +124,15 @@ public class ProviderSelectionFragment extends BaseSetupFragment implements Canc ViewHelper.hideKeyboardFrom(getContext(), v); } }); - return binding.getRoot(); - } - - @Override - public void onFragmentSelected() { - super.onFragmentSelected(); - setupActivityCallback.setCancelButtonHidden(true); - setupActivityCallback.setNavigationButtonHidden(false); - } - - @Override - public void onAttach(@NonNull Context context) { - super.onAttach(context); - setupActivityCallback.registerCancelCallback(this); - } - - @Override - public void onDetach() { - setupActivityCallback.removeCancelCallback(this); - super.onDetach(); + binding.providerRadioGroup.check(viewModel.getSelected()); } @Override public void onDestroyView() { - super.onDestroyView(); + setupActivityCallback.removeCancelCallback(this); binding = null; radioButtons = null; + super.onDestroyView(); } @Override diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupSuccessFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupSuccessFragment.java index 448e357a..daf4ed6c 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupSuccessFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupSuccessFragment.java @@ -13,13 +13,10 @@ import se.leap.bitmaskclient.databinding.FSetupSuccessBinding; public class SetupSuccessFragment extends BaseSetupFragment { - - private SetupSuccessFragment(int position) { - super(position); - } - public static SetupSuccessFragment newInstance(int position) { - return new SetupSuccessFragment(position); + SetupSuccessFragment fragment = new SetupSuccessFragment(); + fragment.setArguments(initBundle(position)); + return fragment; } @Override diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/VpnPermissionSetupFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/VpnPermissionSetupFragment.java index a6af0b36..188ba9ac 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/VpnPermissionSetupFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/VpnPermissionSetupFragment.java @@ -13,13 +13,10 @@ import se.leap.bitmaskclient.databinding.FVpnPermissionSetupBinding; public class VpnPermissionSetupFragment extends BaseSetupFragment { - - private VpnPermissionSetupFragment(int position) { - super(position); - } - public static VpnPermissionSetupFragment newInstance(int position) { - return new VpnPermissionSetupFragment(position); + VpnPermissionSetupFragment fragment = new VpnPermissionSetupFragment(); + fragment.setArguments(initBundle(position)); + return fragment; } @Override @@ -29,11 +26,6 @@ public class VpnPermissionSetupFragment extends BaseSetupFragment { return binding.getRoot(); } - @Override - public void onAttach(@NonNull Context context) { - super.onAttach(context); - } - @Override public void onFragmentSelected() { super.onFragmentSelected(); diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModel.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModel.java index d1954ffb..aa2fe7cb 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModel.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModel.java @@ -54,6 +54,10 @@ public class ProviderSelectionViewModel extends ViewModel { return true; } + public boolean isCustomProviderSelected() { + return selected == ADD_PROVIDER; + } + public CharSequence getProviderDescription(Context context) { if (selected == ADD_PROVIDER) { return context.getText(R.string.add_provider_description); -- cgit v1.2.3 From 34539d080f2ce05eb668267180283f8332835d2c Mon Sep 17 00:00:00 2001 From: cyBerta Date: Mon, 7 Aug 2023 03:08:33 +0200 Subject: avoid possible NPEs in ConfigureProviderFragment --- .../providersetup/fragments/ConfigureProviderFragment.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'app/src/main/java/se/leap/bitmaskclient/providersetup/fragments') diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java index 01bedfa0..3c36065e 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java @@ -147,10 +147,13 @@ public class ConfigureProviderFragment extends BaseSetupFragment implements Obse public void update(Observable o, Object arg) { if (o instanceof TorStatusObservable) { Activity activity = getActivity(); - if (activity == null) { + if (activity == null || binding == null) { return; } activity.runOnUiThread(() -> { + if (binding == null) { + return; + } if (TorStatusObservable.getStatus() != TorStatusObservable.TorStatus.OFF) { if (binding.connectionDetailContainer.getVisibility() == GONE) { showConnectionDetails(); -- cgit v1.2.3