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 2730c340b..c718f2e39 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
@@ -35,7 +35,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.settings.ConnectionsFragment;
@@ -213,4 +216,10 @@ public interface AndroidComponent
void inject(NotificationsFragment notificationsFragment);
void inject(HotspotIntroFragment hotspotIntroFragment);
+
+ void inject(AbstractTabsFragment abstractTabsFragment);
+
+ void inject(HotspotQrFragment hotspotQrFragment);
+
+ void inject(HotspotManualFragment hotspotManualFragment);
}
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 @@
+