Finalize wallpaper UX.
Co-authored-by: Greyson Parrelli <greyson@signal.org> Co-authored-by: Alan Evans <alan@signal.org>
This commit is contained in:
parent
a8ad1e718e
commit
c244a98962
29 changed files with 455 additions and 90 deletions
|
@ -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) {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,21 +296,29 @@ 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()) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable ConversationMessage getItem(int position) {
|
||||
position = hasHeader() ? position - 1 : position;
|
||||
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
|
||||
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.setVisibility(viewModel.isGlobal() ? View.VISIBLE : View.GONE);
|
||||
});
|
||||
|
||||
resetAllWallpaper.setOnClickListener(unused -> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(() -> {
|
||||
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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
@ -23,6 +25,7 @@ public class ChatWallpaperViewModel extends ViewModel {
|
|||
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;
|
||||
|
|
|
@ -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));
|
||||
|
||||
if (allFiles != null) {
|
||||
return Stream.of(allFiles)
|
||||
.map(File::getName)
|
||||
.map(PartAuthority::getWallpaperUri)
|
||||
.map(ChatWallpaperFactory::create)
|
||||
.toList();
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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>
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Reference in a new issue