Implement UI for setting profile pictures

This commit is contained in:
Sebastian Kürten
2020-11-24 16:18:52 +01:00
committed by Torsten Grote
parent f819930570
commit 43c6ae4258
19 changed files with 527 additions and 29 deletions

View File

@@ -6,4 +6,7 @@ package org.briarproject.bramble.api;
public interface FeatureFlags {
boolean shouldEnableImageAttachments();
boolean shouldEnableProfilePictures();
}

View File

@@ -22,6 +22,17 @@ public class BrambleCoreIntegrationTestModule {
@Provides
FeatureFlags provideFeatureFlags() {
return () -> true;
return new FeatureFlags() {
@Override
public boolean shouldEnableImageAttachments() {
return true;
}
@Override
public boolean shouldEnableProfilePictures() {
return true;
}
};
}
}

View File

@@ -33,6 +33,7 @@ import org.briarproject.briar.android.forum.ForumModule;
import org.briarproject.briar.android.keyagreement.ContactExchangeModule;
import org.briarproject.briar.android.login.LoginModule;
import org.briarproject.briar.android.navdrawer.NavDrawerModule;
import org.briarproject.briar.android.settings.SettingsModule;
import org.briarproject.briar.android.privategroup.list.GroupListModule;
import org.briarproject.briar.android.reporting.DevReportModule;
import org.briarproject.briar.android.test.TestAvatarCreatorImpl;
@@ -70,6 +71,7 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
LoginModule.class,
NavDrawerModule.class,
ViewModelModule.class,
SettingsModule.class,
DevReportModule.class,
ContactListModule.class,
// below need to be within same scope as ViewModelProvider.Factory
@@ -255,6 +257,17 @@ public class AppModule {
@Provides
FeatureFlags provideFeatureFlags() {
return () -> IS_DEBUG_BUILD;
return new FeatureFlags() {
@Override
public boolean shouldEnableImageAttachments() {
return IS_DEBUG_BUILD;
}
@Override
public boolean shouldEnableProfilePictures() {
return IS_DEBUG_BUILD;
}
};
}
}

View File

@@ -67,6 +67,7 @@ import org.briarproject.briar.android.privategroup.reveal.RevealContactsFragment
import org.briarproject.briar.android.reporting.CrashFragment;
import org.briarproject.briar.android.reporting.CrashReportActivity;
import org.briarproject.briar.android.reporting.ReportFormFragment;
import org.briarproject.briar.android.settings.ConfirmAvatarDialogFragment;
import org.briarproject.briar.android.settings.SettingsActivity;
import org.briarproject.briar.android.settings.SettingsFragment;
import org.briarproject.briar.android.sharing.BlogInvitationActivity;
@@ -239,4 +240,6 @@ public interface ActivityComponent {
void inject(CrashFragment crashFragment);
void inject(ConfirmAvatarDialogFragment fragment);
}

View File

@@ -16,5 +16,6 @@ public interface RequestCodes {
int REQUEST_KEYGUARD_UNLOCK = 12;
int REQUEST_ATTACH_IMAGE = 13;
int REQUEST_SAVE_ATTACHMENT = 14;
int REQUEST_AVATAR_IMAGE = 15;
}

View File

@@ -105,10 +105,10 @@ class AttachmentCreationTask {
if (is == null) throw new IOException();
is = imageCompressor
.compressImage(is, contentType);
contentType = "image/jpeg";
long timestamp = System.currentTimeMillis();
AttachmentHeader h = messagingManager
.addLocalAttachment(groupId, timestamp, contentType, is);
.addLocalAttachment(groupId, timestamp,
ImageCompressor.MIME_TYPE, is);
tryToClose(is, LOG, WARNING);
logDuration(LOG, "Storing attachment", start);
return h;

View File

@@ -8,10 +8,15 @@ import java.io.InputStream;
public interface ImageCompressor {
/**
* The MIME type of compressed images
*/
String MIME_TYPE = "image/jpeg";
/**
* Load an image from {@code is}, compress it and return an InputStream
* from which the resulting image can be read. The image will be compressed
* such that it fits into a message.
* as a JPEG image such that it fits into a message.
*
* @param is the stream to read the source image from
* @param contentType the mimetype of the source image such as "image/jpeg"
@@ -23,8 +28,8 @@ public interface ImageCompressor {
/**
* Compress an image and return an InputStream from which the resulting
* image can be read. The image will be compressed such that it fits into
* a message.
* image can be read. The image will be compressed as a JPEG image such that
* it fits into a message.
*
* @param bitmap the source image
* @return a stream from which the resulting image can be read

View File

@@ -0,0 +1,90 @@
package org.briarproject.briar.android.settings;
import android.app.Dialog;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.BaseActivity;
import javax.inject.Inject;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProvider;
import static java.util.Objects.requireNonNull;
public class ConfirmAvatarDialogFragment extends DialogFragment {
final static String TAG = ConfirmAvatarDialogFragment.class.getName();
@Inject
ViewModelProvider.Factory viewModelFactory;
private SettingsViewModel settingsViewModel;
private static final String ARG_URI = "uri";
private Uri uri;
static ConfirmAvatarDialogFragment newInstance(Uri uri) {
ConfirmAvatarDialogFragment f = new ConfirmAvatarDialogFragment();
Bundle args = new Bundle();
args.putString(ARG_URI, uri.toString());
f.setArguments(args);
return f;
}
@Override
public void onAttach(Context ctx) {
super.onAttach(ctx);
((BaseActivity) requireActivity()).getActivityComponent().inject(this);
}
public Dialog onCreateDialog(Bundle savedInstanceState) {
Bundle args = requireArguments();
String argUri = requireNonNull(args.getString(ARG_URI));
uri = Uri.parse(argUri);
FragmentActivity activity = requireActivity();
ViewModelProvider provider =
new ViewModelProvider(activity, viewModelFactory);
settingsViewModel = provider.get(SettingsViewModel.class);
settingsViewModel.onCreate();
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
LayoutInflater factory = LayoutInflater.from(getContext());
final View view =
factory.inflate(R.layout.fragment_confirm_avatar_dialog, null);
builder.setView(view);
builder.setTitle(R.string.dialog_confirm_profile_picture_title)
.setMessage(R.string.dialog_confirm_profile_picture_question);
builder.setNegativeButton(R.string.cancel, null);
builder.setPositiveButton(R.string.dialog_confirm_profile_picture_set,
(dialog, id) -> {
settingsViewModel.setAvatar(uri);
});
ImageView imageView = view.findViewById(R.id.image);
imageView.setImageResource(R.drawable.contact_connected);
imageView.setImageURI(uri);
settingsViewModel.getOwnIdentityInfo().observe(activity, us -> {
TextView textViewUserName = view.findViewById(R.id.username);
textViewUserName.setText(us.getLocalAuthor().getName());
});
return builder.create();
}
}

View File

@@ -0,0 +1,26 @@
package org.briarproject.briar.android.settings;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.identity.AuthorInfo;
@NotNullByDefault
class OwnIdentityInfo {
private final LocalAuthor localAuthor;
private final AuthorInfo authorInfo;
OwnIdentityInfo(LocalAuthor localAuthor, AuthorInfo authorInfo) {
this.localAuthor = localAuthor;
this.authorInfo = authorInfo;
}
LocalAuthor getLocalAuthor() {
return localAuthor;
}
AuthorInfo getAuthorInfo() {
return authorInfo;
}
}

View File

@@ -1,16 +1,37 @@
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 org.briarproject.bramble.api.FeatureFlags;
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.lifecycle.ViewModelProvider;
import de.hdodenhof.circleimageview.CircleImageView;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_AVATAR_IMAGE;
public class SettingsActivity extends BriarActivity {
@Inject
ViewModelProvider.Factory viewModelFactory;
private SettingsViewModel settingsViewModel;
@Inject
FeatureFlags featureFlags;
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
@@ -22,6 +43,32 @@ public class SettingsActivity extends BriarActivity {
}
setContentView(R.layout.activity_settings);
if (featureFlags.shouldEnableProfilePictures()) {
ViewModelProvider provider =
new ViewModelProvider(this, viewModelFactory);
settingsViewModel = provider.get(SettingsViewModel.class);
settingsViewModel.onCreate();
settingsViewModel.getOwnIdentityInfo().observe(this, us -> {
TextView textViewUserName = findViewById(R.id.username);
textViewUserName.setText(us.getLocalAuthor().getName());
CircleImageView imageViewAvatar =
findViewById(R.id.avatarImage);
AuthorView
.setAvatar(imageViewAvatar, us.getLocalAuthor().getId(),
us.getAuthorInfo());
});
View avatarGroup = findViewById(R.id.avatarGroup);
avatarGroup.setOnClickListener(e -> {
selectAvatarImage();
});
} else {
View view = findViewById(R.id.avatarGroup);
view.setVisibility(View.GONE);
}
}
@Override
@@ -37,4 +84,31 @@ 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);
}
}

View File

@@ -0,0 +1,19 @@
package org.briarproject.briar.android.settings;
import org.briarproject.briar.android.viewmodel.ViewModelKey;
import androidx.lifecycle.ViewModel;
import dagger.Binds;
import dagger.Module;
import dagger.multibindings.IntoMap;
@Module
public abstract class SettingsModule {
@Binds
@IntoMap
@ViewModelKey(SettingsViewModel.class)
abstract ViewModel bindSettingsViewModel(
SettingsViewModel settingsViewModel);
}

View File

@@ -0,0 +1,116 @@
package org.briarproject.briar.android.settings;
import android.app.Application;
import android.content.ContentResolver;
import android.net.Uri;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.util.LogUtils;
import org.briarproject.briar.android.attachment.ImageCompressor;
import org.briarproject.briar.api.avatar.AvatarManager;
import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.briar.api.identity.AuthorManager;
import org.jsoup.UnsupportedMimeTypeException;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.Executor;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.inject.Inject;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import static java.util.Arrays.asList;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.AndroidUtils.getSupportedImageContentTypes;
@NotNullByDefault
public class SettingsViewModel extends AndroidViewModel {
private final static Logger LOG =
getLogger(SettingsViewModel.class.getName());
private final IdentityManager identityManager;
private final AvatarManager avatarManager;
private final AuthorManager authorManager;
private final ImageCompressor imageCompressor;
@DatabaseExecutor
private final Executor dbExecutor;
private final MutableLiveData<OwnIdentityInfo> ownIdentityInfo =
new MutableLiveData<>();
@Inject
SettingsViewModel(Application application,
IdentityManager identityManager,
AvatarManager avatarManager,
AuthorManager authorManager,
ImageCompressor imageCompressor,
@DatabaseExecutor Executor dbExecutor) {
super(application);
this.identityManager = identityManager;
this.imageCompressor = imageCompressor;
this.avatarManager = avatarManager;
this.authorManager = authorManager;
this.dbExecutor = dbExecutor;
}
void onCreate() {
if (ownIdentityInfo.getValue() == null) loadOwnIdentityInfo();
}
LiveData<OwnIdentityInfo> getOwnIdentityInfo() {
return ownIdentityInfo;
}
private void loadOwnIdentityInfo() {
dbExecutor.execute(() -> {
try {
LocalAuthor localAuthor = identityManager.getLocalAuthor();
AuthorInfo authorInfo = authorManager.getMyAuthorInfo();
ownIdentityInfo
.postValue(
new OwnIdentityInfo(localAuthor, authorInfo));
} catch (DbException e) {
LogUtils.logException(LOG, Level.WARNING, e);
}
});
}
void setAvatar(Uri uri) {
dbExecutor.execute(() -> {
try {
trySetAvatar(uri);
} catch (IOException | DbException e) {
LogUtils.logException(LOG, Level.WARNING, e);
}
});
}
private void trySetAvatar(Uri uri) throws IOException, DbException {
ContentResolver contentResolver =
getApplication().getContentResolver();
String contentType = contentResolver.getType(uri);
if (contentType == null) throw new IOException("null content type");
if (!asList(getSupportedImageContentTypes()).contains(contentType)) {
String uriString = uri.toString();
throw new UnsupportedMimeTypeException("", contentType, uriString);
}
InputStream is = contentResolver.openInputStream(uri);
if (is == null) throw new IOException(
"ContentResolver returned null when opening InputStream");
is = imageCompressor
.compressImage(is, contentType);
avatarManager.addAvatar(ImageCompressor.MIME_TYPE, is);
loadOwnIdentityInfo();
}
}

View File

@@ -57,7 +57,12 @@ import androidx.lifecycle.Observer;
import static android.content.Context.KEYGUARD_SERVICE;
import static android.content.Context.POWER_SERVICE;
import static android.content.Intent.ACTION_GET_CONTENT;
import static android.content.Intent.ACTION_OPEN_DOCUMENT;
import static android.content.Intent.CATEGORY_DEFAULT;
import static android.content.Intent.CATEGORY_OPENABLE;
import static android.content.Intent.EXTRA_ALLOW_MULTIPLE;
import static android.content.Intent.EXTRA_MIME_TYPES;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.os.Build.MANUFACTURER;
import static android.os.Build.VERSION.SDK_INT;
@@ -89,6 +94,7 @@ import static androidx.core.graphics.drawable.DrawableCompat.setTint;
import static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_RTL;
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.TimeUnit.DAYS;
import static org.briarproject.bramble.util.AndroidUtils.getSupportedImageContentTypes;
import static org.briarproject.briar.BuildConfig.APPLICATION_ID;
import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE;
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_APP_START_TIME;
@@ -250,6 +256,18 @@ public class UiUtils {
};
}
public static Intent createSelectImageIntent(boolean allowMultiple) {
Intent intent = new Intent(SDK_INT >= 19 ?
ACTION_OPEN_DOCUMENT : ACTION_GET_CONTENT);
intent.setType("image/*");
intent.addCategory(CATEGORY_OPENABLE);
if (SDK_INT >= 19)
intent.putExtra(EXTRA_MIME_TYPES, getSupportedImageContentTypes());
if (allowMultiple && SDK_INT >= 18)
intent.putExtra(EXTRA_ALLOW_MULTIPLE, true);
return intent;
}
public static void showOnboardingDialog(Context ctx, String text) {
new AlertDialog.Builder(ctx, R.style.OnboardingDialogTheme)
.setMessage(text)

View File

@@ -14,6 +14,7 @@ import org.briarproject.briar.R;
import org.briarproject.briar.android.attachment.AttachmentItemResult;
import org.briarproject.briar.android.attachment.AttachmentManager;
import org.briarproject.briar.android.attachment.AttachmentResult;
import org.briarproject.briar.android.util.UiUtils;
import org.briarproject.briar.android.view.ImagePreview.ImagePreviewListener;
import java.util.ArrayList;
@@ -29,18 +30,12 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt;
import static android.content.Intent.ACTION_GET_CONTENT;
import static android.content.Intent.ACTION_OPEN_DOCUMENT;
import static android.content.Intent.CATEGORY_OPENABLE;
import static android.content.Intent.EXTRA_ALLOW_MULTIPLE;
import static android.content.Intent.EXTRA_MIME_TYPES;
import static android.os.Build.VERSION.SDK_INT;
import static android.view.View.GONE;
import static android.widget.Toast.LENGTH_LONG;
import static androidx.core.content.ContextCompat.getColor;
import static androidx.customview.view.AbsSavedState.EMPTY_STATE;
import static androidx.lifecycle.Lifecycle.State.DESTROYED;
import static org.briarproject.bramble.util.AndroidUtils.getSupportedImageContentTypes;
import static org.briarproject.briar.android.util.UiUtils.resolveColorAttribute;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE;
@@ -127,14 +122,7 @@ public class TextAttachmentController extends TextSendController
}
private Intent getAttachFileIntent() {
Intent intent = new Intent(SDK_INT >= 19 ?
ACTION_OPEN_DOCUMENT : ACTION_GET_CONTENT);
intent.setType("image/*");
intent.addCategory(CATEGORY_OPENABLE);
if (SDK_INT >= 19)
intent.putExtra(EXTRA_MIME_TYPES, getSupportedImageContentTypes());
if (SDK_INT >= 18) intent.putExtra(EXTRA_ALLOW_MULTIPLE, true);
return intent;
return UiUtils.createSelectImageIntent(true);
}
/**

View File

@@ -1,11 +1,87 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/fragment"
android:name="org.briarproject.briar.android.settings.SettingsFragment"
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<fragment
android:id="@+id/fragment"
android:name="org.briarproject.briar.android.settings.SettingsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.core.widget.NestedScrollView>
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/avatarGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/avatarImage"
style="@style/BriarAvatar"
android:layout_width="@dimen/listitem_picture_size"
android:layout_height="@dimen/listitem_picture_size"
android:layout_gravity="bottom|left|start"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@mipmap/ic_launcher_round" />
<TextView
android:id="@+id/username"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:paddingStart="@dimen/margin_medium"
android:paddingEnd="@dimen/margin_medium"
android:textColor="@color/briar_text_primary_inverse"
android:textSize="@dimen/text_size_medium"
app:layout_constraintBottom_toTopOf="@+id/avatarExplanation"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/avatarImage"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="username" />
<TextView
android:id="@+id/avatarExplanation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp"
android:paddingStart="@dimen/margin_medium"
android:paddingEnd="@dimen/margin_medium"
android:text="@string/change_profile_picture"
android:textColor="@color/briar_text_secondary_inverse"
android:textSize="@dimen/text_size_small"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/avatarImage"
app:layout_constraintTop_toBottomOf="@+id/username"
tools:text="Change your profile picture" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingHorizontal="?dialogPreferredPadding">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/image"
style="@style/BriarAvatar"
android:layout_width="@dimen/listitem_picture_size"
android:layout_height="@dimen/listitem_picture_size"
android:layout_gravity="bottom|left|start"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@mipmap/ic_launcher_round" />
<TextView
android:id="@+id/username"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginStart="@dimen/margin_medium"
android:layout_marginLeft="@dimen/margin_medium"
android:gravity="center_vertical"
app:layout_constraintBottom_toBottomOf="@+id/image"
app:layout_constraintStart_toEndOf="@+id/image"
app:layout_constraintTop_toTopOf="parent"
android:textSize="@dimen/text_size_medium"
tools:text="username" />
<TextView
android:id="@+id/remark"
android:layout_marginTop="@dimen/listitem_vertical_margin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dialog_confirm_profile_picture_remark"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/image" />
</androidx.constraintlayout.widget.ConstraintLayout >

View File

@@ -453,6 +453,13 @@
<string name="blogs_rss_feeds_manage_empty_state">No RSS feeds to show\n\nTap the + icon to import a feed</string>
<string name="blogs_rss_feeds_manage_error">There was a problem loading your feeds. Please try again later.</string>
<!-- Settings Profile Picture -->
<string name="change_profile_picture">Tap to change your profile picture</string>
<string name="dialog_confirm_profile_picture_title">Please confirm</string>
<string name="dialog_confirm_profile_picture_question">Do you want to use this image as your profile picture?</string>
<string name="dialog_confirm_profile_picture_remark">Only your contacts can see your profile image</string>
<string name="dialog_confirm_profile_picture_set">Set picture</string>
<!-- Settings Display -->
<string name="pref_language_title">Language &amp; region</string>
<string name="pref_language_changed">This setting will take effect when you restart Briar. Please sign out and restart Briar.</string>

View File

@@ -88,5 +88,8 @@ internal class HeadlessModule(private val appDir: File) {
internal fun provideObjectMapper() = ObjectMapper()
@Provides
internal fun provideFeatureFlags() = FeatureFlags { false }
internal fun provideFeatureFlags() = object : FeatureFlags {
override fun shouldEnableImageAttachments() = false
override fun shouldEnableProfilePictures() = false
}
}

View File

@@ -76,7 +76,10 @@ internal class HeadlessTestModule(private val appDir: File) {
internal fun provideObjectMapper() = ObjectMapper()
@Provides
internal fun provideFeatureFlags() = FeatureFlags { false }
internal fun provideFeatureFlags() = object : FeatureFlags {
override fun shouldEnableImageAttachments() = false
override fun shouldEnableProfilePictures() = false
}
@Provides
internal fun provideTestAvatarCreator() = TestAvatarCreator { null }