Finalize wallpaper UX.

Co-authored-by: Greyson Parrelli <greyson@signal.org>
Co-authored-by: Alan Evans <alan@signal.org>
This commit is contained in:
Alex Hart 2021-01-20 17:09:36 -04:00 committed by Greyson Parrelli
parent a8ad1e718e
commit c244a98962
29 changed files with 455 additions and 90 deletions

View file

@ -309,7 +309,7 @@ public final class ContactSelectionListFragment extends LoggingFragment
}
recyclerView.setAdapter(concatenateAdapter);
recyclerView.addItemDecoration(new StickyHeaderDecoration(concatenateAdapter, true, true));
recyclerView.addItemDecoration(new StickyHeaderDecoration(concatenateAdapter, true, true, 0));
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {

View file

@ -266,12 +266,12 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
}
@Override
public HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent, int position) {
public HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent, int position, int type) {
return new HeaderViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.contact_selection_recyclerview_header, parent, false));
}
@Override
public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position) {
public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position, int type) {
((TextView)viewHolder.itemView).setText(getSpannedHeaderString(position));
}

View file

@ -429,6 +429,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
setContentView(R.layout.conversation_activity);
getWindow().getDecorView().setBackgroundResource(R.color.signal_background_primary);
WindowUtil.setLightNavigationBar(getWindow());
fragment = initFragment(R.id.fragment_content, new ConversationFragment(), dynamicLanguage.getCurrentLocale());
@ -1989,6 +1990,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
}
private void updateWallpaper(@Nullable ChatWallpaper chatWallpaper) {
Log.d(TAG, "Setting wallpaper.");
if (chatWallpaper != null) {
chatWallpaper.loadInto(wallpaper);
ChatWallpaperDimLevelUtil.applyDimLevelForNightMode(wallpaperDim, chatWallpaper);
@ -1996,6 +1998,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
wallpaper.setImageDrawable(null);
wallpaperDim.setVisibility(View.GONE);
}
fragment.onWallpaperChanged(chatWallpaper);
}
protected void initializeActionBar() {
@ -2105,6 +2108,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
this.viewModel = ViewModelProviders.of(this, new ConversationViewModel.Factory()).get(ConversationViewModel.class);
this.viewModel.setArgs(args);
this.viewModel.getWallpaper().observe(this, this::updateWallpaper);
}
private void initializeGroupViewModel() {
@ -2290,7 +2294,6 @@ public class ConversationActivity extends PassphraseRequiredActivity
updateReminders();
updateDefaultSubscriptionId(recipient.getDefaultSubscriptionId());
initializeSecurity(isSecureText, isDefaultSms);
updateWallpaper(recipient.getWallpaper());
if (searchViewItem == null || !searchViewItem.isActionViewExpanded()) {
invalidateOptionsMenu();

View file

@ -73,6 +73,10 @@ public class ConversationAdapter
private static final String TAG = Log.tag(ConversationAdapter.class);
public static final int HEADER_TYPE_POPOVER_DATE = 1;
public static final int HEADER_TYPE_INLINE_DATE = 2;
public static final int HEADER_TYPE_LAST_SEEN = 3;
private static final int MESSAGE_TYPE_OUTGOING_MULTIMEDIA = 0;
private static final int MESSAGE_TYPE_OUTGOING_TEXT = 1;
private static final int MESSAGE_TYPE_INCOMING_MULTIMEDIA = 2;
@ -102,6 +106,7 @@ public class ConversationAdapter
private View headerView;
private View footerView;
private PagingController pagingController;
private boolean hasWallpaper;
ConversationAdapter(@NonNull LifecycleOwner lifecycleOwner,
@NonNull GlideRequests glideRequests,
@ -132,6 +137,7 @@ public class ConversationAdapter
this.releasedFastRecords = new HashSet<>();
this.calendar = Calendar.getInstance();
this.digest = getMessageDigestOrThrow();
this.hasWallpaper = recipient.hasWallpaper();
setHasStableIds(true);
}
@ -243,7 +249,7 @@ public class ConversationAdapter
recipient,
searchQuery,
conversationMessage == recordToPulse,
recipient.hasWallpaper());
hasWallpaper);
if (conversationMessage == recordToPulse) {
recordToPulse = null;
@ -290,19 +296,27 @@ public class ConversationAdapter
}
@Override
public StickyHeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent, int position) {
public StickyHeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent, int position, int type) {
return new StickyHeaderViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.conversation_item_header, parent, false));
}
@Override
public void onBindHeaderViewHolder(StickyHeaderViewHolder viewHolder, int position) {
public void onBindHeaderViewHolder(StickyHeaderViewHolder viewHolder, int position, int type) {
ConversationMessage conversationMessage = Objects.requireNonNull(getItem(position));
viewHolder.setText(DateUtils.getRelativeDate(viewHolder.itemView.getContext(), locale, conversationMessage.getMessageRecord().getDateReceived()));
if (recipient.hasWallpaper()) {
viewHolder.setBackgroundRes(R.drawable.wallpaper_bubble_background_8);
} else {
viewHolder.clearBackground();
if (type == HEADER_TYPE_POPOVER_DATE) {
if (hasWallpaper) {
viewHolder.setBackgroundRes(R.drawable.wallpaper_bubble_background_8);
} else {
viewHolder.setBackgroundRes(R.drawable.sticky_date_header_background);
}
} else if (type == HEADER_TYPE_INLINE_DATE) {
if (hasWallpaper) {
viewHolder.setBackgroundRes(R.drawable.wallpaper_bubble_background_8);
} else {
viewHolder.clearBackground();
}
}
}
@ -334,7 +348,7 @@ public class ConversationAdapter
void onBindLastSeenViewHolder(StickyHeaderViewHolder viewHolder, int position) {
viewHolder.setText(viewHolder.itemView.getContext().getResources().getQuantityString(R.plurals.ConversationAdapter_n_unread_messages, (position + 1), (position + 1)));
if (recipient.hasWallpaper()) {
if (hasWallpaper) {
viewHolder.setBackgroundRes(R.drawable.wallpaper_bubble_background_8);
} else {
viewHolder.clearBackground();
@ -435,6 +449,17 @@ public class ConversationAdapter
notifyDataSetChanged();
}
/**
* Lets the adapter know that the wallpaper state has changed.
*/
void onHasWallpaperChanged(boolean hasWallpaper) {
if (this.hasWallpaper != hasWallpaper) {
Log.d(TAG, "Resetting adapter due to wallpaper change.");
this.hasWallpaper = hasWallpaper;
notifyDataSetChanged();
}
}
/**
* Adds a record to a memory cache to allow it to be rendered immediately, as opposed to waiting
* for a database change.

View file

@ -141,6 +141,7 @@ import org.thoughtcrime.securesms.util.WindowUtil;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import org.thoughtcrime.securesms.util.views.AdaptiveActionsToolbar;
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper;
import org.whispersystems.libsignal.util.guava.Optional;
import java.io.IOException;
@ -171,7 +172,7 @@ public class ConversationFragment extends LoggingFragment {
private Locale locale;
private RecyclerView list;
private RecyclerView.ItemDecoration lastSeenDecoration;
private RecyclerView.ItemDecoration stickyHeaderDecoration;
private RecyclerView.ItemDecoration popoverDateDecoration;
private ViewSwitcher topLoadMoreView;
private ViewSwitcher bottomLoadMoreView;
private ConversationTypingView typingView;
@ -390,6 +391,13 @@ public class ConversationFragment extends LoggingFragment {
snapToTopDataObserver.requestScrollPosition(position);
}
public void onWallpaperChanged(@Nullable ChatWallpaper wallpaper) {
if (list != null) {
Log.d(TAG, "Notifying adapter that wallpaper state has changed.");
getListAdapter().onHasWallpaperChanged(wallpaper != null);
}
}
private int getStartPosition() {
return conversationViewModel.getArgs().getStartingPosition();
}
@ -488,7 +496,7 @@ public class ConversationFragment extends LoggingFragment {
this.threadId = conversationViewModel.getArgs().getThreadId();
this.markReadHelper = new MarkReadHelper(threadId, requireContext());
conversationViewModel.onConversationDataAvailable(threadId, startingPosition);
conversationViewModel.onConversationDataAvailable(recipient.getId(), threadId, startingPosition);
messageCountsViewModel.setThreadId(threadId);
messageCountsViewModel.getUnreadMessagesCount().observe(getViewLifecycleOwner(), scrollToBottomButton::setUnreadCount);
@ -511,7 +519,7 @@ public class ConversationFragment extends LoggingFragment {
ConversationAdapter adapter = new ConversationAdapter(this, GlideApp.with(this), locale, selectionClickListener, this.recipient.get());
adapter.setPagingController(conversationViewModel.getPagingController());
list.setAdapter(adapter);
setStickyHeaderDecoration(adapter);
setPopoverDateDecoration(adapter);
ConversationAdapter.initializePool(list.getRecycledViewPool());
adapter.registerAdapterDataObserver(snapToTopDataObserver);
@ -638,7 +646,7 @@ public class ConversationFragment extends LoggingFragment {
messageRequestViewModel.setConversationInfo(recipient.getId(), threadId);
snapToTopDataObserver.requestScrollPosition(0);
conversationViewModel.onConversationDataAvailable(threadId, -1);
conversationViewModel.onConversationDataAvailable(recipient.getId(), threadId, -1);
messageCountsViewModel.setThreadId(threadId);
initializeListAdapter();
}
@ -652,13 +660,13 @@ public class ConversationFragment extends LoggingFragment {
}
}
public void setStickyHeaderDecoration(@NonNull ConversationAdapter adapter) {
if (stickyHeaderDecoration != null) {
list.removeItemDecoration(stickyHeaderDecoration);
public void setPopoverDateDecoration(@NonNull ConversationAdapter adapter) {
if (popoverDateDecoration != null) {
list.removeItemDecoration(popoverDateDecoration);
}
stickyHeaderDecoration = new StickyHeaderDecoration(adapter, false, false);
list.addItemDecoration(stickyHeaderDecoration);
popoverDateDecoration = new StickyHeaderDecoration(adapter, false, false, ConversationAdapter.HEADER_TYPE_INLINE_DATE);
list.addItemDecoration(popoverDateDecoration);
}
public void setLastSeen(long lastSeen) {
@ -1180,7 +1188,7 @@ public class ConversationFragment extends LoggingFragment {
private void bindScrollHeader(StickyHeaderViewHolder headerViewHolder, int positionId) {
if (((ConversationAdapter)list.getAdapter()).getHeaderId(positionId) != -1) {
((ConversationAdapter) list.getAdapter()).onBindHeaderViewHolder(headerViewHolder, positionId);
((ConversationAdapter) list.getAdapter()).onBindHeaderViewHolder(headerViewHolder, positionId, ConversationAdapter.HEADER_TYPE_POPOVER_DATE);
}
}
}

View file

@ -142,7 +142,7 @@ public final class ConversationReactionOverlay extends RelativeLayout {
}
public void setListVerticalTranslation(float translationY) {
maskView.setTargetParentTranslationY(translationY);
maskView.setTargetParentTranslationY(translationY - ViewUtil.getStatusBarHeight(maskView));
}
public void show(@NonNull Activity activity,

View file

@ -19,7 +19,11 @@ import org.thoughtcrime.securesms.database.DatabaseObserver;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.mediasend.Media;
import org.thoughtcrime.securesms.mediasend.MediaRepository;
import org.thoughtcrime.securesms.recipients.LiveRecipient;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper;
import org.whispersystems.libsignal.util.Pair;
import java.util.List;
@ -41,6 +45,8 @@ class ConversationViewModel extends ViewModel {
private final LiveData<Boolean> canShowAsBubble;
private final ProxyPagingController pagingController;
private final DatabaseObserver.Observer messageObserver;
private final MutableLiveData<RecipientId> recipientId;
private final LiveData<ChatWallpaper> wallpaper;
private ConversationIntents.Args args;
private int jumpToPosition;
@ -53,6 +59,7 @@ class ConversationViewModel extends ViewModel {
this.threadId = new MutableLiveData<>();
this.showScrollButtons = new MutableLiveData<>(false);
this.hasUnreadMentions = new MutableLiveData<>(false);
this.recipientId = new MutableLiveData<>();
this.pagingController = new ProxyPagingController();
this.messageObserver = pagingController::onDataInvalidated;
@ -97,6 +104,9 @@ class ConversationViewModel extends ViewModel {
conversationMetadata = Transformations.switchMap(messages, m -> metadata);
canShowAsBubble = LiveDataUtil.mapAsync(threadId, conversationRepository::canShowAsBubble);
wallpaper = Transformations.distinctUntilChanged(Transformations.map(Transformations.switchMap(recipientId,
id -> Recipient.live(id).getLiveData()),
Recipient::getWallpaper));
}
void onAttachmentKeyboardOpen() {
@ -104,11 +114,12 @@ class ConversationViewModel extends ViewModel {
}
@MainThread
void onConversationDataAvailable(long threadId, int startingPosition) {
void onConversationDataAvailable(@NonNull RecipientId recipientId, long threadId, int startingPosition) {
Log.d(TAG, "[onConversationDataAvailable] threadId: " + threadId + ", startingPosition: " + startingPosition);
this.jumpToPosition = startingPosition;
this.threadId.setValue(threadId);
this.recipientId.setValue(recipientId);
}
void clearThreadId() {
@ -128,6 +139,10 @@ class ConversationViewModel extends ViewModel {
return Transformations.distinctUntilChanged(LiveDataUtil.combineLatest(showScrollButtons, hasUnreadMentions, (a, b) -> a && b));
}
@NonNull LiveData<ChatWallpaper> getWallpaper() {
return wallpaper;
}
void setHasUnreadMentions(boolean hasUnreadMentions) {
this.hasUnreadMentions.setValue(hasUnreadMentions);
}

View file

@ -17,7 +17,7 @@ class LastSeenHeader extends StickyHeaderDecoration {
private final long lastSeenTimestamp;
LastSeenHeader(ConversationAdapter adapter, long lastSeenTimestamp) {
super(adapter, false, false);
super(adapter, false, false, ConversationAdapter.HEADER_TYPE_LAST_SEEN);
this.adapter = adapter;
this.lastSeenTimestamp = lastSeenTimestamp;
}

View file

@ -481,7 +481,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
private void initializeListAdapters() {
defaultAdapter = new ConversationListAdapter(GlideApp.with(this), this);
searchAdapter = new ConversationListSearchAdapter(GlideApp.with(this), this, Locale.getDefault());
searchAdapterDecoration = new StickyHeaderDecoration(searchAdapter, false, false);
searchAdapterDecoration = new StickyHeaderDecoration(searchAdapter, false, false, 0);
setAdapter(defaultAdapter);

View file

@ -94,13 +94,13 @@ class ConversationListSearchAdapter extends RecyclerView.Adapter<Conversation
}
@Override
public HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent, int position) {
public HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent, int position, int type) {
return new HeaderViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.search_result_list_divider, parent, false));
}
@Override
public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position) {
public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position, int type) {
viewHolder.bind((int) getHeaderId(position));
}

View file

@ -855,6 +855,10 @@ public class Recipient {
}
}
public boolean hasOwnWallpaper() {
return wallpaper != null;
}
/**
* A cheap way to check if wallpaper is set without doing any unnecessary proto parsing.
*/

View file

@ -30,15 +30,17 @@ public class StickyHeaderDecoration extends RecyclerView.ItemDecoration {
private final StickyHeaderAdapter adapter;
private final boolean renderInline;
private final boolean sticky;
private final int type;
/**
* @param adapter the sticky header adapter to use
*/
public StickyHeaderDecoration(StickyHeaderAdapter adapter, boolean renderInline, boolean sticky) {
public StickyHeaderDecoration(StickyHeaderAdapter adapter, boolean renderInline, boolean sticky, int type) {
this.adapter = adapter;
this.headerCache = new HashMap<>();
this.renderInline = renderInline;
this.sticky = sticky;
this.type = type;
}
/**
@ -88,9 +90,9 @@ public class StickyHeaderDecoration extends RecyclerView.ItemDecoration {
if (headerHolder == null) {
if (key != StickyHeaderAdapter.NO_HEADER_ID) {
headerHolder = adapter.onCreateHeaderViewHolder(parent, position);
headerHolder = adapter.onCreateHeaderViewHolder(parent, position, type);
//noinspection unchecked
adapter.onBindHeaderViewHolder(headerHolder, position);
adapter.onBindHeaderViewHolder(headerHolder, position, type);
}
if (headerHolder == null) {
@ -221,7 +223,7 @@ public class StickyHeaderDecoration extends RecyclerView.ItemDecoration {
* @param position position in the adapter
* @return a view holder for the created view
*/
T onCreateHeaderViewHolder(ViewGroup parent, int position);
T onCreateHeaderViewHolder(ViewGroup parent, int position, int type);
/**
* Updates the header view to reflect the header data for the given position.
@ -229,7 +231,7 @@ public class StickyHeaderDecoration extends RecyclerView.ItemDecoration {
* @param viewHolder the header view holder
* @param position the header's item position
*/
void onBindHeaderViewHolder(T viewHolder, int position);
void onBindHeaderViewHolder(T viewHolder, int position, int type);
int getItemCount();
}

View file

@ -20,18 +20,18 @@ public final class RecyclerViewConcatenateAdapterStickyHeader extends Recycle
}
@Override
public RecyclerView.ViewHolder onCreateHeaderViewHolder(ViewGroup parent, int position) {
return getForPosition(position).transform(p -> p.first().onCreateHeaderViewHolder(parent, p.second())).orNull();
public RecyclerView.ViewHolder onCreateHeaderViewHolder(ViewGroup parent, int position, int type) {
return getForPosition(position).transform(p -> p.first().onCreateHeaderViewHolder(parent, p.second(), type)).orNull();
}
@Override
public void onBindHeaderViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
public void onBindHeaderViewHolder(RecyclerView.ViewHolder viewHolder, int position, int type) {
Optional<Pair<StickyHeaderDecoration.StickyHeaderAdapter, Integer>> forPosition = getForPosition(position);
if (forPosition.isPresent()) {
Pair<StickyHeaderDecoration.StickyHeaderAdapter, Integer> stickyHeaderAdapterIntegerPair = forPosition.get();
//noinspection unchecked
stickyHeaderAdapterIntegerPair.first().onBindHeaderViewHolder(viewHolder, stickyHeaderAdapterIntegerPair.second());
stickyHeaderAdapterIntegerPair.first().onBindHeaderViewHolder(viewHolder, stickyHeaderAdapterIntegerPair.second(), type);
}
}

View file

@ -0,0 +1,43 @@
package org.thoughtcrime.securesms.wallpaper;
import android.graphics.Rect;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
class ChatWallpaperAlignmentDecoration extends RecyclerView.ItemDecoration {
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
int itemPosition = parent.getChildAdapterPosition(view);
int itemCount = state.getItemCount();
if (itemCount > 0 && itemPosition == itemCount - 1) {
outRect.set(0, 0, 0, 0);
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
int viewWidth = view.getMeasuredWidth() + params.rightMargin + params.leftMargin;
int availableWidth = (parent.getRight() - parent.getPaddingRight()) - (parent.getLeft() + parent.getPaddingLeft());
int itemsPerRow = availableWidth / viewWidth;
if (itemsPerRow == 1 || (itemPosition + 1) % itemsPerRow == 0) {
return;
}
int extraCellsNeeded = itemsPerRow - ((itemPosition + 1) % itemsPerRow);
setEnd(outRect, view.getLayoutDirection(), extraCellsNeeded * viewWidth);
} else {
super.getItemOffsets(outRect, view, parent, state);
}
}
private void setEnd(@NonNull Rect outRect, int layoutDirection, int end) {
if (layoutDirection == View.LAYOUT_DIRECTION_LTR) {
outRect.right = end;
} else {
outRect.left = end;
}
}
}

View file

@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.wallpaper;
import android.app.AlertDialog;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@ -8,6 +9,7 @@ import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.widget.SwitchCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
@ -18,6 +20,12 @@ import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.ThemeUtil;
public class ChatWallpaperFragment extends Fragment {
private boolean isSettingDimFromViewModel;
private View clearWallpaper;
private View resetAllWallpaper;
private View divider;
@Override
public @NonNull View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.chat_wallpaper_fragment, container, false);
@ -30,8 +38,10 @@ public class ChatWallpaperFragment extends Fragment {
View setWallpaper = view.findViewById(R.id.chat_wallpaper_set_wallpaper);
SwitchCompat dimInNightMode = view.findViewById(R.id.chat_wallpaper_dark_theme_dims_wallpaper);
View chatWallpaperDim = view.findViewById(R.id.chat_wallpaper_dim);
View clearWallpaper = view.findViewById(R.id.chat_wallpaper_clear_wallpaper);
View resetAllWallpaper = view.findViewById(R.id.chat_wallpaper_reset_all_wallpapers);
clearWallpaper = view.findViewById(R.id.chat_wallpaper_clear_wallpaper);
resetAllWallpaper = view.findViewById(R.id.chat_wallpaper_reset_all_wallpapers);
divider = view.findViewById(R.id.chat_wallpaper_divider);
viewModel.getCurrentWallpaper().observe(getViewLifecycleOwner(), wallpaper -> {
if (wallpaper.isPresent()) {
@ -40,36 +50,78 @@ public class ChatWallpaperFragment extends Fragment {
chatWallpaperPreview.setImageDrawable(null);
chatWallpaperPreview.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.signal_background_primary));
}
dimInNightMode.setEnabled(wallpaper.isPresent());
});
viewModel.getDimInDarkTheme().observe(getViewLifecycleOwner(), shouldDimInNightMode -> {
if (shouldDimInNightMode != dimInNightMode.isChecked()) {
isSettingDimFromViewModel = true;
dimInNightMode.setChecked(shouldDimInNightMode);
isSettingDimFromViewModel = false;
}
chatWallpaperDim.setAlpha(ChatWallpaper.FIXED_DIM_LEVEL_FOR_DARK_THEME);
chatWallpaperDim.setVisibility(shouldDimInNightMode && ThemeUtil.isDarkTheme(requireContext()) ? View.VISIBLE : View.GONE);
});
viewModel.getEnableWallpaperControls().observe(getViewLifecycleOwner(), enableWallpaperControls -> {
dimInNightMode.setEnabled(enableWallpaperControls);
clearWallpaper.setVisibility(enableWallpaperControls ? View.VISIBLE : View.GONE);
updateDividerVisibility();
});
chatWallpaperPreview.setOnClickListener(unused -> setWallpaper.performClick());
setWallpaper.setOnClickListener(unused -> Navigation.findNavController(view)
.navigate(R.id.action_chatWallpaperFragment_to_chatWallpaperSelectionFragment));
clearWallpaper.setOnClickListener(unused -> {
viewModel.setWallpaper(null);
viewModel.setDimInDarkTheme(false);
viewModel.saveWallpaperSelection();
});
resetAllWallpaper.setVisibility(viewModel.isGlobal() ? View.VISIBLE : View.GONE);
updateDividerVisibility();
clearWallpaper.setOnClickListener(unused -> {
confirmAction(R.string.ChatWallpaperFragment__clear_wallpaper_question_mark,
R.string.ChatWallpaperFragment__clear,
() -> {
viewModel.setWallpaper(null);
viewModel.setDimInDarkTheme(false);
viewModel.saveWallpaperSelection();
});
});
resetAllWallpaper.setOnClickListener(unused -> {
viewModel.setWallpaper(null);
viewModel.setDimInDarkTheme(false);
viewModel.resetAllWallpaper();
confirmAction(R.string.ChatWallpaperFragment__reset_all_wallpapers_question_mark,
R.string.ChatWallpaperFragment__reset,
() -> {
viewModel.setWallpaper(null);
viewModel.setDimInDarkTheme(false);
viewModel.resetAllWallpaper();
});
});
dimInNightMode.setOnCheckedChangeListener((buttonView, isChecked) -> viewModel.setDimInDarkTheme(isChecked));
dimInNightMode.setOnCheckedChangeListener((buttonView, isChecked) -> {
if (!isSettingDimFromViewModel) {
viewModel.setDimInDarkTheme(isChecked);
}
});
}
private void updateDividerVisibility() {
if (clearWallpaper.getVisibility() == View.VISIBLE || resetAllWallpaper.getVisibility() == View.VISIBLE) {
divider.setVisibility(View.VISIBLE);
} else {
divider.setVisibility(View.GONE);
}
}
private void confirmAction(@StringRes int title, @StringRes int positiveActionLabel, @NonNull Runnable onPositiveAction) {
new AlertDialog.Builder(requireContext())
.setMessage(title)
.setPositiveButton(positiveActionLabel, (dialog, which) -> {
onPositiveAction.run();
dialog.dismiss();
})
.setNegativeButton(android.R.string.cancel, (dialog, which) -> {
dialog.dismiss();
})
.setCancelable(true)
.show();
}
}

View file

@ -2,24 +2,30 @@ package org.thoughtcrime.securesms.wallpaper;
import android.content.Context;
import android.content.Intent;
import android.graphics.PorterDuff;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.core.view.ViewCompat;
import androidx.viewpager2.widget.ViewPager2;
import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.PassphraseRequiredActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.ActivityTransitionUtil;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.FullscreenHelper;
import org.thoughtcrime.securesms.util.MappingModel;
import org.thoughtcrime.securesms.util.WindowUtil;
import java.util.Collections;
@ -27,14 +33,16 @@ public class ChatWallpaperPreviewActivity extends PassphraseRequiredActivity {
public static final String EXTRA_CHAT_WALLPAPER = "extra.chat.wallpaper";
private static final String EXTRA_DIM_IN_DARK_MODE = "extra.dim.in.dark.mode";
private static final String EXTRA_RECIPIENT_ID = "extra.recipient.id";
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
public static @NonNull Intent create(@NonNull Context context, @NonNull ChatWallpaper selection, boolean dimInDarkMode) {
public static @NonNull Intent create(@NonNull Context context, @NonNull ChatWallpaper selection, @NonNull RecipientId recipientId, boolean dimInDarkMode) {
Intent intent = new Intent(context, ChatWallpaperPreviewActivity.class);
intent.putExtra(EXTRA_CHAT_WALLPAPER, selection);
intent.putExtra(EXTRA_DIM_IN_DARK_MODE, dimInDarkMode);
intent.putExtra(EXTRA_RECIPIENT_ID, recipientId);
return intent;
}
@ -52,6 +60,8 @@ public class ChatWallpaperPreviewActivity extends PassphraseRequiredActivity {
ChatWallpaper selected = getIntent().getParcelableExtra(EXTRA_CHAT_WALLPAPER);
boolean dim = getIntent().getBooleanExtra(EXTRA_DIM_IN_DARK_MODE, false);
Toolbar toolbar = findViewById(R.id.toolbar);
View bubble1 = findViewById(R.id.preview_bubble_1);
TextView bubble2 = findViewById(R.id.preview_bubble_2_text);
toolbar.setNavigationOnClickListener(unused -> {
finish();
@ -62,7 +72,7 @@ public class ChatWallpaperPreviewActivity extends PassphraseRequiredActivity {
adapter.submitList(Collections.singletonList(new ChatWallpaperSelectionMappingModel(selected)));
repository.getAllWallpaper(wallpapers -> adapter.submitList(Stream.of(wallpapers)
.map(wallpaper -> ChatWallpaperFactory.updateWithDimming(wallpaper, dim ? 1f : 0f))
.map(wallpaper -> ChatWallpaperFactory.updateWithDimming(wallpaper, dim ? ChatWallpaper.FIXED_DIM_LEVEL_FOR_DARK_THEME : 0f))
.<MappingModel<?>>map(ChatWallpaperSelectionMappingModel::new)
.toList()));
@ -73,7 +83,16 @@ public class ChatWallpaperPreviewActivity extends PassphraseRequiredActivity {
finish();
});
RecipientId recipientId = getIntent().getParcelableExtra(EXTRA_RECIPIENT_ID);
if (recipientId != null) {
Recipient recipient = Recipient.live(recipientId).get();
bubble1.getBackground().setColorFilter(recipient.getColor().toConversationColor(this), PorterDuff.Mode.SRC_IN);
bubble2.setText(getString(R.string.ChatWallpaperPreviewActivity__set_wallpaper_for_s, recipient.getDisplayName(this)));
}
new FullscreenHelper(this).showSystemUI();
WindowUtil.setLightStatusBarFromTheme(this);
WindowUtil.setLightNavigationBarFromTheme(this);
}
@Override

View file

@ -14,6 +14,8 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.concurrent.SerialExecutor;
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Executor;
@ -24,14 +26,19 @@ class ChatWallpaperRepository {
@MainThread
@Nullable ChatWallpaper getCurrentWallpaper(@Nullable RecipientId recipientId) {
if (recipientId != null) {
return Recipient.resolved(recipientId).getWallpaper();
return Recipient.live(recipientId).get().getWallpaper();
} else {
return SignalStore.wallpaper().getWallpaper();
}
}
void getAllWallpaper(@NonNull Consumer<List<ChatWallpaper>> consumer) {
consumer.accept(ChatWallpaper.BUILTINS);
EXECUTOR.execute(() -> {
List<ChatWallpaper> wallpapers = new ArrayList<>(ChatWallpaper.BUILTINS);
wallpapers.addAll(WallpaperStorage.getAll(ApplicationDependencies.getApplication()));
consumer.accept(wallpapers);
});
}
void saveWallpaper(@Nullable RecipientId recipientId, @Nullable ChatWallpaper chatWallpaper) {
@ -52,10 +59,21 @@ class ChatWallpaperRepository {
});
}
void setDimInDarkTheme(@NonNull RecipientId recipientId, boolean dimInDarkTheme) {
void setDimInDarkTheme(@Nullable RecipientId recipientId, boolean dimInDarkTheme) {
if (recipientId != null) {
EXECUTOR.execute(() -> {
DatabaseFactory.getRecipientDatabase(ApplicationDependencies.getApplication()).setDimWallpaperInDarkTheme(recipientId, dimInDarkTheme);
Recipient recipient = Recipient.resolved(recipientId);
if (recipient.hasOwnWallpaper()) {
DatabaseFactory.getRecipientDatabase(ApplicationDependencies.getApplication()).setDimWallpaperInDarkTheme(recipientId, dimInDarkTheme);
} else if (recipient.hasWallpaper()) {
DatabaseFactory.getRecipientDatabase(ApplicationDependencies.getApplication())
.setWallpaper(recipientId,
ChatWallpaperFactory.updateWithDimming(recipient.getWallpaper(),
dimInDarkTheme ? ChatWallpaper.FIXED_DIM_LEVEL_FOR_DARK_THEME
: 0f));
} else {
throw new IllegalStateException("Unexpected call to setDimInDarkTheme, no wallpaper has been set on the given recipient or globally.");
}
});
} else {
SignalStore.wallpaper().setDimInDarkTheme(dimInDarkTheme);

View file

@ -1,11 +1,13 @@
package org.thoughtcrime.securesms.wallpaper;
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.annotation.NonNull;
import androidx.annotation.Nullable;
@ -14,10 +16,12 @@ import androidx.lifecycle.ViewModelProviders;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.flexbox.AlignContent;
import com.google.android.flexbox.FlexboxLayoutManager;
import com.google.android.flexbox.JustifyContent;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.util.ActivityTransitionUtil;
import org.thoughtcrime.securesms.wallpaper.crop.WallpaperImageSelectionActivity;
@ -39,23 +43,30 @@ public class ChatWallpaperSelectionFragment extends Fragment {
FlexboxLayoutManager flexboxLayoutManager = new FlexboxLayoutManager(requireContext());
chooseFromPhotos.setOnClickListener(unused -> {
startActivityForResult(WallpaperImageSelectionActivity.getIntent(requireContext(), viewModel.getRecipientId()), CHOOSE_WALLPAPER);
askForPermissionIfNeededAndLaunchPhotoSelection();
});
@SuppressWarnings("CodeBlock2Expr")
ChatWallpaperSelectionAdapter adapter = new ChatWallpaperSelectionAdapter(chatWallpaper -> {
startActivityForResult(ChatWallpaperPreviewActivity.create(requireActivity(), chatWallpaper, viewModel.getDimInDarkTheme().getValue()), CHOOSE_WALLPAPER);
startActivityForResult(ChatWallpaperPreviewActivity.create(requireActivity(), chatWallpaper, viewModel.getRecipientId(), viewModel.getDimInDarkTheme().getValue()), CHOOSE_WALLPAPER);
ActivityTransitionUtil.setSlideInTransition(requireActivity());
});
flexboxLayoutManager.setJustifyContent(JustifyContent.SPACE_AROUND);
flexboxLayoutManager.setJustifyContent(JustifyContent.CENTER);
recyclerView.setLayoutManager(flexboxLayoutManager);
recyclerView.setAdapter(adapter);
recyclerView.addItemDecoration(new ChatWallpaperAlignmentDecoration());
viewModel = ViewModelProviders.of(requireActivity()).get(ChatWallpaperViewModel.class);
viewModel.getWallpapers().observe(getViewLifecycleOwner(), adapter::submitList);
}
@Override
public void onResume() {
super.onResume();
viewModel.refreshWallpaper();
}
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (requestCode == CHOOSE_WALLPAPER && resultCode == Activity.RESULT_OK && data != null) {
@ -67,4 +78,17 @@ public class ChatWallpaperSelectionFragment extends Fragment {
super.onActivityResult(requestCode, resultCode, data);
}
}
private void askForPermissionIfNeededAndLaunchPhotoSelection() {
Permissions.with(this)
.request(Manifest.permission.READ_EXTERNAL_STORAGE)
.ifNecessary()
.onAllGranted(() -> {
startActivityForResult(WallpaperImageSelectionActivity.getIntent(requireContext(), viewModel.getRecipientId()), CHOOSE_WALLPAPER);
ActivityTransitionUtil.setSlideInTransition(requireActivity());
})
.onAnyDenied(() -> Toast.makeText(requireContext(), R.string.ChatWallpaperPreviewActivity__viewing_your_gallery_requires_the_storage_permission, Toast.LENGTH_SHORT)
.show())
.execute();
}
}

View file

@ -9,6 +9,8 @@ import androidx.lifecycle.ViewModelProvider;
import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.MappingModel;
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
@ -19,10 +21,11 @@ import java.util.Objects;
public class ChatWallpaperViewModel extends ViewModel {
private final ChatWallpaperRepository repository = new ChatWallpaperRepository();
private final MutableLiveData<Optional<ChatWallpaper>> wallpaper = new MutableLiveData<>();
private final MutableLiveData<List<ChatWallpaper>> builtins = new MutableLiveData<>();
private final MutableLiveData<Boolean> dimInDarkTheme = new MutableLiveData<>();
private final ChatWallpaperRepository repository = new ChatWallpaperRepository();
private final MutableLiveData<Optional<ChatWallpaper>> wallpaper = new MutableLiveData<>();
private final MutableLiveData<List<ChatWallpaper>> builtins = new MutableLiveData<>();
private final MutableLiveData<Boolean> dimInDarkTheme = new MutableLiveData<>();
private final MutableLiveData<Boolean> enableWallpaperControls = new MutableLiveData<>();
private final RecipientId recipientId;
private ChatWallpaperViewModel(@Nullable RecipientId recipientId) {
@ -30,7 +33,11 @@ public class ChatWallpaperViewModel extends ViewModel {
ChatWallpaper currentWallpaper = repository.getCurrentWallpaper(recipientId);
dimInDarkTheme.setValue(currentWallpaper != null && currentWallpaper.getDimLevelForDarkTheme() > 0f);
enableWallpaperControls.setValue(hasClearableWallpaper());
wallpaper.setValue(Optional.fromNullable(currentWallpaper));
}
void refreshWallpaper() {
repository.getAllWallpaper(builtins::postValue);
}
@ -53,7 +60,18 @@ public class ChatWallpaperViewModel extends ViewModel {
if (!wallpaper.isPresent()) {
repository.saveWallpaper(recipientId, null);
if (recipientId != null) {
ChatWallpaper globalWallpaper = SignalStore.wallpaper().getWallpaper();
this.wallpaper.setValue(Optional.fromNullable(globalWallpaper));
this.dimInDarkTheme.setValue(globalWallpaper != null && globalWallpaper.getDimLevelForDarkTheme() > 0);
}
enableWallpaperControls.setValue(false);
return;
} else {
enableWallpaperControls.setValue(true);
}
Optional<ChatWallpaper> updated = wallpaper.transform(paper -> ChatWallpaperFactory.updateWithDimming(paper, dimInDarkTheme ? ChatWallpaper.FIXED_DIM_LEVEL_FOR_DARK_THEME : 0f));
@ -67,30 +85,39 @@ public class ChatWallpaperViewModel extends ViewModel {
repository.resetAllWallpaper();
}
public @Nullable RecipientId getRecipientId() {
@Nullable RecipientId getRecipientId() {
return recipientId;
}
LiveData<Optional<ChatWallpaper>> getCurrentWallpaper() {
@NonNull LiveData<Optional<ChatWallpaper>> getCurrentWallpaper() {
return wallpaper;
}
LiveData<List<MappingModel<?>>> getWallpapers() {
@NonNull LiveData<List<MappingModel<?>>> getWallpapers() {
return LiveDataUtil.combineLatest(builtins, dimInDarkTheme, (wallpapers, dimInDarkMode) ->
Stream.of(wallpapers)
.map(paper -> ChatWallpaperFactory.updateWithDimming(paper, dimInDarkMode ? 1f : 0f))
.map(paper -> ChatWallpaperFactory.updateWithDimming(paper, dimInDarkMode ? ChatWallpaper.FIXED_DIM_LEVEL_FOR_DARK_THEME : 0f))
.<MappingModel<?>>map(ChatWallpaperSelectionMappingModel::new).toList()
);
}
LiveData<Boolean> getDimInDarkTheme() {
@NonNull LiveData<Boolean> getDimInDarkTheme() {
return dimInDarkTheme;
}
@NonNull LiveData<Boolean> getEnableWallpaperControls() {
return enableWallpaperControls;
}
boolean isGlobal() {
return recipientId == null;
}
private boolean hasClearableWallpaper() {
return (isGlobal() && SignalStore.wallpaper().hasWallpaperSet()) ||
(recipientId != null && Recipient.live(recipientId).get().hasOwnWallpaper());
}
public static class Factory implements ViewModelProvider.Factory {
private final RecipientId recipientId;

View file

@ -22,6 +22,7 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@ -39,9 +40,9 @@ public final class WallpaperStorage {
* Saves the provided input stream as a new wallpaper file.
*/
@WorkerThread
public static @NonNull ChatWallpaper save(@NonNull Context context, @NonNull InputStream wallpaperStream) throws IOException {
public static @NonNull ChatWallpaper save(@NonNull Context context, @NonNull InputStream wallpaperStream, @NonNull String extension) throws IOException {
File directory = context.getDir(DIRECTORY, Context.MODE_PRIVATE);
File file = File.createTempFile(FILENAME_BASE, "", directory);
File file = File.createTempFile(FILENAME_BASE, "." + extension, directory);
StreamUtil.copy(wallpaperStream, getOutputStream(context, file));
@ -61,11 +62,15 @@ public final class WallpaperStorage {
File directory = context.getDir(DIRECTORY, Context.MODE_PRIVATE);
File[] allFiles = directory.listFiles(pathname -> pathname.getName().contains(FILENAME_BASE));
return Stream.of(allFiles)
.map(File::getName)
.map(PartAuthority::getWallpaperUri)
.map(ChatWallpaperFactory::create)
.toList();
if (allFiles != null) {
return Stream.of(allFiles)
.map(File::getName)
.map(PartAuthority::getWallpaperUri)
.map(ChatWallpaperFactory::create)
.toList();
} else {
return Collections.emptyList();
}
}
/**

View file

@ -17,6 +17,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.StyleRes;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.appcompat.widget.SwitchCompat;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
@ -62,6 +63,12 @@ public final class WallpaperCropActivity extends BaseActivity {
return intent;
}
@Override
protected void attachBaseContext(@NonNull Context newBase) {
getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES);
super.attachBaseContext(newBase);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

View file

@ -33,7 +33,7 @@ final class WallpaperCropRepository {
@WorkerThread
@NonNull ChatWallpaper setWallPaper(byte[] bytes) throws IOException {
try (InputStream inputStream = new ByteArrayInputStream(bytes)) {
ChatWallpaper wallpaper = WallpaperStorage.save(context, inputStream);
ChatWallpaper wallpaper = WallpaperStorage.save(context, inputStream, "webp");
if (recipientId != null) {
Log.i(TAG, "Setting image wallpaper for " + recipientId);

View file

@ -1,11 +1,13 @@
package org.thoughtcrime.securesms.wallpaper.crop;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresPermission;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
@ -23,6 +25,7 @@ public final class WallpaperImageSelectionActivity extends AppCompatActivity
private static final String EXTRA_RECIPIENT_ID = "RECIPIENT_ID";
private static final int CROP = 901;
@RequiresPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
public static Intent getIntent(@NonNull Context context,
@Nullable RecipientId recipientId)
{

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/signal_background_secondary" />
<corners
android:radius="50dp" />
</shape>

View file

@ -9,6 +9,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/signal_background_primary"
tools:layout_height="200dp">
<androidx.recyclerview.widget.RecyclerView

View file

@ -55,6 +55,65 @@
app:layout_constraintTop_toTopOf="@id/chat_wallpaper_preview_background"
app:layout_constraintVertical_chainStyle="packed" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/chat_wallpaper_preview_top_bar_navigation"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginStart="4dp"
android:layout_marginBottom="4dp"
app:layout_constraintBottom_toBottomOf="@id/chat_wallpaper_preview_top_bar"
app:layout_constraintStart_toStartOf="@id/chat_wallpaper_preview_top_bar"
app:srcCompat="@drawable/ic_arrow_left_24"
app:tint="@color/core_white" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/chat_wallpaper_preview_top_bar_portrait"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginStart="4dp"
android:layout_marginBottom="2dp"
app:layout_constraintBottom_toBottomOf="@id/chat_wallpaper_preview_top_bar"
app:layout_constraintStart_toEndOf="@id/chat_wallpaper_preview_top_bar_navigation"
app:srcCompat="@drawable/circle_tintable"
app:tint="@color/core_white" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_marginBottom="4dp"
android:text="@string/ChatWallpaperFragment__contact_name"
android:textAppearance="@style/Signal.Text.Body"
android:textColor="@color/core_white"
android:textSize="8sp"
app:layout_constraintBottom_toBottomOf="@id/chat_wallpaper_preview_top_bar"
app:layout_constraintEnd_toStartOf="@id/chat_wallpaper_preview_top_bar_video"
app:layout_constraintStart_toEndOf="@id/chat_wallpaper_preview_top_bar_portrait"
tools:ignore="SmallSp" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/chat_wallpaper_preview_top_bar_video"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
app:layout_constraintBottom_toBottomOf="@id/chat_wallpaper_preview_top_bar"
app:layout_constraintEnd_toStartOf="@id/chat_wallpaper_preview_top_bar_voice"
app:srcCompat="@drawable/ic_video_solid_24"
app:tint="@color/core_white" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/chat_wallpaper_preview_top_bar_voice"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginEnd="6dp"
android:layout_marginBottom="4dp"
app:layout_constraintBottom_toBottomOf="@id/chat_wallpaper_preview_top_bar"
app:layout_constraintEnd_toEndOf="@id/chat_wallpaper_preview_top_bar"
app:srcCompat="@drawable/ic_phone_right_solid_24"
app:tint="@color/core_white" />
<View
android:id="@+id/chat_wallpaper_preview_bottom_bar"
android:layout_width="156dp"
@ -64,6 +123,33 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<View
android:id="@+id/chat_wallpaper_preview_bottom_bar_input"
android:layout_width="0dp"
android:layout_height="14dp"
android:layout_marginStart="8dp"
android:background="@drawable/chat_wallpaper_preview_input"
app:layout_constraintBottom_toBottomOf="@id/chat_wallpaper_preview_bottom_bar"
app:layout_constraintEnd_toStartOf="@id/chat_wallpaper_preview_bottom_bar_plus"
app:layout_constraintStart_toStartOf="@id/chat_wallpaper_preview_bottom_bar"
app:layout_constraintTop_toTopOf="@id/chat_wallpaper_preview_bottom_bar" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/chat_wallpaper_preview_bottom_bar_plus"
android:layout_width="14dp"
android:layout_height="14dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:background="@drawable/circle_tintable"
android:padding="2dp"
app:backgroundTint="@color/signal_accent_primary"
app:layout_constraintBottom_toBottomOf="@id/chat_wallpaper_preview_bottom_bar"
app:layout_constraintEnd_toEndOf="@id/chat_wallpaper_preview_bottom_bar"
app:layout_constraintStart_toEndOf="@id/chat_wallpaper_preview_bottom_bar_input"
app:layout_constraintTop_toTopOf="@id/chat_wallpaper_preview_bottom_bar"
app:srcCompat="@drawable/ic_plus_24"
app:tint="@color/core_white" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/chat_wallpaper_preview_today"
android:layout_width="24dp"
@ -95,7 +181,7 @@
app:layout_constraintEnd_toEndOf="@id/chat_wallpaper_preview_background"
app:layout_constraintTop_toBottomOf="@id/chat_wallpaper_preview_top_bar"
app:srcCompat="@drawable/chat_wallpaper_preview_bubble_10"
app:tint="@color/signal_background_tertiary" />
app:tint="@color/signal_background_secondary" />
<View
android:layout_width="0dp"
@ -141,6 +227,8 @@
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="16dp"
android:visibility="gone"
tools:visibility="visible"
android:background="@color/signal_inverse_transparent_15"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@ -158,9 +246,11 @@
android:text="@string/ChatWallpaperFragment__clear_wallpaper"
android:textAppearance="@style/Signal.Text.Body"
android:textColor="@color/signal_text_primary"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/chat_wallpaper_divider" />
app:layout_constraintTop_toBottomOf="@id/chat_wallpaper_divider"
tools:visibility="visible" />
<TextView
android:id="@+id/chat_wallpaper_reset_all_wallpapers"

View file

@ -65,14 +65,15 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ChatWallpaperPreviewActivity__this_wallpaper_will_be_set_for_all_chats"
android:maxWidth="194dp"
android:text="@string/ChatWallpaperPreviewActivity__swipe_to_preview_more_wallpapers"
android:textAppearance="@style/Signal.Text.Body"
android:textColor="@color/core_white" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ChatWallpaperPreviewActivity__10_49_am"
android:text="@string/DateUtils_just_now"
android:textAppearance="@style/Signal.Text.Caption"
android:textColor="@color/transparent_white_80" />
@ -95,10 +96,12 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/preview_bubble_1">
<TextView
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/preview_bubble_2_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ChatWallpaperPreviewActivity__besides_those_you_manually_override"
android:maxWidth="194dp"
android:text="@string/ChatWallpaperPreviewActivity__set_wallpaper_for_all_chats"
android:textAppearance="@style/Signal.Text.Body"
android:textColor="@color/signal_text_primary" />
@ -107,7 +110,7 @@
android:layout_height="wrap_content"
android:layout_gravity="end"
android:drawablePadding="4dp"
android:text="@string/ChatWallpaperPreviewActivity__10_49_am"
android:text="@string/DateUtils_just_now"
android:textAppearance="@style/Signal.Text.Caption"
android:textColor="@color/signal_text_secondary"
app:drawableEndCompat="@drawable/ic_delivery_status_read"

View file

@ -8,14 +8,17 @@
android:background="@drawable/conversation_item_background"
android:focusable="true"
android:paddingStart="26dp"
android:paddingEnd="26dp">
android:paddingEnd="26dp"
android:clipToPadding="false"
android:clipChildren="false">
<LinearLayout
android:id="@+id/conversation_update_background"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginBottom="3dp"
android:layout_marginTop="2dp"
android:layout_marginBottom="2dp"
android:orientation="vertical"
android:gravity="center"
android:paddingTop="8dp"

View file

@ -2829,7 +2829,12 @@
<string name="ChatWallpaperFragment__set_wallpaper">Set wallpaper</string>
<string name="ChatWallpaperFragment__dark_theme_dims_wallpaper">Dark theme dims wallpaper</string>
<string name="ChatWallpaperFragment__clear_wallpaper">Clear wallpaper</string>
<string name="ChatWallpaperFragment__clear_wallpaper_question_mark">Clear wallpaper?</string>
<string name="ChatWallpaperFragment__reset_all_wallpapers">Reset all wallpapers</string>
<string name="ChatWallpaperFragment__reset_all_wallpapers_question_mark">Reset all wallpapers?</string>
<string name="ChatWallpaperFragment__contact_name">Contact name</string>
<string name="ChatWallpaperFragment__reset">Reset</string>
<string name="ChatWallpaperFragment__clear">Clear</string>
<!-- ChatWallpaperSelectionFragment -->
<string name="ChatWallpaperSelectionFragment__choose_from_photos">Choose from photos</string>
@ -2838,9 +2843,10 @@
<!-- ChatWallpaperPreviewActivity -->
<string name="ChatWallpaperPreviewActivity__preview">Preview</string>
<string name="ChatWallpaperPreviewActivity__set_wallpaper">Set wallpaper</string>
<string name="ChatWallpaperPreviewActivity__10_49_am">10:49 am</string>
<string name="ChatWallpaperPreviewActivity__this_wallpaper_will_be_set_for_all_chats">This wallpaper will be set for all chats</string>
<string name="ChatWallpaperPreviewActivity__besides_those_you_manually_override">Besides those you manually override</string>
<string name="ChatWallpaperPreviewActivity__swipe_to_preview_more_wallpapers">Swipe to preview more wallpapers</string>
<string name="ChatWallpaperPreviewActivity__set_wallpaper_for_all_chats">Set wallpaper for all chats</string>
<string name="ChatWallpaperPreviewActivity__set_wallpaper_for_s">Set wallpaper for %1$s</string>
<string name="ChatWallpaperPreviewActivity__viewing_your_gallery_requires_the_storage_permission">Viewing your gallery requires the storage permission.</string>
<!-- WallpaperImageSelectionActivity -->
<string name="WallpaperImageSelectionActivity__choose_wallpaper_image">Choose wallpaper image</string>