diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java b/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java index 32b53b12e..279f3ee1d 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java @@ -36,7 +36,10 @@ import org.briarproject.briar.BriarCoreModule; import org.briarproject.briar.android.attachment.AttachmentModule; import org.briarproject.briar.android.attachment.media.MediaModule; import org.briarproject.briar.android.conversation.glide.BriarModelLoader; +import org.briarproject.briar.android.hotspot.AbstractTabsFragment; import org.briarproject.briar.android.hotspot.HotspotIntroFragment; +import org.briarproject.briar.android.hotspot.HotspotManualFragment; +import org.briarproject.briar.android.hotspot.HotspotQrFragment; import org.briarproject.briar.android.logging.CachingLogHandler; import org.briarproject.briar.android.login.SignInReminderReceiver; import org.briarproject.briar.android.removabledrive.ChooserFragment; @@ -219,6 +222,12 @@ public interface AndroidComponent void inject(HotspotIntroFragment hotspotIntroFragment); + void inject(AbstractTabsFragment abstractTabsFragment); + + void inject(HotspotQrFragment hotspotQrFragment); + + void inject(HotspotManualFragment hotspotManualFragment); + void inject(ChooserFragment chooserFragment); void inject(SendFragment sendFragment); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/AbstractTabsFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/AbstractTabsFragment.java new file mode 100644 index 000000000..c229cfc0e --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/AbstractTabsFragment.java @@ -0,0 +1,109 @@ +package org.briarproject.briar.android.hotspot; + +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.Toast; + +import com.google.android.material.tabs.TabLayout; +import com.google.android.material.tabs.TabLayoutMediator; + +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +import org.briarproject.briar.R; + +import javax.inject.Inject; + +import androidx.annotation.CallSuper; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; +import androidx.viewpager2.adapter.FragmentStateAdapter; +import androidx.viewpager2.widget.ViewPager2; + +import static android.widget.Toast.LENGTH_SHORT; +import static org.briarproject.briar.android.AppModule.getAndroidComponent; + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public abstract class AbstractTabsFragment extends Fragment { + + @Inject + ViewModelProvider.Factory viewModelFactory; + + protected HotspotViewModel viewModel; + + protected Button stopButton; + protected Button connectedButton; + + @Override + public void onAttach(Context context) { + super.onAttach(context); + getAndroidComponent(requireContext()).inject(this); + viewModel = new ViewModelProvider(requireActivity(), viewModelFactory) + .get(HotspotViewModel.class); + } + + @Override + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + return inflater + .inflate(R.layout.fragment_hotspot_tabs, container, false); + } + + @Override + @CallSuper + public void onViewCreated(@NonNull View view, + @Nullable Bundle savedInstanceState) { + TabAdapter tabAdapter = new TabAdapter(this); + ViewPager2 viewPager = view.findViewById(R.id.pager); + viewPager.setAdapter(tabAdapter); + TabLayout tabLayout = view.findViewById(R.id.tabLayout); + new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> { + // tabs are set in XML, but just dummies that don't get not added + if (position == 0) { + tab.setText(R.string.hotspot_tab_manual); + tab.setIcon(R.drawable.forum_item_create_white); + } else if (position == 1) { + tab.setText(R.string.qr_code); + tab.setIcon(R.drawable.ic_qr_code); + } else throw new AssertionError(); + }).attach(); + + stopButton = view.findViewById(R.id.stopButton); + stopButton.setOnClickListener(v -> { + Toast.makeText(requireContext(), "Not yet implemented", + LENGTH_SHORT).show(); + }); + connectedButton = view.findViewById(R.id.connectedButton); + } + + protected abstract Fragment getFirstFragment(); + + protected abstract Fragment getSecondFragment(); + + private class TabAdapter extends FragmentStateAdapter { + private TabAdapter(Fragment fragment) { + super(fragment); + } + + @NonNull + @Override + public Fragment createFragment(int position) { + if (position == 0) return getFirstFragment(); + if (position == 1) return getSecondFragment(); + throw new AssertionError(); + } + + @Override + public int getItemCount() { + return 2; + } + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotActivity.java index 43ce86ccd..ce511e244 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotActivity.java @@ -1,7 +1,10 @@ package org.briarproject.briar.android.hotspot; import android.os.Bundle; +import android.view.Menu; +import android.view.MenuInflater; import android.view.MenuItem; +import android.widget.Toast; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; @@ -16,6 +19,8 @@ import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; import androidx.lifecycle.ViewModelProvider; +import static android.widget.Toast.LENGTH_SHORT; + @MethodsNotNullByDefault @ParametersNotNullByDefault public class HotspotActivity extends BriarActivity { @@ -42,6 +47,8 @@ public class HotspotActivity extends BriarActivity { ab.setDisplayHomeAsUpEnabled(true); } + // TODO observe viewmodel state and show error or HotspotFragment + if (state == null) { getSupportFragmentManager().beginTransaction() .replace(R.id.fragmentContainer, new HotspotIntroFragment(), @@ -50,11 +57,21 @@ public class HotspotActivity extends BriarActivity { } } + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.hotspot_help_action, menu); + return super.onCreateOptionsMenu(menu); + } + @Override public boolean onOptionsItemSelected(@NonNull MenuItem item) { if (item.getItemId() == android.R.id.home) { onBackPressed(); return true; + } else if (item.getItemId() == R.id.action_help) { + Toast.makeText(this, "Not yet implemented", LENGTH_SHORT).show(); + return true; } return super.onOptionsItemSelected(item); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotFragment.java new file mode 100644 index 000000000..2e6782abf --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotFragment.java @@ -0,0 +1,47 @@ +package org.briarproject.briar.android.hotspot; + +import android.os.Bundle; +import android.view.View; + +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +import org.briarproject.briar.R; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public class HotspotFragment extends AbstractTabsFragment { + + public final static String TAG = HotspotFragment.class.getName(); + + @Override + public void onViewCreated(@NonNull View view, + @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + // no need to call into the ViewModel here + connectedButton.setOnClickListener(v -> { + getParentFragmentManager().beginTransaction() + .setCustomAnimations(R.anim.step_next_in, + R.anim.step_previous_out, R.anim.step_previous_in, + R.anim.step_next_out) + .replace(R.id.fragmentContainer, new WebsiteFragment(), + WebsiteFragment.TAG) + .addToBackStack(WebsiteFragment.TAG) + .commit(); + }); + } + + @Override + protected Fragment getFirstFragment() { + return HotspotManualFragment.newInstance(true); + } + + @Override + protected Fragment getSecondFragment() { + return HotspotQrFragment.newInstance(true); + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotIntroFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotIntroFragment.java index ef565050c..58ce3d323 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotIntroFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotIntroFragment.java @@ -59,6 +59,18 @@ public class HotspotIntroFragment extends Fragment { startButton.setVisibility(INVISIBLE); progressBar.setVisibility(VISIBLE); progressTextView.setVisibility(VISIBLE); + // TODO remove below, tell viewModel to start hotspot instead + v.postDelayed(() -> { + getParentFragmentManager().beginTransaction() + .setCustomAnimations(R.anim.step_next_in, + R.anim.step_previous_out, + R.anim.step_previous_in, + R.anim.step_next_out) + .replace(R.id.fragmentContainer, new HotspotFragment(), + HotspotFragment.TAG) + .addToBackStack(HotspotFragment.TAG) + .commit(); + }, 1500); }); return v; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotManualFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotManualFragment.java new file mode 100644 index 000000000..0efca7768 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotManualFragment.java @@ -0,0 +1,85 @@ +package org.briarproject.briar.android.hotspot; + +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +import org.briarproject.briar.R; + +import javax.inject.Inject; + +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; + +import static android.view.View.GONE; +import static org.briarproject.briar.android.AppModule.getAndroidComponent; + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public class HotspotManualFragment extends Fragment { + + public final static String TAG = HotspotManualFragment.class.getName(); + + @Inject + ViewModelProvider.Factory viewModelFactory; + + private HotspotViewModel viewModel; + + static HotspotManualFragment newInstance(boolean forWifiConnect) { + HotspotManualFragment f = new HotspotManualFragment(); + Bundle bundle = new Bundle(); + bundle.putBoolean("forWifiConnect", forWifiConnect); + f.setArguments(bundle); + return f; + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + getAndroidComponent(requireContext()).inject(this); + viewModel = new ViewModelProvider(requireActivity(), viewModelFactory) + .get(HotspotViewModel.class); + } + + @Override + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + return inflater + .inflate(R.layout.fragment_hotspot_manual, container, false); + } + + @Override + public void onViewCreated(View v, @Nullable Bundle savedInstanceState) { + super.onViewCreated(v, savedInstanceState); + + TextView manualIntroView = v.findViewById(R.id.manualIntroView); + TextView ssidLabelView = v.findViewById(R.id.ssidLabelView); + TextView ssidView = v.findViewById(R.id.ssidView); + TextView passwordView = v.findViewById(R.id.passwordView); + TextView altView = v.findViewById(R.id.altView); + + if (requireArguments().getBoolean("forWifiConnect")) { + manualIntroView.setText(R.string.hotspot_manual_wifi); + ssidLabelView.setText(R.string.hotspot_manual_wifi_ssid); + // TODO observe state in ViewModel and get info from there instead + ssidView.setText("DIRECT-42-dfzsgf34ef"); + passwordView.setText("sdf78shfd8"); + altView.setText(R.string.hotspot_manual_wifi_alt); + } else { + manualIntroView.setText(R.string.hotspot_manual_site); + ssidLabelView.setText(R.string.hotspot_manual_site_address); + // TODO observe state in ViewModel and get info from there instead + ssidView.setText("http://192.168.49.1:9999"); + altView.setText(R.string.hotspot_manual_site_alt); + v.findViewById(R.id.passwordLabelView).setVisibility(GONE); + passwordView.setVisibility(GONE); + } + } +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotQrFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotQrFragment.java new file mode 100644 index 000000000..edf5cf28d --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotQrFragment.java @@ -0,0 +1,70 @@ +package org.briarproject.briar.android.hotspot; + +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +import org.briarproject.briar.R; + +import javax.inject.Inject; + +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; + +import static org.briarproject.briar.android.AppModule.getAndroidComponent; + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public class HotspotQrFragment extends Fragment { + + public final static String TAG = HotspotQrFragment.class.getName(); + + @Inject + ViewModelProvider.Factory viewModelFactory; + + private HotspotViewModel viewModel; + + static HotspotQrFragment newInstance(boolean forWifiConnect) { + HotspotQrFragment f = new HotspotQrFragment(); + Bundle bundle = new Bundle(); + bundle.putBoolean("forWifiConnect", forWifiConnect); + f.setArguments(bundle); + return f; + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + getAndroidComponent(requireContext()).inject(this); + viewModel = new ViewModelProvider(requireActivity(), viewModelFactory) + .get(HotspotViewModel.class); + } + + @Override + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View v = inflater + .inflate(R.layout.fragment_hotspot_qr, container, false); + + TextView qrIntroView = v.findViewById(R.id.qrIntroView); + ImageView qrCodeView = v.findViewById(R.id.qrCodeView); + + if (requireArguments().getBoolean("forWifiConnect")) { + qrIntroView.setText(R.string.hotspot_qr_wifi); + // TODO observe state in ViewModel and get QR code from there + } else { + qrIntroView.setText(R.string.hotspot_qr_site); + // TODO observe state in ViewModel and get QR code from there + } + return v; + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotViewModel.java index 5669a19ae..60b006377 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotViewModel.java @@ -25,4 +25,6 @@ class HotspotViewModel extends DbViewModel { super(application, dbExecutor, lifecycleManager, db, androidExecutor); } + // TODO copy actual code from Offline Hotspot app + } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/WebsiteFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/WebsiteFragment.java new file mode 100644 index 000000000..98d3a9575 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/WebsiteFragment.java @@ -0,0 +1,38 @@ +package org.briarproject.briar.android.hotspot; + +import android.os.Bundle; +import android.view.View; + +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import static android.view.View.INVISIBLE; + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public class WebsiteFragment extends AbstractTabsFragment { + + public final static String TAG = WebsiteFragment.class.getName(); + + @Override + public void onViewCreated(@NonNull View view, + @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + connectedButton.setVisibility(INVISIBLE); + } + + @Override + protected Fragment getFirstFragment() { + return HotspotManualFragment.newInstance(false); + } + + @Override + protected Fragment getSecondFragment() { + return HotspotQrFragment.newInstance(false); + } + +} diff --git a/briar-android/src/main/res/drawable/ic_portable_wifi_off.xml b/briar-android/src/main/res/drawable/ic_portable_wifi_off.xml new file mode 100644 index 000000000..d214583a2 --- /dev/null +++ b/briar-android/src/main/res/drawable/ic_portable_wifi_off.xml @@ -0,0 +1,10 @@ + + + diff --git a/briar-android/src/main/res/drawable/ic_qr_code.xml b/briar-android/src/main/res/drawable/ic_qr_code.xml new file mode 100644 index 000000000..55b5bb0a7 --- /dev/null +++ b/briar-android/src/main/res/drawable/ic_qr_code.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + diff --git a/briar-android/src/main/res/layout/fragment_hotspot_manual.xml b/briar-android/src/main/res/layout/fragment_hotspot_manual.xml new file mode 100644 index 000000000..cf92b01b7 --- /dev/null +++ b/briar-android/src/main/res/layout/fragment_hotspot_manual.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + diff --git a/briar-android/src/main/res/layout/fragment_hotspot_qr.xml b/briar-android/src/main/res/layout/fragment_hotspot_qr.xml new file mode 100644 index 000000000..bb1dfd2fb --- /dev/null +++ b/briar-android/src/main/res/layout/fragment_hotspot_qr.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + diff --git a/briar-android/src/main/res/layout/fragment_hotspot_tabs.xml b/briar-android/src/main/res/layout/fragment_hotspot_tabs.xml new file mode 100644 index 000000000..3620bb00c --- /dev/null +++ b/briar-android/src/main/res/layout/fragment_hotspot_tabs.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + +