Payments.

Co-authored-by: Alan Evans <alan@signal.org>
Co-authored-by: Alex Hart <alex@signal.org>
Co-authored-by: Cody Henthorne <cody@signal.org>
This commit is contained in:
Android Team
2021-04-06 13:03:33 -03:00
committed by Alan Evans
parent c42023855b
commit fddba2906a
311 changed files with 18956 additions and 235 deletions

View File

@@ -8,6 +8,7 @@ import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageView;
@@ -119,11 +120,18 @@ public final class AvatarImageView extends AppCompatImageView {
* Shows self as the actual profile picture.
*/
public void setRecipient(@NonNull Recipient recipient) {
setRecipient(recipient, false);
}
/**
* Shows self as the actual profile picture.
*/
public void setRecipient(@NonNull Recipient recipient, boolean quickContactEnabled) {
if (recipient.isSelf()) {
setAvatar(GlideApp.with(this), null, false);
setAvatar(GlideApp.with(this), null, quickContactEnabled);
AvatarUtil.loadIconIntoImageView(recipient, this);
} else {
setAvatar(GlideApp.with(this), recipient, false);
setAvatar(GlideApp.with(this), recipient, quickContactEnabled);
}
}
@@ -205,8 +213,7 @@ public final class AvatarImageView extends AppCompatImageView {
}
});
} else {
super.setOnClickListener(listener);
setClickable(listener != null);
disableQuickContact();
}
}
@@ -227,6 +234,16 @@ public final class AvatarImageView extends AppCompatImageView {
.into(this);
}
public void setNonAvatarImageResource(@DrawableRes int imageResource) {
recipientContactPhoto = null;
setImageResource(imageResource);
}
public void disableQuickContact() {
super.setOnClickListener(listener);
setClickable(listener != null);
}
private static class RecipientContactPhoto {
private final @NonNull Recipient recipient;

View File

@@ -0,0 +1,64 @@
package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
import org.thoughtcrime.securesms.R;
public class PaymentPillStrip extends ConstraintLayout {
private FrameLayout buttonStart;
private FrameLayout buttonEnd;
public PaymentPillStrip(@NonNull Context context) {
super(context);
}
public PaymentPillStrip(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public PaymentPillStrip(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public PaymentPillStrip(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
buttonStart = findViewById(R.id.button_start_frame);
buttonEnd = findViewById(R.id.button_end_frame);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (buttonStart.getMeasuredWidth() > buttonEnd.getMinimumWidth()) {
buttonEnd.setMinimumWidth(buttonStart.getMeasuredWidth());
}
if (buttonEnd.getMeasuredWidth() > buttonStart.getMinimumWidth()) {
buttonStart.setMinimumWidth(buttonEnd.getMeasuredWidth());
}
if (buttonStart.getMeasuredHeight() > buttonEnd.getMinimumHeight()) {
buttonEnd.setMinimumHeight(buttonStart.getMeasuredHeight());
}
if (buttonEnd.getMeasuredHeight() > buttonStart.getMinimumHeight()) {
buttonStart.setMinimumHeight(buttonEnd.getMeasuredHeight());
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}

View File

@@ -0,0 +1,74 @@
package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.conversationlist.model.UnreadPayments;
/**
* Displays the data in a given UnreadPayments object in a banner.
*/
public class UnreadPaymentsView extends ConstraintLayout {
private TextView title;
private AvatarImageView avatar;
private Listener listener;
public UnreadPaymentsView(@NonNull Context context) {
super(context);
}
public UnreadPaymentsView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public UnreadPaymentsView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public UnreadPaymentsView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
title = findViewById(R.id.payment_notification_title);
avatar = findViewById(R.id.payment_notification_avatar);
View open = findViewById(R.id.payment_notification_touch_target);
View close = findViewById(R.id.payment_notification_close_touch_target);
open.setOnClickListener(v -> {
if (listener != null) listener.onOpenPaymentsNotificationClicked();
});
close.setOnClickListener(v -> {
if (listener != null) listener.onClosePaymentsNotificationClicked();
});
}
public void setListener(@NonNull Listener listener) {
this.listener = listener;
}
public void setUnreadPayments(@NonNull UnreadPayments unreadPayments) {
title.setText(unreadPayments.getDescription(getContext()));
avatar.setAvatar(unreadPayments.getRecipient());
avatar.setVisibility(unreadPayments.getRecipient() == null ? GONE : VISIBLE);
}
public interface Listener {
void onOpenPaymentsNotificationClicked();
void onClosePaymentsNotificationClicked();
}
}

View File

@@ -9,6 +9,12 @@ import org.thoughtcrime.securesms.util.MappingAdapter;
* Reusable adapter for generic settings list.
*/
public class BaseSettingsAdapter extends MappingAdapter {
public BaseSettingsAdapter() {
registerFactory(SettingHeader.Item.class, SettingHeader.ViewHolder::new, R.layout.base_settings_header_item);
registerFactory(SettingProgress.Item.class, SettingProgress.ViewHolder::new, R.layout.base_settings_progress_item);
}
public void configureSingleSelect(@NonNull SingleSelectSetting.SingleSelectSelectionChangedListener selectionChangedListener) {
registerFactory(SingleSelectSetting.Item.class,
new LayoutFactory<>(v -> new SingleSelectSetting.ViewHolder(v, selectionChangedListener), R.layout.single_select_item));

View File

@@ -1,8 +1,6 @@
package org.thoughtcrime.securesms.components.settings;
import android.view.View;
import android.widget.RadioButton;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -20,13 +18,11 @@ import java.util.Objects;
public class CustomizableSingleSelectSetting {
public interface CustomizableSingleSelectionListener extends SingleSelectSetting.SingleSelectSelectionChangedListener {
void onCustomizeClicked(@NonNull Item item);
void onCustomizeClicked(@Nullable Item item);
}
public static class ViewHolder extends MappingViewHolder<Item> {
private final TextView summaryText;
private final View customize;
private final RadioButton radio;
private final SingleSelectSetting.ViewHolder delegate;
private final Group customizeGroup;
private final CustomizableSingleSelectionListener selectionListener;
@@ -35,50 +31,34 @@ public class CustomizableSingleSelectSetting {
super(itemView);
this.selectionListener = selectionListener;
radio = findViewById(R.id.customizable_single_select_radio);
summaryText = findViewById(R.id.customizable_single_select_summary);
customize = findViewById(R.id.customizable_single_select_customize);
customizeGroup = findViewById(R.id.customizable_single_select_customize_group);
delegate = new SingleSelectSetting.ViewHolder(itemView, selectionListener) {
@Override
protected void setChecked(boolean checked) {
radio.setChecked(checked);
}
};
delegate = new SingleSelectSetting.ViewHolder(itemView, selectionListener);
}
@Override
public void bind(@NonNull Item model) {
delegate.bind(model.singleSelectItem);
customizeGroup.setVisibility(radio.isChecked() ? View.VISIBLE : View.GONE);
customizeGroup.setVisibility(model.singleSelectItem.isSelected() ? View.VISIBLE : View.GONE);
customize.setOnClickListener(v -> selectionListener.onCustomizeClicked(model));
if (model.getCustomValue() != null) {
summaryText.setText(model.getSummaryText());
}
}
}
public static class Item implements MappingModel<Item> {
private SingleSelectSetting.Item singleSelectItem;
private Object customValue;
private String summaryText;
private final SingleSelectSetting.Item singleSelectItem;
private final Object customValue;
public <T> Item(@NonNull T item, @Nullable String text, boolean isSelected, @Nullable Object customValue, @Nullable String summaryText) {
this.customValue = customValue;
this.summaryText = summaryText;
singleSelectItem = new SingleSelectSetting.Item(item, text, isSelected);
singleSelectItem = new SingleSelectSetting.Item(item, text, summaryText, isSelected);
}
public @Nullable Object getCustomValue() {
return customValue;
}
public @Nullable String getSummaryText() {
return summaryText;
}
@Override
public boolean areItemsTheSame(@NonNull Item newItem) {
return singleSelectItem.areItemsTheSame(newItem.singleSelectItem);
@@ -86,7 +66,7 @@ public class CustomizableSingleSelectSetting {
@Override
public boolean areContentsTheSame(@NonNull Item newItem) {
return singleSelectItem.areContentsTheSame(newItem.singleSelectItem) && Objects.equals(customValue, newItem.customValue) && Objects.equals(summaryText, newItem.summaryText);
return singleSelectItem.areContentsTheSame(newItem.singleSelectItem) && Objects.equals(customValue, newItem.customValue);
}
}
}

View File

@@ -0,0 +1,63 @@
package org.thoughtcrime.securesms.components.settings;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.MappingModel;
import org.thoughtcrime.securesms.util.MappingViewHolder;
import java.util.Objects;
/**
* Provide a default header {@link MappingModel} and {@link MappingViewHolder} for settings screens.
*/
public final class SettingHeader {
public static final class ViewHolder extends MappingViewHolder<Item> {
private final TextView headerText;
public ViewHolder(@NonNull View itemView) {
super(itemView);
this.headerText = findViewById(R.id.base_settings_header_item_text);
}
@Override
public void bind(@NonNull Item model) {
if (model.text != null) {
headerText.setText(model.text);
} else {
headerText.setText(model.textRes);
}
}
}
public static final class Item implements MappingModel<Item> {
private final int textRes;
private final String text;
public Item(String text) {
this.text = text;
this.textRes = 0;
}
public Item(@StringRes int textRes) {
this.text = null;
this.textRes = textRes;
}
@Override
public boolean areItemsTheSame(@NonNull Item newItem) {
return textRes == newItem.textRes && Objects.equals(text, newItem.text);
}
@Override
public boolean areContentsTheSame(@NonNull Item newItem) {
return areItemsTheSame(newItem);
}
}
}

View File

@@ -0,0 +1,46 @@
package org.thoughtcrime.securesms.components.settings;
import android.view.View;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.util.MappingModel;
import org.thoughtcrime.securesms.util.MappingViewHolder;
/**
* Simple progress indicator that can be used multiple times (if provided with different {@link Item#id}s).
*/
public final class SettingProgress {
public static final class ViewHolder extends MappingViewHolder<SettingProgress.Item> {
public ViewHolder(@NonNull View itemView) {
super(itemView);
}
@Override
public void bind(@NonNull SettingProgress.Item model) { }
}
public static final class Item implements MappingModel<SettingProgress.Item> {
private final int id;
public Item() {
this(0);
}
public Item(int id) {
this.id = id;
}
@Override
public boolean areItemsTheSame(@NonNull SettingProgress.Item newItem) {
return id == newItem.id;
}
@Override
public boolean areContentsTheSame(@NonNull SettingProgress.Item newItem) {
return areItemsTheSame(newItem);
}
}
}

View File

@@ -1,7 +1,10 @@
package org.thoughtcrime.securesms.components.settings;
import android.text.TextUtils;
import android.view.View;
import android.widget.CheckedTextView;
import android.widget.RadioButton;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -23,46 +26,62 @@ public class SingleSelectSetting {
public static class ViewHolder extends MappingViewHolder<Item> {
private final RadioButton radio;
protected final CheckedTextView text;
private final TextView summaryText;
protected final SingleSelectSelectionChangedListener selectionChangedListener;
public ViewHolder(@NonNull View itemView, @NonNull SingleSelectSelectionChangedListener selectionChangedListener) {
super(itemView);
this.selectionChangedListener = selectionChangedListener;
this.radio = findViewById(R.id.single_select_item_radio);
this.text = findViewById(R.id.single_select_item_text);
this.summaryText = findViewById(R.id.single_select_item_summary);
}
@Override
public void bind(@NonNull Item model) {
radio.setChecked(model.isSelected);
text.setText(model.text);
setChecked(model.isSelected);
if (!TextUtils.isEmpty(model.summaryText)) {
summaryText.setText(model.summaryText);
summaryText.setVisibility(View.VISIBLE);
} else {
summaryText.setVisibility(View.GONE);
}
itemView.setOnClickListener(v -> selectionChangedListener.onSelectionChanged(model.item));
}
protected void setChecked(boolean checked) {
text.setChecked(checked);
}
}
public static class Item implements MappingModel<Item> {
private final String text;
private final Object item;
private final String text;
private final String summaryText;
private final boolean isSelected;
public <T> Item(@NonNull T item, @Nullable String text, boolean isSelected) {
this.item = item;
this.text = text != null ? text : item.toString();
this.isSelected = isSelected;
}
public @NonNull String getText() {
return text;
public <T> Item(@NonNull T item, @Nullable String text, @Nullable String summaryText, boolean isSelected) {
this.item = item;
this.summaryText = summaryText;
this.text = text != null ? text : item.toString();
this.isSelected = isSelected;
}
public @NonNull Object getItem() {
return item;
}
public @Nullable String getText() {
return text;
}
public @Nullable String getSummaryText() {
return summaryText;
}
public boolean isSelected() {
return isSelected;
}
@Override
public boolean areItemsTheSame(@NonNull Item newItem) {
return item.equals(newItem.item);
@@ -70,7 +89,9 @@ public class SingleSelectSetting {
@Override
public boolean areContentsTheSame(@NonNull Item newItem) {
return Objects.equals(text, newItem.text) && isSelected == newItem.isSelected;
return Objects.equals(text, newItem.text) &&
Objects.equals(summaryText, newItem.summaryText) &&
isSelected == newItem.isSelected;
}
}
}