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 08cf0d02e..5e04f88eb 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 @@ -39,6 +39,7 @@ import org.briarproject.briar.android.login.SignInReminderReceiver; import org.briarproject.briar.android.settings.ConnectionsFragment; import org.briarproject.briar.android.settings.NotificationsFragment; import org.briarproject.briar.android.settings.SecurityFragment; +import org.briarproject.briar.android.settings.SettingsFragment; import org.briarproject.briar.android.view.EmojiTextInputView; import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.DozeWatchdog; @@ -197,6 +198,8 @@ public interface AndroidComponent void inject(BriarModelLoader briarModelLoader); + void inject(SettingsFragment settingsFragment); + void inject(ConnectionsFragment connectionsFragment); void inject(SecurityFragment securityFragment); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/AvatarPreference.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/AvatarPreference.java new file mode 100644 index 000000000..e3f30f981 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/AvatarPreference.java @@ -0,0 +1,45 @@ +package org.briarproject.briar.android.settings; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.TextView; + +import org.briarproject.briar.R; + +import androidx.annotation.Nullable; +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; +import de.hdodenhof.circleimageview.CircleImageView; + +import static org.briarproject.briar.android.view.AuthorView.setAvatar; + +public class AvatarPreference extends Preference { + + @Nullable + private OwnIdentityInfo info; + + public AvatarPreference(Context context, AttributeSet attrs) { + super(context, attrs); + setLayoutResource(R.layout.preference_avatar); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + View v = holder.itemView; + if (info != null) { + TextView textViewUserName = v.findViewById(R.id.username); + CircleImageView imageViewAvatar = v.findViewById(R.id.avatarImage); + textViewUserName.setText(info.getLocalAuthor().getName()); + setAvatar(imageViewAvatar, info.getLocalAuthor().getId(), + info.getAuthorInfo()); + } + } + + void setOwnIdentityInfo(OwnIdentityInfo info) { + this.info = info; + notifyChanged(); + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsActivity.java index 530e54b4e..afb93173f 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsActivity.java @@ -1,55 +1,31 @@ package org.briarproject.briar.android.settings; -import android.content.Intent; -import android.net.Uri; import android.os.Bundle; import android.view.MenuItem; -import android.view.View; -import android.widget.TextView; -import android.widget.Toast; -import org.briarproject.bramble.api.FeatureFlags; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.BriarActivity; -import org.briarproject.briar.android.util.UiUtils; -import org.briarproject.briar.android.view.AuthorView; - -import javax.inject.Inject; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentFactory; import androidx.fragment.app.FragmentManager; -import androidx.lifecycle.ViewModelProvider; import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback; -import de.hdodenhof.circleimageview.CircleImageView; - -import static android.widget.Toast.LENGTH_LONG; -import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_AVATAR_IMAGE; @MethodsNotNullByDefault @ParametersNotNullByDefault public class SettingsActivity extends BriarActivity implements OnPreferenceStartFragmentCallback { - @Inject - ViewModelProvider.Factory viewModelFactory; - @Inject - FeatureFlags featureFlags; - - private SettingsViewModel settingsViewModel; - @Override public void injectActivity(ActivityComponent component) { component.inject(this); - settingsViewModel = new ViewModelProvider(this, viewModelFactory) - .get(SettingsViewModel.class); } @Override @@ -63,33 +39,6 @@ public class SettingsActivity extends BriarActivity } setContentView(R.layout.activity_settings); - - if (featureFlags.shouldEnableProfilePictures()) { - TextView textViewUserName = findViewById(R.id.username); - CircleImageView imageViewAvatar = - findViewById(R.id.avatarImage); - - settingsViewModel.getOwnIdentityInfo().observe(this, us -> { - textViewUserName.setText(us.getLocalAuthor().getName()); - AuthorView.setAvatar(imageViewAvatar, - us.getLocalAuthor().getId(), us.getAuthorInfo()); - }); - - settingsViewModel.getSetAvatarFailed() - .observeEvent(this, failed -> { - if (failed) { - Toast.makeText(this, - R.string.change_profile_picture_failed_message, - LENGTH_LONG).show(); - } - }); - - View avatarGroup = findViewById(R.id.avatarGroup); - avatarGroup.setOnClickListener(e -> selectAvatarImage()); - } else { - View view = findViewById(R.id.avatarGroup); - view.setVisibility(View.GONE); - } } @Override @@ -101,32 +50,6 @@ public class SettingsActivity extends BriarActivity return false; } - private void selectAvatarImage() { - Intent intent = UiUtils.createSelectImageIntent(false); - startActivityForResult(intent, REQUEST_AVATAR_IMAGE); - } - - @Override - protected void onActivityResult(int request, int result, - @Nullable Intent data) { - super.onActivityResult(request, result, data); - - if (request == REQUEST_AVATAR_IMAGE && result == RESULT_OK) { - onAvatarImageReceived(data); - } - } - - private void onAvatarImageReceived(@Nullable Intent resultData) { - if (resultData == null) return; - Uri uri = resultData.getData(); - if (uri == null) return; - - ConfirmAvatarDialogFragment dialog = - ConfirmAvatarDialogFragment.newInstance(uri); - dialog.show(getSupportFragmentManager(), - ConfirmAvatarDialogFragment.TAG); - } - @Override public boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref) { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java index 8b5de3b4c..e3769e9e2 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java @@ -1,17 +1,31 @@ package org.briarproject.briar.android.settings; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; 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 javax.inject.Inject; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; +import androidx.lifecycle.ViewModelProvider; import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceGroup; +import static android.app.Activity.RESULT_OK; import static java.util.Objects.requireNonNull; +import static org.briarproject.briar.android.AppModule.getAndroidComponent; import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; +import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_AVATAR_IMAGE; +import static org.briarproject.briar.android.util.UiUtils.createSelectImageIntent; import static org.briarproject.briar.android.util.UiUtils.triggerFeedback; @MethodsNotNullByDefault @@ -20,35 +34,88 @@ public class SettingsFragment extends PreferenceFragmentCompat { public static final String SETTINGS_NAMESPACE = "android-ui"; + private static final String PREF_KEY_AVATAR = "pref_key_avatar"; + private static final String PREF_KEY_FEEDBACK = "pref_key_send_feedback"; + private static final String PREF_KEY_DEV = "pref_key_dev"; + private static final String PREF_KEY_EXPLODE = "pref_key_explode"; + + @Inject + ViewModelProvider.Factory viewModelFactory; + + private SettingsViewModel viewModel; + private AvatarPreference prefAvatar; + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + getAndroidComponent(context).inject(this); + viewModel = new ViewModelProvider(requireActivity(), viewModelFactory) + .get(SettingsViewModel.class); + } + @Override public void onCreatePreferences(Bundle bundle, String s) { addPreferencesFromResource(R.xml.settings); + prefAvatar = requireNonNull(findPreference(PREF_KEY_AVATAR)); + if (viewModel.shouldEnableProfilePictures()) { + prefAvatar.setOnPreferenceClickListener(preference -> { + Intent intent = createSelectImageIntent(false); + startActivityForResult(intent, REQUEST_AVATAR_IMAGE); + return true; + }); + } else { + prefAvatar.setVisible(false); + } + Preference prefFeedback = - requireNonNull(findPreference("pref_key_send_feedback")); + requireNonNull(findPreference(PREF_KEY_FEEDBACK)); prefFeedback.setOnPreferenceClickListener(preference -> { triggerFeedback(requireContext()); return true; }); - Preference explode = requireNonNull(findPreference("pref_key_explode")); + Preference explode = requireNonNull(findPreference(PREF_KEY_EXPLODE)); if (IS_DEBUG_BUILD) { explode.setOnPreferenceClickListener(preference -> { throw new RuntimeException("Boom!"); }); } else { - explode.setVisible(false); - findPreference("pref_key_test_data").setVisible(false); - PreferenceGroup testing = explode.getParent(); - if (testing == null) throw new AssertionError(); - testing.setVisible(false); + PreferenceGroup dev = requireNonNull(findPreference(PREF_KEY_DEV)); + dev.setVisible(false); } } + @Override + public void onViewCreated(@NonNull View view, + @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + viewModel.getOwnIdentityInfo().observe(getViewLifecycleOwner(), us -> + prefAvatar.setOwnIdentityInfo(us) + ); + } + @Override public void onStart() { super.onStart(); requireActivity().setTitle(R.string.settings_button); } + @Override + public void onActivityResult(int request, int result, + @Nullable Intent data) { + super.onActivityResult(request, result, data); + if (request == REQUEST_AVATAR_IMAGE && result == RESULT_OK) { + if (data == null) return; + Uri uri = data.getData(); + if (uri == null) return; + + DialogFragment dialog = + ConfirmAvatarDialogFragment.newInstance(uri); + dialog.show(getParentFragmentManager(), + ConfirmAvatarDialogFragment.TAG); + } + } + } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsViewModel.java index d0123ecd3..707d63b1a 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsViewModel.java @@ -3,7 +3,9 @@ package org.briarproject.briar.android.settings; import android.app.Application; import android.content.ContentResolver; import android.net.Uri; +import android.widget.Toast; +import org.briarproject.bramble.api.FeatureFlags; import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.TransactionManager; @@ -29,8 +31,6 @@ import org.briarproject.briar.R; import org.briarproject.briar.android.attachment.UnsupportedMimeTypeException; import org.briarproject.briar.android.attachment.media.ImageCompressor; import org.briarproject.briar.android.viewmodel.DbViewModel; -import org.briarproject.briar.android.viewmodel.LiveEvent; -import org.briarproject.briar.android.viewmodel.MutableLiveEvent; import org.briarproject.briar.api.avatar.AvatarManager; import org.briarproject.briar.api.identity.AuthorInfo; import org.briarproject.briar.api.identity.AuthorManager; @@ -45,6 +45,7 @@ import javax.inject.Inject; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; +import static android.widget.Toast.LENGTH_LONG; import static java.util.Arrays.asList; import static java.util.logging.Level.WARNING; import static java.util.logging.Logger.getLogger; @@ -75,6 +76,7 @@ class SettingsViewModel extends DbViewModel implements EventListener { private final AuthorManager authorManager; private final ImageCompressor imageCompressor; private final Executor ioExecutor; + private final FeatureFlags featureFlags; final SettingsStore settingsStore; final TorSummaryProvider torSummaryProvider; @@ -85,8 +87,6 @@ class SettingsViewModel extends DbViewModel implements EventListener { private final MutableLiveData ownIdentityInfo = new MutableLiveData<>(); - private final MutableLiveEvent setAvatarFailed = - new MutableLiveEvent<>(); private final MutableLiveData screenLockEnabled = new MutableLiveData<>(); private final MutableLiveData screenLockTimeout = @@ -106,7 +106,8 @@ class SettingsViewModel extends DbViewModel implements EventListener { ImageCompressor imageCompressor, LocationUtils locationUtils, CircumventionProvider circumventionProvider, - @IoExecutor Executor ioExecutor) { + @IoExecutor Executor ioExecutor, + FeatureFlags featureFlags) { super(application, dbExecutor, lifecycleManager, db, androidExecutor); this.settingsManager = settingsManager; this.identityManager = identityManager; @@ -115,6 +116,7 @@ class SettingsViewModel extends DbViewModel implements EventListener { this.avatarManager = avatarManager; this.authorManager = authorManager; this.ioExecutor = ioExecutor; + this.featureFlags = featureFlags; this.settingsStore = new SettingsStore(settingsManager, dbExecutor, SETTINGS_NAMESPACE); torSummaryProvider = new TorSummaryProvider(getApplication(), @@ -126,7 +128,7 @@ class SettingsViewModel extends DbViewModel implements EventListener { eventBus.addListener(this); loadSettings(); - loadOwnIdentityInfo(); + if (shouldEnableProfilePictures()) loadOwnIdentityInfo(); } @Override @@ -154,6 +156,10 @@ class SettingsViewModel extends DbViewModel implements EventListener { }); } + boolean shouldEnableProfilePictures() { + return featureFlags.shouldEnableProfilePictures(); + } + private void loadOwnIdentityInfo() { runOnDbThread(() -> { try { @@ -206,7 +212,7 @@ class SettingsViewModel extends DbViewModel implements EventListener { trySetAvatar(uri); } catch (IOException e) { logException(LOG, WARNING, e); - setAvatarFailed.postEvent(true); + onSetAvatarFailed(); } }); } @@ -230,17 +236,19 @@ class SettingsViewModel extends DbViewModel implements EventListener { loadOwnIdentityInfo(); } catch (IOException | DbException e) { logException(LOG, WARNING, e); - setAvatarFailed.postEvent(true); + onSetAvatarFailed(); } }); } - LiveData getOwnIdentityInfo() { - return ownIdentityInfo; + private void onSetAvatarFailed() { + Toast.makeText(getApplication(), + R.string.change_profile_picture_failed_message, + LENGTH_LONG).show(); } - LiveEvent getSetAvatarFailed() { - return setAvatarFailed; + LiveData getOwnIdentityInfo() { + return ownIdentityInfo; } LiveData getScreenLockEnabled() { diff --git a/briar-android/src/main/res/drawable/ic_feedback.xml b/briar-android/src/main/res/drawable/ic_feedback.xml new file mode 100644 index 000000000..b7e7b41f4 --- /dev/null +++ b/briar-android/src/main/res/drawable/ic_feedback.xml @@ -0,0 +1,10 @@ + + + diff --git a/briar-android/src/main/res/layout/activity_settings.xml b/briar-android/src/main/res/layout/activity_settings.xml index 180e2728e..90826e8ba 100644 --- a/briar-android/src/main/res/layout/activity_settings.xml +++ b/briar-android/src/main/res/layout/activity_settings.xml @@ -1,78 +1,8 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file + android:layout_height="match_parent" + app:layout_behavior="@string/appbar_scrolling_view_behavior" /> diff --git a/briar-android/src/main/res/layout/preference_avatar.xml b/briar-android/src/main/res/layout/preference_avatar.xml new file mode 100644 index 000000000..61173ea4a --- /dev/null +++ b/briar-android/src/main/res/layout/preference_avatar.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + diff --git a/briar-android/src/main/res/xml/settings.xml b/briar-android/src/main/res/xml/settings.xml index d98dc01af..b3cbe0e53 100644 --- a/briar-android/src/main/res/xml/settings.xml +++ b/briar-android/src/main/res/xml/settings.xml @@ -2,6 +2,8 @@ + + + android:title="@string/send_feedback" + app:icon="@drawable/ic_feedback" />