UI Improvements (#445)

UI Improvements
This commit is contained in:
Konrad Pozniak 2017-11-30 20:12:09 +01:00 committed by GitHub
parent 0077388c65
commit 41233a837b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
54 changed files with 1266 additions and 1042 deletions

View file

@ -312,9 +312,7 @@ public final class AccountActivity extends BaseActivity implements ActionButtonA
getSupportActionBar().setSubtitle(subtitle);
}
boolean useCustomTabs = PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean("customTabs", false);
LinkHelper.setClickableText(note, account.note, null, useCustomTabs, new LinkListener() {
LinkHelper.setClickableText(note, account.note, null, new LinkListener() {
@Override
public void onViewTag(String tag) {
Intent intent = new Intent(AccountActivity.this, ViewTagActivity.class);

View file

@ -78,7 +78,7 @@ import com.keylesspalace.tusky.adapter.MentionAutoCompleteAdapter;
import com.keylesspalace.tusky.db.TootDao;
import com.keylesspalace.tusky.db.TootEntity;
import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.entity.Media;
import com.keylesspalace.tusky.entity.Attachment;
import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.fragment.ComposeOptionsFragment;
import com.keylesspalace.tusky.network.ProgressRequestBody;
@ -1274,9 +1274,9 @@ public final class ComposeActivity extends BaseActivity
item.uploadRequest = mastodonApi.uploadMedia(body);
item.uploadRequest.enqueue(new Callback<Media>() {
item.uploadRequest.enqueue(new Callback<Attachment>() {
@Override
public void onResponse(@NonNull Call<Media> call, @NonNull retrofit2.Response<Media> response) {
public void onResponse(@NonNull Call<Attachment> call, @NonNull retrofit2.Response<Attachment> response) {
if (response.isSuccessful()) {
onUploadSuccess(item, response.body());
} else {
@ -1286,14 +1286,14 @@ public final class ComposeActivity extends BaseActivity
}
@Override
public void onFailure(@NonNull Call<Media> call, @NonNull Throwable t) {
public void onFailure(@NonNull Call<Attachment> call, @NonNull Throwable t) {
Log.d(TAG, "Upload request failed. " + t.getMessage());
onUploadFailure(item, call.isCanceled());
}
});
}
private void onUploadSuccess(final QueuedMedia item, Media media) {
private void onUploadSuccess(final QueuedMedia item, Attachment media) {
item.id = media.id;
item.preview.setProgress(-1);
item.readyStage = QueuedMedia.ReadyStage.UPLOADED;
@ -1460,7 +1460,7 @@ public final class ComposeActivity extends BaseActivity
ProgressImageView preview;
Uri uri;
String id;
Call<Media> uploadRequest;
Call<Attachment> uploadRequest;
URLSpan uploadUrl;
ReadyStage readyStage;
byte[] content;

View file

@ -20,6 +20,7 @@ import android.app.SearchableInfo;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.LinearLayoutManager;
@ -150,8 +151,7 @@ public class SearchActivity extends BaseActivity implements SearchView.OnQueryTe
}
searchView.setOnQueryTextListener(this);
searchView.setFocusable(false);
searchView.setFocusableInTouchMode(false);
searchView.requestFocus();
searchView.setMaxWidth(Integer.MAX_VALUE);
}
@ -160,7 +160,7 @@ public class SearchActivity extends BaseActivity implements SearchView.OnQueryTe
clearResults();
Callback<SearchResults> callback = new Callback<SearchResults>() {
@Override
public void onResponse(Call<SearchResults> call, Response<SearchResults> response) {
public void onResponse(@NonNull Call<SearchResults> call, @NonNull Response<SearchResults> response) {
if (response.isSuccessful()) {
SearchResults results = response.body();
if (results.accounts != null && results.accounts.length > 0 || results.hashtags != null && results.hashtags.length > 0) {
@ -175,7 +175,7 @@ public class SearchActivity extends BaseActivity implements SearchView.OnQueryTe
}
@Override
public void onFailure(Call<SearchResults> call, Throwable t) {
public void onFailure(@NonNull Call<SearchResults> call, @NonNull Throwable t) {
onSearchFailure();
}
};

View file

@ -19,6 +19,7 @@ import android.content.Context;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.RecyclerView;
@ -26,7 +27,6 @@ import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.StyleSpan;
import android.text.style.URLSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -38,13 +38,18 @@ import android.widget.ToggleButton;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Notification;
import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.interfaces.LinkListener;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.util.CustomEmojiHelper;
import com.keylesspalace.tusky.util.DateUtils;
import com.keylesspalace.tusky.util.LinkHelper;
import com.keylesspalace.tusky.view.RoundedTransformation;
import com.keylesspalace.tusky.viewdata.NotificationViewData;
import com.keylesspalace.tusky.viewdata.StatusViewData;
import com.squareup.picasso.Picasso;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class NotificationsAdapter extends RecyclerView.Adapter {
@ -126,13 +131,17 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
case FAVOURITE:
case REBLOG: {
StatusNotificationViewHolder holder = (StatusNotificationViewHolder) viewHolder;
holder.setMessage(type, concreteNotificaton.getAccount().getDisplayName(),
concreteNotificaton.getStatusViewData());
StatusViewData.Concrete statusViewData = concreteNotificaton.getStatusViewData();
holder.setDisplayName(statusViewData.getUserFullName());
holder.setUsername(statusViewData.getNickname());
holder.setCreatedAt(statusViewData.getCreatedAt());
holder.setMessage(concreteNotificaton, statusListener);
holder.setupButtons(notificationActionListener,
concreteNotificaton.getAccount().id,
concreteNotificaton.getId());
holder.setAvatars(concreteNotificaton.getStatusViewData().getAvatar(),
concreteNotificaton.getId());
concreteNotificaton.getAccount().avatar);
break;
}
case FOLLOW: {
@ -220,6 +229,9 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
void onViewAccount(String id);
void onViewStatusForNotificationId(String notificationId);
void onExpandedChange(boolean expanded, int position);
}
private static class FollowViewHolder extends RecyclerView.ViewHolder {
@ -270,30 +282,32 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
private static class StatusNotificationViewHolder extends RecyclerView.ViewHolder
implements View.OnClickListener, ToggleButton.OnCheckedChangeListener {
private final TextView message;
private final ImageView icon;
private final TextView displayName;
private final TextView username;
private final TextView timestampInfo;
private final TextView statusContent;
private final ViewGroup container;
private final ImageView statusAvatar;
private final ImageView notificationAvatar;
private final ViewGroup topBar;
private final View contentWarningBar;
private final TextView contentWarningDescriptionTextView;
private final ToggleButton contentWarningButton;
private String accountId;
private String notificationId;
private NotificationActionListener listener;
private NotificationActionListener notificationActionListener;
private StatusViewData.Concrete statusViewData;
StatusNotificationViewHolder(View itemView) {
super(itemView);
message = itemView.findViewById(R.id.notification_text);
icon = itemView.findViewById(R.id.notification_icon);
message = itemView.findViewById(R.id.notification_top_text);
displayName = itemView.findViewById(R.id.status_display_name);
username = itemView.findViewById(R.id.status_username);
timestampInfo = itemView.findViewById(R.id.status_timestamp_info);
statusContent = itemView.findViewById(R.id.notification_content);
container = itemView.findViewById(R.id.notification_container);
statusAvatar = itemView.findViewById(R.id.notification_status_avatar);
notificationAvatar = itemView.findViewById(R.id.notification_notification_avatar);
topBar = itemView.findViewById(R.id.notification_top_bar);
contentWarningBar = itemView.findViewById(R.id.notification_content_warning_bar);
contentWarningDescriptionTextView = itemView.findViewById(R.id.notification_content_warning_description);
contentWarningButton = itemView.findViewById(R.id.notification_content_warning_button);
@ -303,33 +317,77 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
notificationAvatar.setColorFilter(darkerFilter, PorterDuff.Mode.MULTIPLY);
container.setOnClickListener(this);
topBar.setOnClickListener(this);
message.setOnClickListener(this);
statusContent.setOnClickListener(this);
contentWarningButton.setOnCheckedChangeListener(this);
}
void setMessage(Notification.Type type, String displayName,
StatusViewData.Concrete status) {
this.statusViewData = status;
private void setDisplayName(String name) {
displayName.setText(name);
}
private void setUsername(String name) {
Context context = username.getContext();
String format = context.getString(R.string.status_username_format);
String usernameText = String.format(format, name);
username.setText(usernameText);
}
private void setCreatedAt(@Nullable Date createdAt) {
// This is the visible timestampInfo.
String readout;
/* This one is for screen-readers. Frequently, they would mispronounce timestamps like "17m"
* as 17 meters instead of minutes. */
CharSequence readoutAloud;
if (createdAt != null) {
long then = createdAt.getTime();
long now = new Date().getTime();
readout = DateUtils.getRelativeTimeSpanString(timestampInfo.getContext(), then, now);
readoutAloud = android.text.format.DateUtils.getRelativeTimeSpanString(then, now,
android.text.format.DateUtils.SECOND_IN_MILLIS,
android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE);
} else {
// unknown minutes~
readout = "?m";
readoutAloud = "? minutes";
}
timestampInfo.setText(readout);
timestampInfo.setContentDescription(readoutAloud);
}
void setMessage(NotificationViewData.Concrete notificationViewData, LinkListener listener) {
this.statusViewData = notificationViewData.getStatusViewData();
String displayName = notificationViewData.getAccount().getDisplayName();
Notification.Type type = notificationViewData.getType();
Context context = message.getContext();
String format;
Drawable icon;
switch (type) {
default:
case FAVOURITE: {
icon.setImageResource(R.drawable.ic_star_24dp);
icon.setColorFilter(ContextCompat.getColor(context,
R.color.status_favourite_button_marked_dark));
icon = ContextCompat.getDrawable(context, R.drawable.ic_star_24dp);
if (icon != null) {
icon.setColorFilter(ContextCompat.getColor(context,
R.color.status_favourite_button_marked_dark), PorterDuff.Mode.SRC_ATOP);
}
format = context.getString(R.string.notification_favourite_format);
break;
}
case REBLOG: {
icon.setImageResource(R.drawable.ic_repeat_24dp);
icon.setColorFilter(ContextCompat.getColor(context,
R.color.color_accent_dark));
icon = ContextCompat.getDrawable(context, R.drawable.ic_repeat_24dp);
if(icon != null) {
icon.setColorFilter(ContextCompat.getColor(context,
R.color.color_accent_dark), PorterDuff.Mode.SRC_ATOP);
}
format = context.getString(R.string.notification_reblog_format);
break;
}
}
message.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
String wholeMessage = String.format(format, displayName);
final SpannableStringBuilder str = new SpannableStringBuilder(wholeMessage);
str.setSpan(new StyleSpan(Typeface.BOLD), 0, displayName.length(),
@ -338,12 +396,12 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
boolean hasSpoiler = !TextUtils.isEmpty(statusViewData.getSpoilerText());
contentWarningBar.setVisibility(hasSpoiler ? View.VISIBLE : View.GONE);
setupContentAndSpoiler(false);
setupContentAndSpoiler(notificationViewData, listener);
}
void setupButtons(final NotificationActionListener listener, final String accountId,
final String notificationId) {
this.listener = listener;
this.notificationActionListener = listener;
this.accountId = accountId;
this.notificationId = notificationId;
}
@ -362,11 +420,11 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
}
if (notificationAvatarUrl == null || notificationAvatarUrl.isEmpty()) {
notificationAvatar.setVisibility(View.GONE);
notificationAvatar.setImageResource(R.drawable.avatar_default);
} else {
notificationAvatar.setVisibility(View.VISIBLE);
Picasso.with(context)
.load(notificationAvatarUrl)
.placeholder(R.drawable.avatar_default)
.fit()
.transform(new RoundedTransformation(7, 0))
.into(notificationAvatar);
@ -377,46 +435,48 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
public void onClick(View v) {
switch (v.getId()) {
case R.id.notification_container:
if (listener != null) listener.onViewStatusForNotificationId(notificationId);
case R.id.notification_content:
if (notificationActionListener != null) notificationActionListener.onViewStatusForNotificationId(notificationId);
break;
case R.id.notification_top_bar:
if (listener != null) listener.onViewAccount(accountId);
case R.id.notification_top_text:
if (notificationActionListener != null) notificationActionListener.onViewAccount(accountId);
break;
}
}
private void setupContentAndSpoiler(boolean shouldShowContentIfSpoiler) {
private void setupContentAndSpoiler(NotificationViewData.Concrete notificationViewData, final LinkListener listener) {
boolean shouldShowContentIfSpoiler = notificationViewData.isExpanded();
boolean hasSpoiler = !TextUtils.isEmpty(statusViewData.getSpoilerText());
CharSequence content;
if (!shouldShowContentIfSpoiler && hasSpoiler) {
if (statusViewData.getMentions() != null &&
statusViewData.getMentions().length > 0) {
// If there is a content warning and mentions we're alternating between
// showing mentions and showing full content. As mentions are plain text we
// have to construct URLSpans ourselves.
SpannableStringBuilder contentBuilder = new SpannableStringBuilder();
for (Status.Mention mention : statusViewData.getMentions()) {
int start = contentBuilder.length() > 0 ? contentBuilder.length() - 1 : 0;
contentBuilder.append('@');
contentBuilder.append(mention.username);
contentBuilder.append(' ');
contentBuilder.setSpan(new URLSpan(mention.url), start,
mention.username.length() + 1, 0);
}
content = contentBuilder;
} else {
content = null;
}
statusContent.setVisibility(View.GONE);
} else {
content = statusViewData.getContent();
statusContent.setVisibility(View.VISIBLE);
}
statusContent.setText(content);
contentWarningDescriptionTextView.setText(statusViewData.getSpoilerText());
Spanned content = statusViewData.getContent();
List<Status.Emoji> emojis = statusViewData.getEmojis();
Spanned emojifiedText = CustomEmojiHelper.emojifyText(content, emojis, statusContent);
LinkHelper.setClickableText(statusContent, emojifiedText, statusViewData.getMentions(), listener);
Spanned emojifiedContentWarning =
CustomEmojiHelper.emojifyString(statusViewData.getSpoilerText(), statusViewData.getEmojis(), contentWarningDescriptionTextView);
contentWarningDescriptionTextView.setText(emojifiedContentWarning);
}
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
setupContentAndSpoiler(isChecked);
if (getAdapterPosition() != RecyclerView.NO_POSITION) {
notificationActionListener.onExpandedChange(isChecked, getAdapterPosition());
}
if (isChecked) {
statusContent.setVisibility(View.VISIBLE);
} else {
statusContent.setVisibility(View.GONE);
}
}
}
}

View file

@ -1,21 +1,14 @@
package com.keylesspalace.tusky.adapter;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.preference.PreferenceManager;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.content.res.AppCompatResources;
import android.support.v7.widget.RecyclerView;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.ReplacementSpan;
import android.text.TextUtils;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.ImageButton;
@ -24,26 +17,24 @@ import android.widget.TextView;
import android.widget.ToggleButton;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Attachment;
import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.util.CustomEmojiHelper;
import com.keylesspalace.tusky.util.DateUtils;
import com.keylesspalace.tusky.util.HtmlUtils;
import com.keylesspalace.tusky.util.LinkHelper;
import com.keylesspalace.tusky.util.ThemeUtils;
import com.keylesspalace.tusky.view.RoundedTransformation;
import com.keylesspalace.tusky.viewdata.StatusViewData;
import com.squareup.picasso.Callback;
import com.squareup.picasso.Picasso;
import com.squareup.picasso.Target;
import com.varunest.sparkbutton.SparkButton;
import com.varunest.sparkbutton.SparkEventListener;
import java.lang.ref.WeakReference;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class StatusBaseViewHolder extends RecyclerView.ViewHolder {
abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
private View container;
private TextView displayName;
private TextView username;
@ -58,23 +49,26 @@ class StatusBaseViewHolder extends RecyclerView.ViewHolder {
private ImageView mediaPreview1;
private ImageView mediaPreview2;
private ImageView mediaPreview3;
private View sensitiveMediaWarning;
private ImageView mediaOverlay0;
private ImageView mediaOverlay1;
private ImageView mediaOverlay2;
private ImageView mediaOverlay3;
private TextView sensitiveMediaWarning;
private View sensitiveMediaShow;
private View videoIndicator;
private TextView mediaLabel;
private View contentWarningBar;
private TextView contentWarningDescription;
private ToggleButton contentWarningButton;
ImageView avatar;
TextView timestamp;
TextView timestampInfo;
StatusBaseViewHolder(View itemView) {
super(itemView);
container = itemView.findViewById(R.id.status_container);
displayName = itemView.findViewById(R.id.status_display_name);
username = itemView.findViewById(R.id.status_username);
timestamp = itemView.findViewById(R.id.status_timestamp);
timestampInfo = itemView.findViewById(R.id.status_timestamp_info);
content = itemView.findViewById(R.id.status_content);
avatar = itemView.findViewById(R.id.status_avatar);
replyButton = itemView.findViewById(R.id.status_reply);
@ -87,15 +81,20 @@ class StatusBaseViewHolder extends RecyclerView.ViewHolder {
mediaPreview1 = itemView.findViewById(R.id.status_media_preview_1);
mediaPreview2 = itemView.findViewById(R.id.status_media_preview_2);
mediaPreview3 = itemView.findViewById(R.id.status_media_preview_3);
mediaOverlay0 = itemView.findViewById(R.id.status_media_overlay_0);
mediaOverlay1 = itemView.findViewById(R.id.status_media_overlay_1);
mediaOverlay2 = itemView.findViewById(R.id.status_media_overlay_2);
mediaOverlay3 = itemView.findViewById(R.id.status_media_overlay_3);
sensitiveMediaWarning = itemView.findViewById(R.id.status_sensitive_media_warning);
sensitiveMediaShow = itemView.findViewById(R.id.status_sensitive_media_button);
videoIndicator = itemView.findViewById(R.id.status_video_indicator);
mediaLabel = itemView.findViewById(R.id.status_media_label);
contentWarningBar = itemView.findViewById(R.id.status_content_warning_bar);
contentWarningDescription = itemView.findViewById(R.id.status_content_warning_description);
contentWarningButton = itemView.findViewById(R.id.status_content_warning_button);
}
protected abstract int getMediaPreviewHeight(Context context);
private void setDisplayName(String name) {
displayName.setText(name);
}
@ -107,44 +106,11 @@ class StatusBaseViewHolder extends RecyclerView.ViewHolder {
username.setText(usernameText);
}
private Callback spanCallback = new Callback() {
@Override
public void onSuccess() {
content.invalidate();
}
@Override
public void onError() {
}
};
private void setContent(Spanned content, Status.Mention[] mentions, List<Status.Emoji> emojis,
StatusActionListener listener) {
Context context = this.content.getContext();
SpannableStringBuilder builder = new SpannableStringBuilder(content);
if (!emojis.isEmpty()) {
CharSequence text = builder.subSequence(0, builder.length());
for (Status.Emoji emoji : emojis) {
CharSequence pattern = new StringBuilder(":").append(emoji.getShortcode()).append(':');
Matcher matcher = Pattern.compile(pattern.toString()).matcher(text);
while (matcher.find()) {
// We keep a span as a Picasso target, because Picasso keeps weak reference to
// the target so an anonymous class would likely be garbage collected.
EmojiSpan span = new EmojiSpan(context);
span.setCallback(spanCallback);
builder.setSpan(span, matcher.start(), matcher.end(), 0);
Picasso.with(container.getContext())
.load(emoji.getUrl())
.into(span);
}
}
}
Spanned emojifiedText = CustomEmojiHelper.emojifyText(content, emojis, this.content);
/* Redirect URLSpan's in the status content to the listener for viewing tag pages and
* account pages. */
boolean useCustomTabs =
PreferenceManager.getDefaultSharedPreferences(context).getBoolean("customTabs", false);
LinkHelper.setClickableText(this.content, builder, mentions, useCustomTabs, listener);
LinkHelper.setClickableText(this.content, emojifiedText, mentions, listener);
}
void setAvatar(String url, @Nullable String rebloggedUrl) {
@ -160,7 +126,7 @@ class StatusBaseViewHolder extends RecyclerView.ViewHolder {
}
protected void setCreatedAt(@Nullable Date createdAt) {
// This is the visible timestamp.
// This is the visible timestampInfo.
String readout;
/* This one is for screen-readers. Frequently, they would mispronounce timestamps like "17m"
* as 17 meters instead of minutes. */
@ -168,7 +134,7 @@ class StatusBaseViewHolder extends RecyclerView.ViewHolder {
if (createdAt != null) {
long then = createdAt.getTime();
long now = new Date().getTime();
readout = DateUtils.getRelativeTimeSpanString(timestamp.getContext(), then, now);
readout = DateUtils.getRelativeTimeSpanString(timestampInfo.getContext(), then, now);
readoutAloud = android.text.format.DateUtils.getRelativeTimeSpanString(then, now,
android.text.format.DateUtils.SECOND_IN_MILLIS,
android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE);
@ -177,8 +143,18 @@ class StatusBaseViewHolder extends RecyclerView.ViewHolder {
readout = "?m";
readoutAloud = "? minutes";
}
timestamp.setText(readout);
timestamp.setContentDescription(readoutAloud);
timestampInfo.setText(readout);
timestampInfo.setContentDescription(readoutAloud);
}
private void setIsReply(boolean isReply) {
if(isReply) {
replyButton.setImageResource(R.drawable.ic_reply_all_24dp);
} else {
replyButton.setImageResource(R.drawable.ic_reply_24dp);
}
}
private void setReblogged(boolean reblogged) {
@ -214,11 +190,14 @@ class StatusBaseViewHolder extends RecyclerView.ViewHolder {
favouriteButton.setChecked(favourited);
}
private void setMediaPreviews(final Status.MediaAttachment[] attachments, boolean sensitive,
final StatusActionListener listener, boolean showingSensitive) {
private void setMediaPreviews(final Attachment[] attachments, boolean sensitive,
final StatusActionListener listener, boolean showingContent) {
final ImageView[] previews = {
mediaPreview0, mediaPreview1, mediaPreview2, mediaPreview3
};
final ImageView[] overlays = {
mediaOverlay0, mediaOverlay1, mediaOverlay2, mediaOverlay3
};
Context context = mediaPreview0.getContext();
int mediaPreviewUnloadedId =
@ -234,6 +213,13 @@ class StatusBaseViewHolder extends RecyclerView.ViewHolder {
for (int i = 0; i < n; i++) {
String previewUrl = attachments[i].previewUrl;
String description = attachments[i].description;
if(TextUtils.isEmpty(description)) {
previews[i].setContentDescription(context.getString(R.string.action_view_media));
} else {
previews[i].setContentDescription(description);
}
previews[i].setVisibility(View.VISIBLE);
@ -246,9 +232,11 @@ class StatusBaseViewHolder extends RecyclerView.ViewHolder {
.into(previews[i]);
}
final Status.MediaAttachment.Type type = attachments[i].type;
if (type == Status.MediaAttachment.Type.VIDEO | type == Status.MediaAttachment.Type.GIFV) {
videoIndicator.setVisibility(View.VISIBLE);
final Attachment.Type type = attachments[i].type;
if (type == Attachment.Type.VIDEO | type == Attachment.Type.GIFV) {
overlays[i].setVisibility(View.VISIBLE);
} else {
overlays[i].setVisibility(View.GONE);
}
if (urls[i] == null || urls[i].isEmpty()) {
@ -262,34 +250,56 @@ class StatusBaseViewHolder extends RecyclerView.ViewHolder {
}
});
}
if(n <= 2) {
previews[0].getLayoutParams().height = getMediaPreviewHeight(context)*2;
previews[1].getLayoutParams().height = getMediaPreviewHeight(context)*2;
} else {
previews[0].getLayoutParams().height = getMediaPreviewHeight(context);
previews[1].getLayoutParams().height = getMediaPreviewHeight(context);
previews[2].getLayoutParams().height = getMediaPreviewHeight(context);
previews[3].getLayoutParams().height = getMediaPreviewHeight(context);
}
}
SharedPreferences pm = PreferenceManager.getDefaultSharedPreferences(context);
Boolean isAlwayShowSensitive = pm.getBoolean("alwaysShowSensitiveMedia", false);
if (sensitive && (!isAlwayShowSensitive)) {
sensitiveMediaWarning.setVisibility(showingSensitive ? View.GONE : View.VISIBLE);
sensitiveMediaShow.setVisibility(showingSensitive ? View.VISIBLE : View.GONE);
sensitiveMediaShow.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (getAdapterPosition() != RecyclerView.NO_POSITION) {
listener.onContentHiddenChange(false, getAdapterPosition());
}
v.setVisibility(View.GONE);
sensitiveMediaWarning.setVisibility(View.VISIBLE);
}
});
sensitiveMediaWarning.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (getAdapterPosition() != RecyclerView.NO_POSITION) {
listener.onContentHiddenChange(true, getAdapterPosition());
}
v.setVisibility(View.GONE);
sensitiveMediaShow.setVisibility(View.VISIBLE);
}
});
String hiddenContentText;
if(sensitive) {
hiddenContentText = context.getString(R.string.status_sensitive_media_template,
context.getString(R.string.status_sensitive_media_title),
context.getString(R.string.status_sensitive_media_directions));
} else {
hiddenContentText = context.getString(R.string.status_sensitive_media_template,
context.getString(R.string.status_media_hidden_title),
context.getString(R.string.status_sensitive_media_directions));
}
sensitiveMediaWarning.setText(HtmlUtils.fromHtml(hiddenContentText));
sensitiveMediaWarning.setVisibility(showingContent ? View.GONE : View.VISIBLE);
sensitiveMediaShow.setVisibility(showingContent ? View.VISIBLE : View.GONE);
sensitiveMediaShow.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (getAdapterPosition() != RecyclerView.NO_POSITION) {
listener.onContentHiddenChange(false, getAdapterPosition());
}
v.setVisibility(View.GONE);
sensitiveMediaWarning.setVisibility(View.VISIBLE);
}
});
sensitiveMediaWarning.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (getAdapterPosition() != RecyclerView.NO_POSITION) {
listener.onContentHiddenChange(true, getAdapterPosition());
}
v.setVisibility(View.GONE);
sensitiveMediaShow.setVisibility(View.VISIBLE);
}
});
// Hide any of the placeholder previews beyond the ones set.
for (int i = n; i < Status.MAX_MEDIA_ATTACHMENTS; i++) {
previews[i].setVisibility(View.GONE);
@ -297,7 +307,7 @@ class StatusBaseViewHolder extends RecyclerView.ViewHolder {
}
@NonNull
private static String getLabelTypeText(Context context, Status.MediaAttachment.Type type) {
private static String getLabelTypeText(Context context, Attachment.Type type) {
switch (type) {
default:
case IMAGE:
@ -309,7 +319,7 @@ class StatusBaseViewHolder extends RecyclerView.ViewHolder {
}
@DrawableRes
private static int getLabelIcon(Status.MediaAttachment.Type type) {
private static int getLabelIcon(Attachment.Type type) {
switch (type) {
default:
case IMAGE:
@ -320,7 +330,7 @@ class StatusBaseViewHolder extends RecyclerView.ViewHolder {
}
}
private void setMediaLabel(Status.MediaAttachment[] attachments, boolean sensitive,
private void setMediaLabel(Attachment[] attachments, boolean sensitive,
final StatusActionListener listener) {
if (attachments.length == 0) {
mediaLabel.setVisibility(View.GONE);
@ -349,7 +359,7 @@ class StatusBaseViewHolder extends RecyclerView.ViewHolder {
for (int i = 0; i < n; i++) {
urls[i] = attachments[i].url;
}
final Status.MediaAttachment.Type type = attachments[0].type;
final Attachment.Type type = attachments[0].type;
mediaLabel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@ -363,14 +373,17 @@ class StatusBaseViewHolder extends RecyclerView.ViewHolder {
sensitiveMediaShow.setVisibility(View.GONE);
}
private void setSpoilerText(String spoilerText, final boolean expanded,
final StatusActionListener listener) {
contentWarningDescription.setText(spoilerText);
private void setSpoilerText(String spoilerText, List<Status.Emoji> emojis,
final boolean expanded, final StatusActionListener listener) {
CharSequence emojiSpoiler =
CustomEmojiHelper.emojifyString(spoilerText, emojis, contentWarningDescription);
contentWarningDescription.setText(emojiSpoiler);
contentWarningBar.setVisibility(View.VISIBLE);
contentWarningButton.setChecked(expanded);
contentWarningButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
contentWarningDescription.invalidate();
if (getAdapterPosition() != RecyclerView.NO_POSITION) {
listener.onExpandedChange(isChecked, getAdapterPosition());
}
@ -478,21 +491,19 @@ class StatusBaseViewHolder extends RecyclerView.ViewHolder {
setDisplayName(status.getUserFullName());
setUsername(status.getNickname());
setCreatedAt(status.getCreatedAt());
setIsReply(status.getInReplyToId() != null);
setContent(status.getContent(), status.getMentions(), status.getEmojis(), listener);
setAvatar(status.getAvatar(), status.getRebloggedAvatar());
setReblogged(status.isReblogged());
setFavourited(status.isFavourited());
Status.MediaAttachment[] attachments = status.getAttachments();
Attachment[] attachments = status.getAttachments();
boolean sensitive = status.isSensitive();
if (mediaPreviewEnabled) {
setMediaPreviews(attachments, sensitive, listener, status.isShowingSensitiveContent());
/* A status without attachments is sometimes still marked sensitive, so it's necessary
* to check both whether there are any attachments and if it's marked sensitive. */
if (!sensitive || attachments.length == 0) {
hideSensitiveMediaWarning();
}
setMediaPreviews(attachments, sensitive, listener, status.isShowingContent());
if (attachments.length == 0) {
videoIndicator.setVisibility(View.GONE);
hideSensitiveMediaWarning();
// videoIndicator.setVisibility(View.GONE);
}
// Hide the unused label.
mediaLabel.setVisibility(View.GONE);
@ -504,7 +515,7 @@ class StatusBaseViewHolder extends RecyclerView.ViewHolder {
mediaPreview2.setVisibility(View.GONE);
mediaPreview3.setVisibility(View.GONE);
hideSensitiveMediaWarning();
videoIndicator.setVisibility(View.GONE);
// videoIndicator.setVisibility(View.GONE);
}
setupButtons(listener, status.getSenderId());
@ -512,66 +523,9 @@ class StatusBaseViewHolder extends RecyclerView.ViewHolder {
if (status.getSpoilerText() == null || status.getSpoilerText().isEmpty()) {
hideSpoilerText();
} else {
setSpoilerText(status.getSpoilerText(), status.isExpanded(), listener);
setSpoilerText(status.getSpoilerText(), status.getEmojis(), status.isExpanded(), listener);
}
}
private static class EmojiSpan extends ReplacementSpan implements Target {
private @Nullable
Drawable imageDrawable;
private WeakReference<Callback> callbackWeakReference;
private Context context;
EmojiSpan(Context context) {
this.context = context.getApplicationContext();
}
public void setCallback(Callback callback) {
this.callbackWeakReference = new WeakReference<>(callback);
}
@Override
public int getSize(@NonNull Paint paint, CharSequence text, int start, int end,
@Nullable Paint.FontMetricsInt fm) {
return (int) (paint.getTextSize()*1.2);
}
@Override
public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x,
int top, int y, int bottom, @NonNull Paint paint) {
if (imageDrawable == null) return;
canvas.save();
int emojiSize = (int) (paint.getTextSize() * 1.1);
imageDrawable.setBounds(0, 0, emojiSize, emojiSize);
int transY = bottom - imageDrawable.getBounds().bottom;
transY -= paint.getFontMetricsInt().descent/2;
canvas.translate(x, transY);
imageDrawable.draw(canvas);
canvas.restore();
}
@Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
// I hope using resources from application context is okay
// It's probably better than keeping activity alive. My assumption is that resources are
// only needed to look up the density which is really unlikely to change with
// configuration
imageDrawable = new BitmapDrawable(context.getResources(), bitmap);
if (callbackWeakReference != null) {
Callback cb = callbackWeakReference.get();
if (cb != null) cb.onSuccess();
}
}
@Override
public void onBitmapFailed(Drawable errorDrawable) {
}
@Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
}
}
}

View file

@ -2,7 +2,6 @@ package com.keylesspalace.tusky.adapter;
import android.content.Context;
import android.os.Build;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
@ -19,7 +18,7 @@ import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Card;
import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.util.CustomTabURLSpan;
import com.keylesspalace.tusky.util.CustomURLSpan;
import com.keylesspalace.tusky.util.LinkHelper;
import com.keylesspalace.tusky.viewdata.StatusViewData;
import com.squareup.picasso.Picasso;
@ -30,7 +29,6 @@ import java.util.Date;
class StatusDetailedViewHolder extends StatusBaseViewHolder {
private TextView reblogs;
private TextView favourites;
private TextView application;
private LinearLayout cardView;
private LinearLayout cardInfo;
private ImageView cardImage;
@ -42,7 +40,6 @@ class StatusDetailedViewHolder extends StatusBaseViewHolder {
super(view);
reblogs = view.findViewById(R.id.status_reblogs);
favourites = view.findViewById(R.id.status_favourites);
application = view.findViewById(R.id.status_application);
cardView = view.findViewById(R.id.card_view);
cardInfo = view.findViewById(R.id.card_info);
cardImage = view.findViewById(R.id.card_image);
@ -51,36 +48,36 @@ class StatusDetailedViewHolder extends StatusBaseViewHolder {
cardUrl = view.findViewById(R.id.card_link);
}
@Override
protected int getMediaPreviewHeight(Context context) {
return context.getResources().getDimensionPixelSize(R.dimen.status_detail_media_preview_height);
}
@Override
protected void setCreatedAt(@Nullable Date createdAt) {
if (createdAt != null) {
DateFormat dateFormat = android.text.format.DateFormat.getMediumDateFormat(
timestamp.getContext());
timestamp.setText(dateFormat.format(createdAt));
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.SHORT);
timestampInfo.setText(dateFormat.format(createdAt));
} else {
timestamp.setText("");
timestampInfo.setText("");
}
}
private void setApplication(@Nullable Status.Application app) {
if (app == null) {
application.setText("");
} else if (app.website != null) {
URLSpan span;
Context context = application.getContext();
boolean useCustomTabs = PreferenceManager.getDefaultSharedPreferences(context)
.getBoolean("customTabs", true);
if (useCustomTabs) {
span = new CustomTabURLSpan(app.website);
if (app != null) {
timestampInfo.append("");
if (app.website != null) {
URLSpan span = new CustomURLSpan(app.website);
SpannableStringBuilder text = new SpannableStringBuilder(app.name);
text.setSpan(span, 0, app.name.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
timestampInfo.append(text);
timestampInfo.setMovementMethod(LinkMovementMethod.getInstance());
} else {
span = new URLSpan(app.website);
timestampInfo.append(app.name);
}
SpannableStringBuilder text = new SpannableStringBuilder(app.name);
text.setSpan(span, 0, app.name.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
application.setText(text);
application.setMovementMethod(LinkMovementMethod.getInstance());
} else {
application.setText(app.name);
}
}

View file

@ -31,14 +31,12 @@ import com.varunest.sparkbutton.helpers.Utils;
public class StatusViewHolder extends StatusBaseViewHolder {
private ImageView avatarReblog;
private View rebloggedBar;
private TextView rebloggedByDisplayName;
private TextView rebloggedBar;
StatusViewHolder(View itemView) {
super(itemView);
avatarReblog = itemView.findViewById(R.id.status_avatar_reblog);
rebloggedBar = itemView.findViewById(R.id.status_reblogged_bar);
rebloggedByDisplayName = itemView.findViewById(R.id.status_reblogged);
rebloggedBar = itemView.findViewById(R.id.status_reblogged);
}
@Override
@ -66,6 +64,11 @@ public class StatusViewHolder extends StatusBaseViewHolder {
}
}
@Override
protected int getMediaPreviewHeight(Context context) {
return context.getResources().getDimensionPixelSize(R.dimen.status_media_preview_height);
}
@Override
void setupWithStatus(StatusViewData.Concrete status, final StatusActionListener listener,
boolean mediaPreviewEnabled) {
@ -90,10 +93,10 @@ public class StatusViewHolder extends StatusBaseViewHolder {
}
private void setRebloggedByDisplayName(String name) {
Context context = rebloggedByDisplayName.getContext();
Context context = rebloggedBar.getContext();
String format = context.getString(R.string.status_boosted_format);
String boostedText = String.format(format, name);
rebloggedByDisplayName.setText(boostedText);
rebloggedBar.setText(boostedText);
rebloggedBar.setVisibility(View.VISIBLE);
}

View file

@ -22,7 +22,6 @@ import android.view.View;
import android.view.ViewGroup;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Card;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.viewdata.StatusViewData;
@ -38,8 +37,6 @@ public class ThreadAdapter extends RecyclerView.Adapter {
private boolean mediaPreviewEnabled;
private int detailedStatusPosition;
private Card detailedStatusCard;
public ThreadAdapter(StatusActionListener listener) {
this.statusActionListener = listener;
this.statuses = new ArrayList<>();
@ -155,4 +152,8 @@ public class ThreadAdapter extends RecyclerView.Adapter {
detailedStatusPosition = position;
}
}
public int getDetailedStatusPosition() {
return detailedStatusPosition;
}
}

View file

@ -0,0 +1,79 @@
/* Copyright 2017 Andrew Dawson
*
* This file is a part of Tusky.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.entity;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.annotations.JsonAdapter;
import com.google.gson.annotations.SerializedName;
public class Attachment {
public String id;
public String url;
@SerializedName("preview_url")
public String previewUrl;
@SerializedName("text_url")
public String textUrl;
public Type type;
public String description;
public static class Meta {
public MediaProperties original;
public MediaProperties small;
}
public static class MediaProperties {
public int width;
public int height;
public float aspect;
}
@JsonAdapter(MediaTypeDeserializer.class)
public enum Type {
@SerializedName("image")
IMAGE,
@SerializedName("gifv")
GIFV,
@SerializedName("video")
VIDEO,
@SerializedName("unknown")
UNKNOWN
}
static class MediaTypeDeserializer implements JsonDeserializer<Type> {
@Override
public Type deserialize(JsonElement json, java.lang.reflect.Type classOfT, JsonDeserializationContext context)
throws JsonParseException {
switch(json.toString()) {
case "\"image\"":
return Type.IMAGE;
case "\"gifv\"":
return Type.GIFV;
case "\"video\"":
return Type.VIDEO;
default:
return Type.UNKNOWN;
}
}
}
}

View file

@ -1,32 +0,0 @@
/* Copyright 2017 Andrew Dawson
*
* This file is a part of Tusky.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.entity;
import com.google.gson.annotations.SerializedName;
public class Media {
public String id;
public String type;
public String url;
@SerializedName("preview_url")
public String previewUrl;
@SerializedName("text_url")
public String textUrl;
}

View file

@ -17,10 +17,6 @@ package com.keylesspalace.tusky.entity;
import android.text.Spanned;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.annotations.SerializedName;
import java.util.Date;
@ -137,7 +133,7 @@ public class Status {
}
@SerializedName("media_attachments")
public MediaAttachment[] attachments;
public Attachment[] attachments;
public Mention[] mentions;
@ -159,48 +155,6 @@ public class Status {
return id != null ? id.hashCode() : 0;
}
public static class MediaAttachment {
@com.google.gson.annotations.JsonAdapter(MediaTypeDeserializer.class)
public enum Type {
@SerializedName("image")
IMAGE,
@SerializedName("gifv")
GIFV,
@SerializedName("video")
VIDEO,
UNKNOWN
}
public String url;
@SerializedName("preview_url")
public String previewUrl;
@SerializedName("text_url")
public String textUrl;
@SerializedName("remote_url")
public String remoteUrl;
public Type type;
static class MediaTypeDeserializer implements JsonDeserializer<Type> {
@Override
public Type deserialize(JsonElement json, java.lang.reflect.Type classOfT, JsonDeserializationContext context)
throws JsonParseException {
switch(json.toString()) {
case "\"image\"":
return Type.IMAGE;
case "\"gifv\"":
return Type.GIFV;
case "\"video\"":
return Type.VIDEO;
default:
return Type.UNKNOWN;
}
}
}
}
public static final class Mention {
public String id;

View file

@ -34,6 +34,7 @@ import com.keylesspalace.tusky.BaseActivity
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.ViewMediaActivity
import com.keylesspalace.tusky.ViewVideoActivity
import com.keylesspalace.tusky.entity.Attachment
import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.view.SquareImageView
@ -86,7 +87,7 @@ class AccountMediaFragment : BaseFragment() {
body?.let { fetched ->
statuses.addAll(0, fetched)
// flatMap requires iterable but I don't want to box each array into list
val result = mutableListOf<Status.MediaAttachment>()
val result = mutableListOf<Attachment>()
for (status in fetched) {
result.addAll(status.attachments)
}
@ -110,7 +111,7 @@ class AccountMediaFragment : BaseFragment() {
statuses.addAll(fetched)
Log.d(TAG, "now there are ${statuses.size} statuses")
// flatMap requires iterable but I don't want to box each array into list
val result = mutableListOf<Status.MediaAttachment>()
val result = mutableListOf<Attachment>()
for (status in fetched) {
result.addAll(status.attachments)
}
@ -190,12 +191,12 @@ class AccountMediaFragment : BaseFragment() {
}
}
private fun viewMedia(items: List<Status.MediaAttachment>, currentIndex: Int, view: View?) {
private fun viewMedia(items: List<Attachment>, currentIndex: Int, view: View?) {
val urls = items.map { it.url }.toTypedArray()
val type = items[currentIndex].type
when (type) {
Status.MediaAttachment.Type.IMAGE -> {
Attachment.Type.IMAGE -> {
val intent = Intent(context, ViewMediaActivity::class.java)
intent.putExtra("urls", urls)
intent.putExtra("urlIndex", currentIndex)
@ -208,12 +209,12 @@ class AccountMediaFragment : BaseFragment() {
startActivity(intent)
}
}
Status.MediaAttachment.Type.GIFV, Status.MediaAttachment.Type.VIDEO -> {
Attachment.Type.GIFV, Attachment.Type.VIDEO -> {
val intent = Intent(context, ViewVideoActivity::class.java)
intent.putExtra("url", urls[currentIndex])
startActivity(intent)
}
Status.MediaAttachment.Type.UNKNOWN, null -> {
Attachment.Type.UNKNOWN, null -> {
}/* Intentionally do nothing. This case is here is to handle when new attachment
* types are added to the API before code is added here to handle them. So, the
* best fallback is to just show the preview and ignore requests to view them. */
@ -229,16 +230,16 @@ class AccountMediaFragment : BaseFragment() {
var baseItemColor = Color.BLACK
private val items = mutableListOf<Status.MediaAttachment>()
private val items = mutableListOf<Attachment>()
private val itemBgBaseHSV = FloatArray(3)
private val random = Random()
fun addTop(newItems: List<Status.MediaAttachment>) {
fun addTop(newItems: List<Attachment>) {
items.addAll(0, newItems)
notifyItemRangeInserted(0, newItems.size)
}
fun addBottom(newItems: List<Status.MediaAttachment>) {
fun addBottom(newItems: List<Attachment>) {
if (newItems.isEmpty()) return
val oldLen = items.size

View file

@ -42,6 +42,7 @@ import com.keylesspalace.tusky.NotificationPullJobCreator;
import com.keylesspalace.tusky.adapter.FooterViewHolder;
import com.keylesspalace.tusky.adapter.NotificationsAdapter;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Attachment;
import com.keylesspalace.tusky.entity.Notification;
import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.interfaces.ActionButtonActivity;
@ -108,6 +109,7 @@ public class NotificationsFragment extends SFragment implements
private int bottomFetches;
private String bottomId;
private String topId;
private boolean alwaysShowSensitiveMedia;
// Each element is either a Notification for loading data or a Placeholder
private final PairedList<Either<Placeholder, Notification>, NotificationViewData> notifications
@ -116,7 +118,7 @@ public class NotificationsFragment extends SFragment implements
public NotificationViewData apply(Either<Placeholder, Notification> input) {
if (input.isRight()) {
Notification notification = input.getAsRight();
return ViewDataUtils.notificationToViewData(notification);
return ViewDataUtils.notificationToViewData(notification, alwaysShowSensitiveMedia);
} else {
return new NotificationViewData.Placeholder(false);
}
@ -155,6 +157,7 @@ public class NotificationsFragment extends SFragment implements
adapter = new NotificationsAdapter(this, this);
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(
getActivity());
alwaysShowSensitiveMedia = preferences.getBoolean("alwaysShowSensitiveMedia", false);
boolean mediaPreviewEnabled = preferences.getBoolean("mediaPreviewEnabled", true);
adapter.setMediaPreviewEnabled(mediaPreviewEnabled);
recyclerView.setAdapter(adapter);
@ -273,13 +276,19 @@ public class NotificationsFragment extends SFragment implements
if (status.reblog != null) {
status.reblog.reblogged = reblog;
}
// Java's type inference *eyeroll*
notifications.set(position,
Either.<Placeholder, Notification>right(notification));
adapter.updateItemWithNotify(position, notifications.getPairedItem(position), true);
NotificationViewData.Concrete viewdata = (NotificationViewData.Concrete)notifications.getPairedItem(position);
adapter.notifyItemChanged(position);
StatusViewData.Builder viewDataBuilder = new StatusViewData.Builder(viewdata.getStatusViewData());
viewDataBuilder.setReblogged(reblog);
NotificationViewData.Concrete newViewData = new NotificationViewData.Concrete(
viewdata.getType(), viewdata.getId(), viewdata.getAccount(),
viewDataBuilder.createStatusViewData(), viewdata.isExpanded());
notifications.setPairedItem(position, newViewData);
adapter.updateItemWithNotify(position, newViewData, true);
}
}
@ -305,12 +314,19 @@ public class NotificationsFragment extends SFragment implements
status.reblog.favourited = favourite;
}
notifications.set(position,
Either.<Placeholder, Notification>right(notification));
NotificationViewData.Concrete viewdata = (NotificationViewData.Concrete)notifications.getPairedItem(position);
adapter.updateItemWithNotify(position, notifications.getPairedItem(position), true);
StatusViewData.Builder viewDataBuilder = new StatusViewData.Builder(viewdata.getStatusViewData());
viewDataBuilder.setFavourited(favourite);
NotificationViewData.Concrete newViewData = new NotificationViewData.Concrete(
viewdata.getType(), viewdata.getId(), viewdata.getAccount(),
viewDataBuilder.createStatusViewData(), viewdata.isExpanded());
notifications.setPairedItem(position, newViewData);
adapter.updateItemWithNotify(position, newViewData, true);
adapter.notifyItemChanged(position);
}
}
@ -328,7 +344,7 @@ public class NotificationsFragment extends SFragment implements
}
@Override
public void onViewMedia(String[] urls, int urlIndex, Status.MediaAttachment.Type type,
public void onViewMedia(String[] urls, int urlIndex, Attachment.Type type,
View view) {
super.viewMedia(urls, urlIndex, type, view);
}
@ -354,7 +370,7 @@ public class NotificationsFragment extends SFragment implements
.setIsExpanded(expanded)
.createStatusViewData();
NotificationViewData notificationViewData = new NotificationViewData.Concrete(old.getType(),
old.getId(), old.getAccount(), statusViewData);
old.getId(), old.getAccount(), statusViewData, expanded);
notifications.setPairedItem(position, notificationViewData);
adapter.updateItemWithNotify(position, notificationViewData, false);
}
@ -368,7 +384,7 @@ public class NotificationsFragment extends SFragment implements
.setIsShowingSensitiveContent(isShowing)
.createStatusViewData();
NotificationViewData notificationViewData = new NotificationViewData.Concrete(old.getType(),
old.getId(), old.getAccount(), statusViewData);
old.getId(), old.getAccount(), statusViewData, old.isExpanded());
notifications.setPairedItem(position, notificationViewData);
adapter.updateItemWithNotify(position, notificationViewData, false);
}

View file

@ -40,6 +40,7 @@ import com.keylesspalace.tusky.ViewMediaActivity;
import com.keylesspalace.tusky.ViewTagActivity;
import com.keylesspalace.tusky.ViewThreadActivity;
import com.keylesspalace.tusky.ViewVideoActivity;
import com.keylesspalace.tusky.entity.Attachment;
import com.keylesspalace.tusky.entity.Relationship;
import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.interfaces.AdapterItemRemover;
@ -259,7 +260,7 @@ public abstract class SFragment extends BaseFragment implements AdapterItemRemov
popup.show();
}
protected void viewMedia(String[] urls, int urlIndex, Status.MediaAttachment.Type type,
protected void viewMedia(String[] urls, int urlIndex, Attachment.Type type,
@Nullable View view) {
switch (type) {
case IMAGE: {

View file

@ -40,6 +40,7 @@ import com.keylesspalace.tusky.BuildConfig;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.adapter.FooterViewHolder;
import com.keylesspalace.tusky.adapter.TimelineAdapter;
import com.keylesspalace.tusky.entity.Attachment;
import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.interfaces.ActionButtonActivity;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
@ -108,13 +109,16 @@ public class TimelineFragment extends SFragment implements
private String bottomId;
@Nullable
private String topId;
private boolean alwaysShowSensitiveMedia;
private PairedList<Either<Placeholder, Status>, StatusViewData> statuses =
new PairedList<>(new Function<Either<Placeholder, Status>, StatusViewData>() {
@Override
public StatusViewData apply(Either<Placeholder, Status> input) {
Status status = input.getAsRightOrNull();
if (status != null) {
return ViewDataUtils.statusToViewData(status);
return ViewDataUtils.statusToViewData(status, alwaysShowSensitiveMedia);
} else {
return new StatusViewData.Placeholder(false);
}
@ -150,7 +154,7 @@ public class TimelineFragment extends SFragment implements
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Bundle arguments = getArguments();
kind = Kind.valueOf(arguments.getString(KIND_ARG));
@ -179,6 +183,7 @@ public class TimelineFragment extends SFragment implements
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(
getActivity());
preferences.registerOnSharedPreferenceChangeListener(this);
alwaysShowSensitiveMedia = preferences.getBoolean("alwaysShowSensitiveMedia", false);
boolean mediaPreviewEnabled = preferences.getBoolean("mediaPreviewEnabled", true);
adapter.setMediaPreviewEnabled(mediaPreviewEnabled);
recyclerView.setAdapter(adapter);
@ -402,7 +407,7 @@ public class TimelineFragment extends SFragment implements
}
@Override
public void onViewMedia(String[] urls, int urlIndex, Status.MediaAttachment.Type type,
public void onViewMedia(String[] urls, int urlIndex, Attachment.Type type,
View view) {
super.viewMedia(urls, urlIndex, type, view);
}
@ -462,6 +467,10 @@ public class TimelineFragment extends SFragment implements
}
break;
}
case "alwaysShowSensitiveMedia": {
//it is ok if only newly loaded statuses are affected, no need to fully refresh
alwaysShowSensitiveMedia = sharedPreferences.getBoolean("alwaysShowSensitiveMedia", false);
}
}
}

View file

@ -15,6 +15,7 @@
package com.keylesspalace.tusky.fragment;
import android.arch.core.util.Function;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.drawable.Drawable;
@ -23,7 +24,6 @@ import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.DividerItemDecoration;
@ -37,6 +37,7 @@ import android.view.ViewGroup;
import com.keylesspalace.tusky.BuildConfig;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.adapter.ThreadAdapter;
import com.keylesspalace.tusky.entity.Attachment;
import com.keylesspalace.tusky.entity.Card;
import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.entity.StatusContext;
@ -66,11 +67,17 @@ public class ViewThreadFragment extends SFragment implements
private String thisThreadsStatusId;
private TimelineReceiver timelineReceiver;
private Card card;
private boolean alwaysShowSensitiveMedia;
private int statusIndex = 0;
private final PairedList<Status, StatusViewData.Concrete> statuses =
new PairedList<>(ViewDataUtils.statusMapper());
private PairedList<Status, StatusViewData.Concrete> statuses =
new PairedList<>(new Function<Status, StatusViewData.Concrete>() {
@Override
public StatusViewData.Concrete apply(Status input) {
return ViewDataUtils.statusToViewData(input, alwaysShowSensitiveMedia);
}
});
public static ViewThreadFragment newInstance(String id) {
Bundle arguments = new Bundle();
@ -82,7 +89,7 @@ public class ViewThreadFragment extends SFragment implements
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_view_thread, container, false);
@ -96,15 +103,19 @@ public class ViewThreadFragment extends SFragment implements
recyclerView.setLayoutManager(layoutManager);
DividerItemDecoration divider = new DividerItemDecoration(
context, layoutManager.getOrientation());
Drawable drawable = ThemeUtils.getDrawable(context, R.attr.status_divider_drawable,
Drawable dividerDrawable = ThemeUtils.getDrawable(context, R.attr.status_divider_drawable,
R.drawable.status_divider_dark);
divider.setDrawable(drawable);
divider.setDrawable(dividerDrawable);
recyclerView.addItemDecoration(divider);
Drawable threadLineDrawable = ThemeUtils.getDrawable(context, R.attr.conversation_thread_line_drawable,
R.drawable.conversation_thread_line_dark);
recyclerView.addItemDecoration(new ConversationLineItemDecoration(context,
ContextCompat.getDrawable(context, R.drawable.conversation_divider_dark)));
threadLineDrawable));
adapter = new ThreadAdapter(this);
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(
getActivity());
alwaysShowSensitiveMedia = preferences.getBoolean("alwaysShowSensitiveMedia", false);
boolean mediaPreviewEnabled = preferences.getBoolean("mediaPreviewEnabled", true);
adapter.setMediaPreviewEnabled(mediaPreviewEnabled);
recyclerView.setAdapter(adapter);
@ -157,10 +168,16 @@ public class ViewThreadFragment extends SFragment implements
if (status.reblog != null) {
status.reblog.reblogged = reblog;
}
// create new viewData as side effect
statuses.set(position, status);
adapter.setItem(position, statuses.getPairedItem(position), true);
StatusViewData.Concrete viewdata = statuses.getPairedItem(position);
StatusViewData.Builder viewDataBuilder = new StatusViewData.Builder((viewdata));
viewDataBuilder.setReblogged(reblog);
StatusViewData.Concrete newViewData = viewDataBuilder.createStatusViewData();
statuses.setPairedItem(position, newViewData);
adapter.setItem(position, newViewData, true);
}
}
@ -184,9 +201,16 @@ public class ViewThreadFragment extends SFragment implements
if (status.reblog != null) {
status.reblog.favourited = favourite;
}
// create new viewData as side effect
statuses.set(position, status);
adapter.setItem(position, statuses.getPairedItem(position), true);
StatusViewData.Concrete viewdata = statuses.getPairedItem(position);
StatusViewData.Builder viewDataBuilder = new StatusViewData.Builder((viewdata));
viewDataBuilder.setFavourited(favourite);
StatusViewData.Concrete newViewData = viewDataBuilder.createStatusViewData();
statuses.setPairedItem(position, newViewData);
adapter.setItem(position, newViewData, true);
}
}
@ -204,7 +228,7 @@ public class ViewThreadFragment extends SFragment implements
}
@Override
public void onViewMedia(String[] urls, int urlIndex, Status.MediaAttachment.Type type,
public void onViewMedia(String[] urls, int urlIndex, Attachment.Type type,
View view) {
super.viewMedia(urls, urlIndex, type, view);
}
@ -367,6 +391,7 @@ public class ViewThreadFragment extends SFragment implements
public void onClick(View v) {
sendThreadRequest(id);
sendStatusRequest(id);
sendCardRequest(id);
}
})
.show();
@ -392,6 +417,7 @@ public class ViewThreadFragment extends SFragment implements
.setCard(card)
.createStatusViewData();
}
statuses.setPairedItem(i, viewData);
adapter.addItem(i, viewData);
return i;
}
@ -432,6 +458,8 @@ public class ViewThreadFragment extends SFragment implements
viewData = new StatusViewData.Builder(viewData)
.setCard(card)
.createStatusViewData();
statuses.setPairedItem(statusIndex, viewData);
}
adapter.addItem(statusIndex, viewData);
}

View file

@ -17,14 +17,14 @@ package com.keylesspalace.tusky.interfaces;
import android.view.View;
import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.entity.Attachment;
public interface StatusActionListener extends LinkListener {
void onReply(int position);
void onReblog(final boolean reblog, final int position);
void onFavourite(final boolean favourite, final int position);
void onMore(View view, final int position);
void onViewMedia(String[] urls, int index, Status.MediaAttachment.Type type, View view);
void onViewMedia(String[] urls, int index, Attachment.Type type, View view);
void onViewThread(int position);
void onOpenReblog(int position);
void onExpandedChange(boolean expanded, int position);

View file

@ -20,8 +20,8 @@ import android.support.annotation.Nullable;
import com.keylesspalace.tusky.entity.AccessToken;
import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.entity.AppCredentials;
import com.keylesspalace.tusky.entity.Attachment;
import com.keylesspalace.tusky.entity.Card;
import com.keylesspalace.tusky.entity.Media;
import com.keylesspalace.tusky.entity.Notification;
import com.keylesspalace.tusky.entity.Profile;
import com.keylesspalace.tusky.entity.Relationship;
@ -80,7 +80,7 @@ public interface MastodonApi {
@Multipart
@POST("api/v1/media")
Call<Media> uploadMedia(@Part MultipartBody.Part file);
Call<Attachment> uploadMedia(@Part MultipartBody.Part file);
@FormUrlEncoded
@POST("api/v1/statuses")

View file

@ -0,0 +1,137 @@
/* Copyright 2017 Andrew Dawson
*
* This file is a part of Tusky.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.util;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.SpannedString;
import android.text.style.ReplacementSpan;
import android.widget.TextView;
import com.keylesspalace.tusky.entity.Status;
import com.squareup.picasso.Picasso;
import com.squareup.picasso.Target;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class CustomEmojiHelper {
/**
* replaces emoji shortcodes in a text with EmojiSpans
* @param text the text containing custom emojis
* @param emojis a list of the custom emojis
* @param textView a reference to the textView the emojis will be shown in
* @return the text with the shortcodes replaced by EmojiSpans
*/
public static Spanned emojifyText(Spanned text, List<Status.Emoji> emojis, final TextView textView) {
if (!emojis.isEmpty()) {
SpannableStringBuilder builder = new SpannableStringBuilder(text);
for (Status.Emoji emoji : emojis) {
CharSequence pattern = new StringBuilder(":").append(emoji.getShortcode()).append(':');
Matcher matcher = Pattern.compile(pattern.toString()).matcher(text);
while (matcher.find()) {
// We keep a span as a Picasso target, because Picasso keeps weak reference to
// the target so an anonymous class would likely be garbage collected.
EmojiSpan span = new EmojiSpan(textView);
builder.setSpan(span, matcher.start(), matcher.end(), 0);
Picasso.with(textView.getContext())
.load(emoji.getUrl())
.into(span);
}
}
return builder;
}
return text;
}
public static Spanned emojifyString(String string, List<Status.Emoji> emojis, final TextView textView) {
return emojifyText(new SpannedString(string), emojis, textView);
}
public static class EmojiSpan extends ReplacementSpan implements Target {
private @Nullable Drawable imageDrawable;
private WeakReference<TextView> textViewWeakReference;
EmojiSpan(TextView textView) {
this.textViewWeakReference = new WeakReference<>(textView);
}
@Override
public int getSize(@NonNull Paint paint, CharSequence text, int start, int end,
@Nullable Paint.FontMetricsInt fm) {
/* update FontMetricsInt or otherwise span does not get drawn when
it covers the whole text */
Paint.FontMetricsInt metrics = paint.getFontMetricsInt();
if (fm != null) {
fm.top = metrics.top;
fm.ascent = metrics.ascent;
fm.descent = metrics.descent;
fm.bottom = metrics.bottom;
}
return (int) (paint.getTextSize()*1.2);
}
@Override
public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x,
int top, int y, int bottom, @NonNull Paint paint) {
if (imageDrawable == null) return;
canvas.save();
int emojiSize = (int) (paint.getTextSize() * 1.1);
imageDrawable.setBounds(0, 0, emojiSize, emojiSize);
int transY = bottom - imageDrawable.getBounds().bottom;
transY -= paint.getFontMetricsInt().descent/2;
canvas.translate(x, transY);
imageDrawable.draw(canvas);
canvas.restore();
}
@Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
TextView textView = textViewWeakReference.get();
if(textView != null) {
imageDrawable = new BitmapDrawable(textView.getContext().getResources(), bitmap);
textView.invalidate();
}
}
@Override
public void onBitmapFailed(Drawable errorDrawable) {}
@Override
public void onPrepareLoad(Drawable placeHolderDrawable) {}
}
}

View file

@ -1,36 +0,0 @@
package com.keylesspalace.tusky.util;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.style.URLSpan;
import android.view.View;
public class CustomTabURLSpan extends URLSpan {
public CustomTabURLSpan(String url) {
super(url);
}
private CustomTabURLSpan(Parcel src) {
super(src);
}
public static final Parcelable.Creator<CustomTabURLSpan> CREATOR = new Parcelable.Creator<CustomTabURLSpan>() {
@Override
public CustomTabURLSpan createFromParcel(Parcel source) {
return new CustomTabURLSpan(source);
}
@Override
public CustomTabURLSpan[] newArray(int size) {
return new CustomTabURLSpan[size];
}
};
@Override
public void onClick(View view) {
Uri uri = Uri.parse(getURL());
LinkHelper.openLinkInCustomTab(uri, view.getContext());
}
}

View file

@ -0,0 +1,41 @@
package com.keylesspalace.tusky.util;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextPaint;
import android.text.style.URLSpan;
import android.view.View;
public class CustomURLSpan extends URLSpan {
public CustomURLSpan(String url) {
super(url);
}
private CustomURLSpan(Parcel src) {
super(src);
}
public static final Parcelable.Creator<CustomURLSpan> CREATOR = new Parcelable.Creator<CustomURLSpan>() {
@Override
public CustomURLSpan createFromParcel(Parcel source) {
return new CustomURLSpan(source);
}
@Override
public CustomURLSpan[] newArray(int size) {
return new CustomURLSpan[size];
}
};
@Override
public void onClick(View view) {
LinkHelper.openLink(getURL(), view.getContext());
}
@Override public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setUnderlineText(false);
}
}

View file

@ -26,6 +26,7 @@ import android.support.customtabs.CustomTabsIntent;
import android.support.v4.content.ContextCompat;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.text.style.URLSpan;
@ -63,12 +64,11 @@ public class LinkHelper {
* @param view the returned text will be put in
* @param content containing text with mentions, links, or hashtags
* @param mentions any '@' mentions which are known to be in the content
* @param useCustomTabs whether to use custom tabs when opening web links
* @param listener to notify about particular spans that are clicked
*/
public static void setClickableText(TextView view, Spanned content,
@Nullable Status.Mention[] mentions, boolean useCustomTabs,
final LinkListener listener) {
@Nullable Status.Mention[] mentions, final LinkListener listener) {
SpannableStringBuilder builder = new SpannableStringBuilder(content);
URLSpan[] urlSpans = content.getSpans(0, content.length(), URLSpan.class);
for (URLSpan span : urlSpans) {
@ -83,17 +83,21 @@ public class LinkHelper {
public void onClick(View widget) {
listener.onViewTag(tag);
}
@Override public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setUnderlineText(false);
}
};
builder.removeSpan(span);
builder.setSpan(newSpan, start, end, flags);
} else if (text.charAt(0) == '@' && mentions != null) {
} else if (text.charAt(0) == '@' && mentions != null && mentions.length > 0) {
String accountUsername = text.subSequence(1, text.length()).toString();
/* There may be multiple matches for users on different instances with the same
* username. If a match has the same domain we know it's for sure the same, but if
* that can't be found then just go with whichever one matched last. */
String id = null;
for (Status.Mention mention : mentions) {
if (mention.localUsername.equals(accountUsername)) {
if (mention.localUsername.equalsIgnoreCase(accountUsername)) {
id = mention.id;
if (mention.url.contains(getDomain(span.getURL()))) {
break;
@ -107,12 +111,16 @@ public class LinkHelper {
public void onClick(View widget) {
listener.onViewAccount(accountId);
}
@Override public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setUnderlineText(false);
}
};
builder.removeSpan(span);
builder.setSpan(newSpan, start, end, flags);
}
} else if (useCustomTabs) {
ClickableSpan newSpan = new CustomTabURLSpan(span.getURL());
} else {
ClickableSpan newSpan = new CustomURLSpan(span.getURL());
builder.removeSpan(span);
builder.setSpan(newSpan, start, end, flags);
}

View file

@ -85,9 +85,7 @@ public class OkHttpUtils {
@NonNull
public static OkHttpClient getCompatibleClient() {
OkHttpClient client = getCompatibleClientBuilder().build();
Log.d(TAG, client.connectTimeoutMillis()+" "+client.readTimeoutMillis()+" "+client.writeTimeoutMillis());
return client;
return getCompatibleClientBuilder().build();
}
/**

View file

@ -15,7 +15,6 @@
package com.keylesspalace.tusky.util;
import android.arch.core.util.Function;
import android.support.annotation.Nullable;
import com.keylesspalace.tusky.entity.Notification;
@ -23,16 +22,14 @@ import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.viewdata.NotificationViewData;
import com.keylesspalace.tusky.viewdata.StatusViewData;
import java.util.ArrayList;
import java.util.List;
/**
* Created by charlag on 12/07/2017.
*/
public final class ViewDataUtils {
@Nullable
public static StatusViewData.Concrete statusToViewData(@Nullable Status status) {
public static StatusViewData.Concrete statusToViewData(@Nullable Status status,
boolean alwaysShowSensitiveMedia) {
if (status == null) return null;
Status visibleStatus = status.reblog == null ? status : status.reblog;
return new StatusViewData.Builder().setId(status.id)
@ -51,6 +48,7 @@ public final class ViewDataUtils {
.setNickname(visibleStatus.account.username)
.setRebloggedAvatar(status.reblog == null ? null : status.account.avatar)
.setSensitive(visibleStatus.sensitive)
.setIsShowingSensitiveContent(alwaysShowSensitiveMedia || !visibleStatus.sensitive)
.setSpoilerText(visibleStatus.spoilerText)
.setRebloggedByUsername(status.reblog == null ? null : status.account.username)
.setUserFullName(visibleStatus.account.getDisplayName())
@ -62,37 +60,9 @@ public final class ViewDataUtils {
.createStatusViewData();
}
public static List<StatusViewData> statusListToViewDataList(List<Status> statuses) {
List<StatusViewData> viewDatas = new ArrayList<>(statuses.size());
for (Status s : statuses) {
viewDatas.add(statusToViewData(s));
}
return viewDatas;
}
public static Function<Status, StatusViewData.Concrete> statusMapper() {
return statusMapper;
}
public static NotificationViewData.Concrete notificationToViewData(Notification notification) {
public static NotificationViewData.Concrete notificationToViewData(Notification notification, boolean alwaysShowSensitiveData) {
return new NotificationViewData.Concrete(notification.type, notification.id, notification.account,
statusToViewData(notification.status));
statusToViewData(notification.status, alwaysShowSensitiveData), false);
}
public static List<NotificationViewData> notificationListToViewDataList(
List<Notification> notifications) {
List<NotificationViewData> viewDatas = new ArrayList<>(notifications.size());
for (Notification n : notifications) {
viewDatas.add(notificationToViewData(n));
}
return viewDatas;
}
private static final Function<Status, StatusViewData.Concrete> statusMapper =
new Function<Status, StatusViewData.Concrete>() {
@Override
public StatusViewData.Concrete apply(Status input) {
return ViewDataUtils.statusToViewData(input);
}
};
}

View file

@ -49,6 +49,7 @@ public class ConversationLineItemDecoration extends RecyclerView.ItemDecoration
int position = parent.getChildAdapterPosition(child);
ThreadAdapter adapter = (ThreadAdapter) parent.getAdapter();
StatusViewData.Concrete current = adapter.getItem(position);
int dividerTop, dividerBottom;
if (current != null) {
@ -59,25 +60,17 @@ public class ConversationLineItemDecoration extends RecyclerView.ItemDecoration
dividerTop = child.getTop() + avatarMargin;
}
StatusViewData.Concrete below = adapter.getItem(position + 1);
if (below != null && current.getId().equals(below.getInReplyToId())) {
if (below != null && current.getId().equals(below.getInReplyToId()) &&
adapter.getDetailedStatusPosition() != position) {
dividerBottom = child.getBottom();
} else {
dividerBottom = child.getTop() + avatarMargin;
}
} else {
dividerTop = child.getTop();
if (i == 0) {
dividerTop += avatarMargin;
}
if (i == childCount - 1) {
dividerBottom = child.getTop() + avatarMargin;
} else {
dividerBottom = child.getBottom();
}
}
divider.setBounds(dividerLeft, dividerTop, dividerRight, dividerBottom);
divider.draw(c);
divider.setBounds(dividerLeft, dividerTop, dividerRight, dividerBottom);
divider.draw(c);
}
}
}
}

View file

@ -38,13 +38,15 @@ public abstract class NotificationViewData {
private final String id;
private final Account account;
private final StatusViewData.Concrete statusViewData;
private final boolean isExpanded;
public Concrete(Notification.Type type, String id, Account account,
StatusViewData.Concrete statusViewData) {
StatusViewData.Concrete statusViewData, boolean isExpanded) {
this.type = type;
this.id = id;
this.account = account;
this.statusViewData = statusViewData;
this.isExpanded = isExpanded;
}
public Notification.Type getType() {
@ -62,6 +64,10 @@ public abstract class NotificationViewData {
public StatusViewData.Concrete getStatusViewData() {
return statusViewData;
}
public boolean isExpanded() {
return isExpanded;
}
}
public static final class Placeholder extends NotificationViewData {

View file

@ -18,6 +18,7 @@ package com.keylesspalace.tusky.viewdata;
import android.support.annotation.Nullable;
import android.text.Spanned;
import com.keylesspalace.tusky.entity.Attachment;
import com.keylesspalace.tusky.entity.Card;
import com.keylesspalace.tusky.entity.Status;
@ -45,14 +46,14 @@ public abstract class StatusViewData {
@Nullable
private final String spoilerText;
private final Status.Visibility visibility;
private final Status.MediaAttachment[] attachments;
private final Attachment[] attachments;
@Nullable
private final String rebloggedByUsername;
@Nullable
private final String rebloggedAvatar;
private final boolean isSensitive;
private final boolean isExpanded;
private final boolean isShowingSensitiveContent;
private final boolean isShowingContent;
private final String userFullName;
private final String nickname;
private final String avatar;
@ -72,9 +73,9 @@ public abstract class StatusViewData {
private final Card card;
public Concrete(String id, Spanned content, boolean reblogged, boolean favourited,
@Nullable String spoilerText, Status.Visibility visibility, Status.MediaAttachment[] attachments,
@Nullable String spoilerText, Status.Visibility visibility, Attachment[] attachments,
@Nullable String rebloggedByUsername, @Nullable String rebloggedAvatar, boolean sensitive, boolean isExpanded,
boolean isShowingSensitiveWarning, String userFullName, String nickname, String avatar,
boolean isShowingContent, String userFullName, String nickname, String avatar,
Date createdAt, String reblogsCount, String favouritesCount, @Nullable String inReplyToId,
@Nullable Status.Mention[] mentions, String senderId, boolean rebloggingEnabled,
Status.Application application, List<Status.Emoji> emojis, @Nullable Card card) {
@ -89,7 +90,7 @@ public abstract class StatusViewData {
this.rebloggedAvatar = rebloggedAvatar;
this.isSensitive = sensitive;
this.isExpanded = isExpanded;
this.isShowingSensitiveContent = isShowingSensitiveWarning;
this.isShowingContent = isShowingContent;
this.userFullName = userFullName;
this.nickname = nickname;
this.avatar = avatar;
@ -130,7 +131,7 @@ public abstract class StatusViewData {
return visibility;
}
public Status.MediaAttachment[] getAttachments() {
public Attachment[] getAttachments() {
return attachments;
}
@ -147,8 +148,8 @@ public abstract class StatusViewData {
return isExpanded;
}
public boolean isShowingSensitiveContent() {
return isShowingSensitiveContent;
public boolean isShowingContent() {
return isShowingContent;
}
@Nullable
@ -232,12 +233,12 @@ public abstract class StatusViewData {
private boolean favourited;
private String spoilerText;
private Status.Visibility visibility;
private Status.MediaAttachment[] attachments;
private Attachment[] attachments;
private String rebloggedByUsername;
private String rebloggedAvatar;
private boolean isSensitive;
private boolean isExpanded;
private boolean isShowingSensitiveContent;
private boolean isShowingContent;
private String userFullName;
private String nickname;
private String avatar;
@ -267,7 +268,7 @@ public abstract class StatusViewData {
rebloggedAvatar = viewData.rebloggedAvatar;
isSensitive = viewData.isSensitive;
isExpanded = viewData.isExpanded;
isShowingSensitiveContent = viewData.isShowingSensitiveContent;
isShowingContent = viewData.isShowingContent;
userFullName = viewData.userFullName;
nickname = viewData.nickname;
avatar = viewData.avatar;
@ -313,7 +314,7 @@ public abstract class StatusViewData {
return this;
}
public Builder setAttachments(Status.MediaAttachment[] attachments) {
public Builder setAttachments(Attachment[] attachments) {
this.attachments = attachments;
return this;
}
@ -339,7 +340,7 @@ public abstract class StatusViewData {
}
public Builder setIsShowingSensitiveContent(boolean isShowingSensitiveContent) {
this.isShowingSensitiveContent = isShowingSensitiveContent;
this.isShowingContent = isShowingSensitiveContent;
return this;
}
@ -414,7 +415,7 @@ public abstract class StatusViewData {
return new StatusViewData.Concrete(id, content, reblogged, favourited, spoilerText, visibility,
attachments, rebloggedByUsername, rebloggedAvatar, isSensitive, isExpanded,
isShowingSensitiveContent, userFullName, nickname, avatar, createdAt, reblogsCount,
isShowingContent, userFullName, nickname, avatar, createdAt, reblogsCount,
favouritesCount, inReplyToId, mentions, senderId, rebloggingEnabled, application,
emojis, card);
}

View file

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

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size android:width="4dp" />
<solid android:color="@color/status_divider_dark" />
</shape>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size android:width="4dp" />
<solid android:color="@color/status_divider_light" />
</shape>

View file

@ -1,9 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/status_favourite_button_dark"
android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z"/>
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/status_favourite_button_dark"
android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z"/>
</vector>

View file

@ -1,9 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/status_favourite_button_light"
android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z"/>
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/status_favourite_button_light"
android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z"/>
</vector>

View file

@ -4,6 +4,6 @@
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/toolbar_icon_dark"
android:fillColor="@color/color_accent_dark"
android:pathData="M15,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM6,10L6,7L4,7v3L1,10v2h3v3h2v-3h3v-2L6,10zM15,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
</vector>

View file

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="48dp"
android:width="48dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:autoMirrored="true">
<path android:fillColor="#FFF" android:pathData="M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M10,16.5L16,12L10,7.5V16.5Z" />
</vector>

View file

@ -0,0 +1,8 @@
<vector android:height="48dp" android:viewportHeight="24.0"
android:viewportWidth="24.0" android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillAlpha="1" android:fillColor="#1a1c23"
android:pathData="M21.282,12A9.282,9.282 0,0 1,12 21.282,9.282 9.282,0 0,1 2.718,12 9.282,9.282 0,0 1,12 2.718,9.282 9.282,0 0,1 21.282,12Z"
android:strokeAlpha="1" android:strokeColor="#00000000"
android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="8"/>
<path android:fillAlpha="1" android:fillColor="#2b90d9" android:pathData="M10,16.5l6,-4.5 -6,-4.5v9zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
</vector>

View file

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:autoMirrored="true"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillAlpha="1"
android:fillColor="#ffff"
android:pathData="M21.282,12A9.282,9.282 0,0 1,12 21.282,9.282 9.282,0 0,1 2.718,12 9.282,9.282 0,0 1,12 2.718,9.282 9.282,0 0,1 21.282,12Z" />
<path
android:fillAlpha="1"
android:fillColor="#2b90d9"
android:pathData="M10,16.5l6,-4.5 -6,-4.5v9zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z" />
</vector>

View file

@ -0,0 +1,4 @@
<vector android:height="18dp" android:viewportHeight="24.0"
android:viewportWidth="24.0" android:width="18dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#9baec8" android:pathData="M7,7h10v3l4,-4 -4,-4v3L5,5v6h2L7,7zM17,17L7,17v-3l-4,4 4,4v-3h12v-6h-2v4z"/>
</vector>

View file

@ -0,0 +1,4 @@
<vector android:height="18dp" android:viewportHeight="24.0"
android:viewportWidth="24.0" android:width="18dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#5f636f" android:pathData="M7,7h10v3l4,-4 -4,-4v3L5,5v6h2L7,7zM17,17L7,17v-3l-4,4 4,4v-3h12v-6h-2v4z"/>
</vector>

View file

@ -1,10 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
android:autoMirrored="true">
android:width="24dp"
android:height="24dp"
android:autoMirrored="true"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="@color/toolbar_icon_dark"
android:pathData="M10,9V5l-7,7 7,7v-4.1c5,0 8.5,1.6 11,5.1 -1,-5 -4,-10 -11,-11z"/>
android:pathData="M10,9V5l-7,7 7,7v-4.1c5,0 8.5,1.6 11,5.1 -1,-5 -4,-10 -11,-11z" />
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:autoMirrored="true"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="@color/toolbar_icon_dark"
android:pathData="M7,8L7,5l-7,7 7,7v-3l-4,-4 4,-4zM13,9L13,5l-7,7 7,7v-4.1c5,0 8.5,1.6 11,5.1 -1,-5 -4,-10 -11,-11z" />
</vector>

View file

@ -132,36 +132,36 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/account_note"
tools:text="3000 Followers"
android:background="@android:color/transparent"
android:textColor="@color/account_tab_font_color"
app:layout_constraintHorizontal_bias="0"/>
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/account_note"
tools:text="3000 Followers" />
<TextView
android:id="@+id/following_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/account_tab_font_color"
app:layout_constraintBottom_toBottomOf="@id/followers_tv"
app:layout_constraintEnd_toStartOf="@id/statuses_btn"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toEndOf="@id/followers_tv"
app:layout_constraintTop_toTopOf="@id/followers_tv"
tools:text="500 Following"
android:textColor="@color/account_tab_font_color" />
tools:text="500 Following" />
<TextView
android:id="@+id/statuses_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@id/following_tv"
android:textColor="@color/account_tab_font_color"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/followers_tv"
tools:text="3000 Posts"
app:layout_constraintHorizontal_bias="0"
android:textColor="@color/account_tab_font_color"/>
app:layout_constraintStart_toEndOf="@id/following_tv"
app:layout_constraintTop_toTopOf="@id/followers_tv"
tools:text="3000 Posts" />
</android.support.constraint.ConstraintLayout>
@ -192,7 +192,9 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:colorBackground"
app:tabSelectedTextColor="?attr/colorAccent" >
app:tabGravity="fill"
app:tabMaxWidth="0dp"
app:tabSelectedTextColor="?attr/colorAccent">
<android.support.design.widget.TabItem
android:layout_width="wrap_content"

View file

@ -89,7 +89,8 @@
android:ems="10"
android:gravity="start|top"
android:hint="@string/hint_compose"
android:inputType="text|textMultiLine|textCapSentences" />
android:inputType="text|textMultiLine|textCapSentences"
android:textSize="20sp" />
<HorizontalScrollView
android:layout_width="match_parent"

View file

@ -1,87 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
<?xml version="1.0" encoding="utf-8"?><!--
* This is the for follow notifications, the layout for the follows/following listings on account
* pages are instead in item_account.xml.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:orientation="vertical"
android:paddingBottom="10dp"
android:orientation="vertical">
android:paddingLeft="14dp"
android:paddingRight="14dp">
<RelativeLayout
android:layout_width="match_parent"
<TextView
android:id="@+id/notification_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:layout_marginTop="8dp"
android:layout_height="wrap_content">
android:drawableLeft="@drawable/ic_person_add_24dp"
android:drawablePadding="10dp"
android:drawableStart="@drawable/ic_person_add_24dp"
android:ellipsize="end"
android:gravity="center_vertical"
android:maxLines="1"
android:paddingLeft="28dp"
android:paddingStart="28dp"
android:textColor="?android:textColorTertiary"
tools:text="Someone followed you" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/follow_icon"
app:srcCompat="@drawable/ic_person_add_24dp"
android:paddingStart="24dp"
android:paddingLeft="24dp"
android:paddingEnd="10dp"
android:paddingRight="10dp"
android:tint="?attr/colorAccent"
android:contentDescription="@null" />
<ImageView
android:id="@+id/notification_avatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/notification_text"
android:layout_marginEnd="14dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="14dp"
android:layout_marginStart="8dp"
android:contentDescription="@string/action_view_profile"
android:scaleType="fitCenter" />
<TextView
android:id="@+id/notification_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?android:textColorTertiary"
android:layout_centerVertical="true"
android:maxLines="1"
android:ellipsize="end"
android:layout_toEndOf="@id/follow_icon"
android:layout_toRightOf="@id/follow_icon" />
<TextView
android:id="@+id/notification_display_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/notification_text"
android:layout_toEndOf="@id/notification_avatar"
android:layout_toRightOf="@id/notification_avatar"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:textColorPrimary"
android:textStyle="normal|bold"
tools:text="Test User" />
</RelativeLayout>
<TextView
android:id="@+id/notification_username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/notification_display_name"
android:layout_toEndOf="@id/notification_avatar"
android:layout_toRightOf="@id/notification_avatar"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:textColorSecondary"
tools:text="\@testuser" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_marginTop="4dp"
android:layout_height="wrap_content">
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:id="@+id/notification_avatar"
android:scaleType="fitCenter"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="10dp"
android:layout_marginRight="10dp"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:contentDescription="@string/action_view_profile" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_toEndOf="@id/notification_avatar"
android:layout_toRightOf="@id/notification_avatar">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/notification_display_name"
android:textColor="?android:textColorPrimary"
android:textStyle="normal|bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/notification_username"
android:textColor="?android:textColorSecondary" />
</LinearLayout>
</RelativeLayout>
</LinearLayout>
</RelativeLayout>

View file

@ -5,48 +5,31 @@
android:id="@+id/status_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="16dp"
android:paddingRight="16dp">
android:paddingLeft="14dp"
android:paddingRight="14dp">
<LinearLayout
android:id="@+id/status_reblogged_bar"
<TextView
android:id="@+id/status_reblogged"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_marginTop="@dimen/status_reblogged_bar_top_padding"
android:drawableLeft="?attr/status_reblog_small_drawable"
android:drawablePadding="6dp"
android:drawableStart="?attr/status_reblog_small_drawable"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/status_reblogged_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@null"
android:paddingEnd="10dp"
android:paddingLeft="24dp"
android:paddingRight="10dp"
android:paddingStart="24dp"
android:tint="?android:textColorTertiary"
app:srcCompat="@drawable/ic_repeat_24dp" />
<TextView
android:id="@+id/status_reblogged"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?android:textColorTertiary" />
</LinearLayout>
android:paddingLeft="38dp"
android:paddingStart="38dp"
android:textColor="?android:textColorTertiary"
tools:text="ConnyDuck boosted" />
<ImageView
android:id="@+id/status_avatar"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_below="@+id/status_reblogged_bar"
android:layout_marginEnd="10dp"
android:layout_marginRight="10dp"
android:layout_marginTop="11dp"
android:layout_below="@+id/status_reblogged"
android:layout_marginEnd="14dp"
android:layout_marginRight="14dp"
android:layout_marginTop="14dp"
android:contentDescription="@string/action_view_profile"
android:scaleType="fitCenter"
tools:src="@drawable/avatar_default" />
@ -67,54 +50,51 @@
android:id="@+id/status_name_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/status_reblogged_bar"
android:layout_below="@+id/status_reblogged"
android:layout_toEndOf="@+id/status_avatar"
android:layout_toRightOf="@+id/status_avatar"
android:paddingBottom="4dp"
android:paddingTop="@dimen/status_avatar_padding">
<TextView
android:id="@+id/status_timestamp"
android:id="@+id/status_display_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:ellipsize="end"
android:maxLines="1"
android:paddingEnd="@dimen/status_display_name_right_padding"
android:paddingLeft="0dp"
android:paddingRight="@dimen/status_display_name_right_padding"
android:paddingStart="0dp"
android:textColor="?android:textColorPrimary"
android:textStyle="normal|bold"
tools:text="Ente" />
<TextView
android:id="@+id/status_username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/status_display_name"
android:layout_toLeftOf="@+id/status_timestamp_info"
android:layout_toRightOf="@id/status_display_name"
android:layout_toStartOf="@+id/status_timestamp_info"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:textColorSecondary"
tools:text="\@Entenhausen" />
<TextView
android:id="@+id/status_timestamp_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginLeft="4dp"
android:layout_marginStart="4dp"
android:textColor="?android:textColorSecondary" />
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_toLeftOf="@id/status_timestamp"
android:layout_toStartOf="@id/status_timestamp"
android:orientation="horizontal">
<TextView
android:id="@+id/status_display_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:paddingEnd="@dimen/status_display_name_right_padding"
android:paddingLeft="0dp"
android:paddingRight="@dimen/status_display_name_right_padding"
android:paddingStart="0dp"
android:textAppearance="@android:style/TextAppearance.DeviceDefault.Small"
android:textColor="?android:textColorPrimary"
android:textStyle="normal|bold" />
<TextView
android:id="@+id/status_username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:textColorSecondary" />
</LinearLayout>
android:textColor="?android:textColorSecondary"
tools:text="13:37" />
</RelativeLayout>
@ -134,6 +114,7 @@
android:id="@+id/status_content_warning_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:lineSpacingMultiplier="1.1"
android:textColor="?android:textColorPrimary" />
<ToggleButton
@ -166,163 +147,177 @@
android:layout_toEndOf="@+id/status_avatar"
android:layout_toRightOf="@+id/status_avatar"
android:focusable="true"
android:textColor="?android:textColorPrimary" />
android:lineSpacingMultiplier="1.1"
android:textColor="?android:textColorPrimary"
tools:text="This is a status" />
<FrameLayout
<android.support.constraint.ConstraintLayout
android:id="@+id/status_media_preview_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/status_content"
android:layout_marginBottom="4dp"
android:layout_marginTop="@dimen/status_media_preview_top_margin"
android:layout_toEndOf="@+id/status_avatar"
android:layout_toRightOf="@+id/status_avatar">
<LinearLayout
android:layout_width="match_parent"
<ImageView
android:id="@+id/status_media_preview_0"
android:layout_width="0dp"
android:layout_height="@dimen/status_media_preview_height"
android:scaleType="centerCrop"
app:layout_constraintEnd_toStartOf="@+id/status_media_preview_1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/status_media_preview_1"
android:layout_width="0dp"
android:layout_height="@dimen/status_media_preview_height"
android:layout_marginLeft="4dp"
android:layout_marginStart="4dp"
android:scaleType="centerCrop"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/status_media_preview_0"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/status_media_preview_2"
android:layout_width="0dp"
android:layout_height="@dimen/status_media_preview_height"
android:layout_marginTop="4dp"
android:scaleType="centerCrop"
app:layout_constraintEnd_toStartOf="@+id/status_media_preview_3"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/status_media_preview_0"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/status_media_preview_3"
android:layout_width="0dp"
android:layout_height="@dimen/status_media_preview_height"
android:layout_marginLeft="4dp"
android:layout_marginStart="4dp"
android:layout_marginTop="4dp"
android:scaleType="centerCrop"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/status_media_preview_2"
app:layout_constraintTop_toBottomOf="@+id/status_media_preview_1"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/status_media_overlay_0"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="center"
app:layout_constraintBottom_toBottomOf="@+id/status_media_preview_0"
app:layout_constraintEnd_toEndOf="@+id/status_media_preview_0"
app:layout_constraintStart_toStartOf="@+id/status_media_preview_0"
app:layout_constraintTop_toTopOf="@+id/status_media_preview_0"
app:srcCompat="?attr/play_indicator_drawable"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/status_media_overlay_1"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="center"
app:layout_constraintBottom_toBottomOf="@+id/status_media_preview_1"
app:layout_constraintEnd_toEndOf="@+id/status_media_preview_1"
app:layout_constraintStart_toStartOf="@+id/status_media_preview_1"
app:layout_constraintTop_toTopOf="@+id/status_media_preview_1"
app:srcCompat="?attr/play_indicator_drawable"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/status_media_overlay_2"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="center"
app:layout_constraintBottom_toBottomOf="@+id/status_media_preview_2"
app:layout_constraintEnd_toEndOf="@+id/status_media_preview_2"
app:layout_constraintStart_toStartOf="@+id/status_media_preview_2"
app:layout_constraintTop_toTopOf="@+id/status_media_preview_2"
app:srcCompat="?attr/play_indicator_drawable"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/status_media_overlay_3"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="center"
app:layout_constraintBottom_toBottomOf="@+id/status_media_preview_3"
app:layout_constraintEnd_toEndOf="@+id/status_media_preview_3"
app:layout_constraintStart_toStartOf="@+id/status_media_preview_3"
app:layout_constraintTop_toTopOf="@+id/status_media_preview_3"
app:srcCompat="?attr/play_indicator_drawable"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/status_sensitive_media_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
android:alpha="0.7"
android:contentDescription="@null"
android:padding="@dimen/status_sensitive_media_button_padding"
android:visibility="gone"
app:layout_constraintLeft_toLeftOf="@+id/status_media_preview_container"
app:layout_constraintTop_toTopOf="@+id/status_media_preview_container"
app:srcCompat="@drawable/ic_remove_red_eye_black_24dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/status_media_preview_0"
android:layout_width="wrap_content"
android:layout_height="@dimen/status_media_preview_height"
android:layout_marginEnd="2dp"
android:layout_marginRight="2dp"
android:layout_marginTop="@dimen/status_media_preview_top_margin"
android:layout_weight="1"
android:contentDescription="@string/action_view_media"
android:scaleType="centerCrop" />
<ImageView
android:id="@+id/status_media_preview_1"
android:layout_width="wrap_content"
android:layout_height="@dimen/status_media_preview_height"
android:layout_marginLeft="2dp"
android:layout_marginStart="2dp"
android:layout_marginTop="@dimen/status_media_preview_top_margin"
android:layout_weight="1"
android:contentDescription="@string/action_view_media"
android:scaleType="centerCrop" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/status_media_preview_2"
android:layout_width="wrap_content"
android:layout_height="@dimen/status_media_preview_height"
android:layout_marginEnd="2dp"
android:layout_marginRight="2dp"
android:layout_weight="1"
android:contentDescription="@string/action_view_media"
android:scaleType="centerCrop" />
<ImageView
android:id="@+id/status_media_preview_3"
android:layout_width="wrap_content"
android:layout_height="@dimen/status_media_preview_height"
android:layout_marginLeft="2dp"
android:layout_marginStart="2dp"
android:layout_weight="1"
android:contentDescription="@string/action_view_media"
android:scaleType="centerCrop" />
</LinearLayout>
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/status_video_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:alpha="0.5"
android:contentDescription="@null"
android:visibility="gone"
app:srcCompat="@drawable/ic_play_48dp" />
<ImageView
android:id="@+id/status_sensitive_media_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:alpha="0.7"
android:contentDescription="@null"
android:padding="@dimen/status_sensitive_media_button_padding"
android:visibility="gone"
app:srcCompat="@drawable/ic_remove_red_eye_black_24dp" />
</RelativeLayout>
<LinearLayout
<TextView
android:id="@+id/status_sensitive_media_warning"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/status_media_preview_top_margin"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?attr/sensitive_media_warning_background_color"
android:gravity="center"
android:lineSpacingMultiplier="1.2"
android:orientation="vertical"
android:padding="8dp"
android:visibility="gone">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/status_sensitive_media_title"
android:textAlignment="center"
android:textColor="@android:color/white"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/status_sensitive_media_directions"
android:textAlignment="center"
android:textColor="@android:color/white" />
</LinearLayout>
android:textAlignment="center"
android:textColor="@android:color/white"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/status_media_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:drawablePadding="4dp"
android:gravity="center_vertical"
android:includeFontPadding="false"
android:visibility="gone" />
</FrameLayout>
</android.support.constraint.ConstraintLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/status_media_preview_container"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_toEndOf="@+id/status_avatar"
android:layout_toRightOf="@+id/status_avatar"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="horizontal"
android:paddingBottom="2dp">
android:paddingBottom="4dp">
<ImageButton
android:id="@+id/status_reply"
style="?attr/image_button_style"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_width="24dp"
android:layout_height="30dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/action_reply"
android:padding="4dp"
android:paddingLeft="8dp"
android:paddingRight="8dp"
app:srcCompat="@drawable/ic_reply_24dp" />
<Space
@ -332,14 +327,12 @@
<com.varunest.sparkbutton.SparkButton
android:id="@+id/status_reblog"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_width="24dp"
android:layout_height="30dp"
android:layout_gravity="center"
android:clipToPadding="false"
android:contentDescription="@string/action_reblog"
android:padding="4dp"
app:sparkbutton_activeImage="@drawable/reblog_active"
app:sparkbutton_iconSize="28dp"
app:sparkbutton_iconSize="24dp"
app:sparkbutton_inActiveImage="?attr/status_reblog_inactive_drawable"
app:sparkbutton_primaryColor="@color/status_reblog_button_marked_dark"
app:sparkbutton_secondaryColor="@color/status_reblog_button_marked_light" />
@ -351,14 +344,12 @@
<com.varunest.sparkbutton.SparkButton
android:id="@+id/status_favourite"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_width="24dp"
android:layout_height="30dp"
android:layout_gravity="center"
android:clipToPadding="false"
android:contentDescription="@string/action_favourite"
android:padding="4dp"
app:sparkbutton_activeImage="?attr/status_favourite_active_drawable"
app:sparkbutton_iconSize="28dp"
app:sparkbutton_iconSize="24dp"
app:sparkbutton_inActiveImage="?attr/status_favourite_inactive_drawable"
app:sparkbutton_primaryColor="@color/status_favourite_button_marked_dark"
app:sparkbutton_secondaryColor="@color/status_favourite_button_marked_light" />
@ -371,17 +362,14 @@
<ImageButton
android:id="@+id/status_more"
style="?attr/image_button_style"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_width="24dp"
android:layout_height="30dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/action_more"
android:padding="4dp"
android:paddingLeft="6dp"
android:paddingRight="6dp"
app:srcCompat="@drawable/ic_more_horiz_24dp" />
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
</RelativeLayout>

View file

@ -5,16 +5,16 @@
android:id="@+id/status_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="16dp"
android:paddingRight="16dp">
android:paddingLeft="14dp"
android:paddingRight="14dp">
<ImageView
android:id="@+id/status_avatar"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="10dp"
android:layout_marginRight="10dp"
android:layout_marginTop="11dp"
android:layout_marginEnd="14dp"
android:layout_marginRight="14dp"
android:layout_marginTop="14dp"
android:contentDescription="@string/action_view_profile"
android:scaleType="fitCenter"
tools:src="@drawable/avatar_default" />
@ -22,11 +22,12 @@
<LinearLayout
android:id="@+id/status_name_bar"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginTop="11dp"
android:layout_marginTop="14dp"
android:layout_toEndOf="@+id/status_avatar"
android:layout_toRightOf="@+id/status_avatar"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
@ -35,22 +36,19 @@
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="@android:style/TextAppearance.DeviceDefault.Small"
android:textColor="?android:textColorPrimary"
android:textStyle="normal|bold" />
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
android:textStyle="normal|bold"
tools:text="Display Name" />
<TextView
android:id="@+id/status_username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:textColorSecondary" />
android:textColor="?android:textColorSecondary"
tools:text="\@ConnyDuck\@mastodon.social" />
</LinearLayout>
@ -60,8 +58,6 @@
android:layout_height="wrap_content"
android:layout_below="@+id/status_name_bar"
android:layout_marginBottom="4dp"
android:layout_toEndOf="@+id/status_avatar"
android:layout_toRightOf="@+id/status_avatar"
android:focusable="true"
android:visibility="gone"
app:paddingHorizontal="4dp">
@ -70,7 +66,7 @@
android:id="@+id/status_content_warning_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium"
android:lineSpacingMultiplier="1.1"
android:textColor="?android:textColorPrimary" />
<ToggleButton
@ -94,14 +90,10 @@
android:id="@+id/status_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_below="@+id/status_content_warning_bar"
android:layout_marginBottom="4dp"
android:layout_toEndOf="@+id/status_avatar"
android:layout_toRightOf="@+id/status_avatar"
android:focusable="true"
android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium"
android:lineSpacingMultiplier="1.1"
android:textColor="?android:textColorPrimary" />
<LinearLayout
@ -110,8 +102,6 @@
android:layout_height="match_parent"
android:layout_below="@+id/status_content"
android:layout_marginTop="4dp"
android:layout_toEndOf="@+id/status_avatar"
android:layout_toRightOf="@+id/status_avatar"
android:background="?attr/card_background"
android:clipChildren="true"
android:orientation="vertical">
@ -161,184 +151,171 @@
android:textColor="?android:textColorTertiary" />
</LinearLayout>
</LinearLayout>
<FrameLayout
<android.support.constraint.ConstraintLayout
android:id="@+id/status_media_preview_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/card_view"
android:layout_toEndOf="@+id/status_avatar"
android:layout_toRightOf="@+id/status_avatar">
android:layout_marginBottom="4dp"
android:layout_marginTop="@dimen/status_media_preview_top_margin">
<LinearLayout
android:layout_width="match_parent"
<ImageView
android:id="@+id/status_media_preview_0"
android:layout_width="0dp"
android:layout_height="@dimen/status_media_preview_height"
android:scaleType="centerCrop"
app:layout_constraintEnd_toStartOf="@+id/status_media_preview_1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/status_media_preview_1"
android:layout_width="0dp"
android:layout_height="@dimen/status_media_preview_height"
android:layout_marginLeft="4dp"
android:layout_marginStart="4dp"
android:scaleType="centerCrop"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/status_media_preview_0"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/status_media_preview_2"
android:layout_width="0dp"
android:layout_height="@dimen/status_media_preview_height"
android:layout_marginTop="4dp"
android:scaleType="centerCrop"
app:layout_constraintEnd_toStartOf="@+id/status_media_preview_3"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/status_media_preview_0"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/status_media_preview_3"
android:layout_width="0dp"
android:layout_height="@dimen/status_media_preview_height"
android:layout_marginLeft="4dp"
android:layout_marginStart="4dp"
android:layout_marginTop="4dp"
android:scaleType="centerCrop"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/status_media_preview_2"
app:layout_constraintTop_toBottomOf="@+id/status_media_preview_1"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/status_media_overlay_0"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="center"
app:layout_constraintBottom_toBottomOf="@+id/status_media_preview_0"
app:layout_constraintEnd_toEndOf="@+id/status_media_preview_0"
app:layout_constraintStart_toStartOf="@+id/status_media_preview_0"
app:layout_constraintTop_toTopOf="@+id/status_media_preview_0"
app:srcCompat="?attr/play_indicator_drawable"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/status_media_overlay_1"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="center"
app:layout_constraintBottom_toBottomOf="@+id/status_media_preview_1"
app:layout_constraintEnd_toEndOf="@+id/status_media_preview_1"
app:layout_constraintStart_toStartOf="@+id/status_media_preview_1"
app:layout_constraintTop_toTopOf="@+id/status_media_preview_1"
app:srcCompat="?attr/play_indicator_drawable"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/status_media_overlay_2"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="center"
app:layout_constraintBottom_toBottomOf="@+id/status_media_preview_2"
app:layout_constraintEnd_toEndOf="@+id/status_media_preview_2"
app:layout_constraintStart_toStartOf="@+id/status_media_preview_2"
app:layout_constraintTop_toTopOf="@+id/status_media_preview_2"
app:srcCompat="?attr/play_indicator_drawable"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/status_media_overlay_3"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="center"
app:layout_constraintBottom_toBottomOf="@+id/status_media_preview_3"
app:layout_constraintEnd_toEndOf="@+id/status_media_preview_3"
app:layout_constraintStart_toStartOf="@+id/status_media_preview_3"
app:layout_constraintTop_toTopOf="@+id/status_media_preview_3"
app:srcCompat="?attr/play_indicator_drawable"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/status_sensitive_media_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
android:alpha="0.7"
android:contentDescription="@string/action_hide_media"
android:padding="@dimen/status_sensitive_media_button_padding"
android:visibility="gone"
app:layout_constraintLeft_toLeftOf="@+id/status_media_preview_container"
app:layout_constraintTop_toTopOf="@+id/status_media_preview_container"
app:srcCompat="@drawable/ic_remove_red_eye_black_24dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/status_media_preview_0"
android:layout_width="wrap_content"
android:layout_height="@dimen/status_media_preview_height"
android:layout_marginEnd="2dp"
android:layout_marginRight="2dp"
android:layout_marginTop="@dimen/status_media_preview_top_margin"
android:layout_weight="1"
android:contentDescription="@string/action_view_media"
android:scaleType="centerCrop" />
<ImageView
android:id="@+id/status_media_preview_1"
android:layout_width="wrap_content"
android:layout_height="@dimen/status_media_preview_height"
android:layout_marginLeft="2dp"
android:layout_marginStart="2dp"
android:layout_marginTop="@dimen/status_media_preview_top_margin"
android:layout_weight="1"
android:contentDescription="@string/action_view_media"
android:scaleType="centerCrop" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/status_media_preview_2"
android:layout_width="wrap_content"
android:layout_height="@dimen/status_media_preview_height"
android:layout_marginEnd="2dp"
android:layout_marginRight="2dp"
android:layout_weight="1"
android:contentDescription="@string/action_view_media"
android:scaleType="centerCrop" />
<ImageView
android:id="@+id/status_media_preview_3"
android:layout_width="wrap_content"
android:layout_height="@dimen/status_media_preview_height"
android:layout_marginLeft="2dp"
android:layout_marginStart="2dp"
android:layout_weight="1"
android:contentDescription="@string/action_view_media"
android:scaleType="centerCrop" />
</LinearLayout>
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/status_video_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:alpha="0.5"
android:contentDescription="@null"
android:visibility="gone"
app:srcCompat="@drawable/ic_play_48dp" />
<ImageView
android:id="@+id/status_sensitive_media_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:alpha="0.7"
android:contentDescription="@null"
android:padding="@dimen/status_sensitive_media_button_padding"
android:visibility="gone"
app:srcCompat="@drawable/ic_remove_red_eye_black_24dp" />
</RelativeLayout>
<LinearLayout
<TextView
android:id="@+id/status_sensitive_media_warning"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/status_media_preview_top_margin"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?attr/sensitive_media_warning_background_color"
android:gravity="center"
android:lineSpacingMultiplier="1.2"
android:orientation="vertical"
android:padding="8dp"
android:visibility="gone">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/status_sensitive_media_title"
android:textAlignment="center"
android:textColor="@android:color/white"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/status_sensitive_media_directions"
android:textAlignment="center"
android:textColor="@android:color/white" />
</LinearLayout>
android:textAlignment="center"
android:textColor="@android:color/white"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/status_media_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:drawablePadding="4dp"
android:gravity="center_vertical"
android:includeFontPadding="false"
android:visibility="gone" />
</FrameLayout>
</android.support.constraint.ConstraintLayout>
<LinearLayout
android:id="@+id/status_info_bar"
android:layout_width="match_parent"
<TextView
android:id="@+id/status_timestamp_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/status_media_preview_container"
android:layout_marginBottom="4dp"
android:layout_marginTop="8dp"
android:layout_toEndOf="@id/status_avatar"
android:layout_toRightOf="@id/status_avatar"
android:orientation="horizontal">
<TextView
android:id="@+id/status_timestamp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?android:textColorTertiary" />
<TextView
android:id="@+id/status_application"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp" />
</LinearLayout>
android:layout_marginBottom="6dp"
android:layout_marginTop="10dp"
android:textColor="?android:textColorTertiary" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/status_info_bar"
android:layout_toEndOf="@id/status_avatar"
android:layout_toRightOf="@id/status_avatar"
android:layout_below="@id/status_timestamp_info"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:clipChildren="false"
android:clipToPadding="false"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingBottom="2dp">
android:paddingBottom="4dp"
android:paddingTop="4dp">
<ImageButton
android:id="@+id/status_reply"
@ -409,11 +386,6 @@
android:padding="4dp"
app:srcCompat="@drawable/ic_more_horiz_24dp" />
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
</RelativeLayout>

View file

@ -6,37 +6,74 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="16dp"
android:paddingRight="16dp">
android:paddingLeft="14dp"
android:paddingRight="14dp">
<TextView
android:id="@+id/notification_top_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginTop="8dp"
android:drawablePadding="10dp"
android:ellipsize="end"
android:gravity="center_vertical"
android:maxLines="1"
android:paddingLeft="28dp"
android:paddingStart="28dp"
android:textColor="?android:textColorSecondary"
tools:text="Someone favourited your status" />
<RelativeLayout
android:id="@+id/notification_top_bar"
android:id="@+id/status_name_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp">
<ImageView
android:id="@+id/notification_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@null"
android:paddingEnd="10dp"
android:paddingLeft="24dp"
android:paddingRight="10dp"
android:paddingStart="24dp"
app:srcCompat="@drawable/ic_repeat_24dp" />
android:layout_below="@+id/notification_top_text"
android:layout_toEndOf="@+id/notification_status_avatar"
android:layout_toRightOf="@+id/notification_status_avatar"
android:paddingBottom="4dp"
android:paddingTop="6dp">
<TextView
android:id="@+id/notification_text"
android:id="@+id/status_display_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toEndOf="@id/notification_icon"
android:layout_toRightOf="@id/notification_icon"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:textColorSecondary"
tools:text="Someone favourited your status" />
android:paddingEnd="@dimen/status_display_name_right_padding"
android:paddingLeft="0dp"
android:paddingRight="@dimen/status_display_name_right_padding"
android:paddingStart="0dp"
android:textAppearance="@android:style/TextAppearance.DeviceDefault.Small"
android:textColor="?android:textColorTertiary"
android:textStyle="normal|bold"
tools:text="Ente" />
<TextView
android:id="@+id/status_username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/status_display_name"
android:layout_toLeftOf="@+id/status_timestamp_info"
android:layout_toRightOf="@id/status_display_name"
android:layout_toStartOf="@+id/status_timestamp_info"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:textColorTertiary"
tools:text="\@Entenhausen" />
<TextView
android:id="@+id/status_timestamp_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginLeft="4dp"
android:layout_marginStart="4dp"
android:textColor="?android:textColorTertiary"
tools:text="13:37" />
</RelativeLayout>
@ -44,7 +81,7 @@
android:id="@+id/notification_content_warning_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/notification_top_bar"
android:layout_below="@+id/status_name_bar"
android:layout_marginBottom="4dp"
android:layout_toEndOf="@+id/notification_status_avatar"
android:layout_toRightOf="@+id/notification_status_avatar"
@ -57,8 +94,9 @@
android:id="@+id/notification_content_warning_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:lineSpacingMultiplier="1.1"
android:textColor="?android:textColorTertiary"
tools:text="Example CW text"/>
tools:text="Example CW text" />
<ToggleButton
android:id="@+id/notification_content_warning_button"
@ -84,11 +122,10 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/notification_content_warning_bar"
android:layout_toEndOf="@+id/notification_status_avatar"
android:layout_toRightOf="@+id/notification_status_avatar"
android:lineSpacingMultiplier="1.1"
android:paddingBottom="10dp"
android:paddingEnd="0dp"
android:paddingLeft="58dp"
android:paddingRight="0dp"
android:paddingStart="58dp"
android:textColor="?android:textColorTertiary"
tools:text="Example status here" />
@ -96,11 +133,11 @@
android:id="@+id/notification_status_avatar"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_below="@id/notification_top_bar"
android:layout_marginBottom="8dp"
android:layout_marginEnd="10dp"
android:layout_marginRight="10dp"
android:layout_marginTop="11dp"
android:layout_below="@id/notification_top_text"
android:layout_marginBottom="14dp"
android:layout_marginEnd="14dp"
android:layout_marginRight="14dp"
android:layout_marginTop="10dp"
android:contentDescription="@string/action_view_profile"
android:paddingBottom="12dp"
android:paddingRight="12dp"
@ -114,9 +151,6 @@
android:layout_height="24dp"
android:layout_alignBottom="@+id/notification_status_avatar"
android:layout_alignEnd="@id/notification_status_avatar"
android:layout_alignRight="@id/notification_status_avatar"
android:visibility="gone"
tools:src="@color/accent"
tools:visibility="visible" />
android:layout_alignRight="@id/notification_status_avatar" />
</RelativeLayout>

View file

@ -33,8 +33,8 @@
<string name="status_username_format">\@%s</string>
<string name="status_boosted_format">%s teilte</string>
<string name="status_sensitive_media_title">Sensible Medien</string>
<string name="status_sensitive_media_directions">Tippe um zu zeigen</string>
<string name="status_sensitive_media_title">Heikle Inhalte</string>
<string name="status_sensitive_media_directions">Zum Anzeigen tippen</string>
<string name="status_content_warning_show_more">Zeige mehr</string>
<string name="status_content_warning_show_less">Zeige weniger</string>

View file

@ -36,8 +36,7 @@
<string name="status_username_format">\@%s</string>
<string name="status_boosted_format">%s podbił</string>
<string name="status_sensitive_media_title">Wrażliwe treści</string>
<string name="status_sensitive_media_directions">Dotknij, aby wyświetlić</string>
<string name="status_sensitive_media_title"><b>Wrażliwe treści</b>\nDotknij, aby wyświetlić</string>
<string name="status_content_warning_show_more">Pokaż więcej</string>
<string name="status_content_warning_show_less">Ukryj</string>

View file

@ -14,6 +14,7 @@
<attr name="toolbar_background_color" format="reference" />
<attr name="toolbar_icon_tint" format="reference" />
<attr name="image_button_style" format="reference" />
<attr name="status_reblog_small_drawable" format="reference" />
<attr name="status_reblog_inactive_drawable" format="reference" />
<attr name="status_reblog_disabled_drawable" format="reference" />
<attr name="status_reblog_direct_drawable" format="reference" />
@ -23,6 +24,7 @@
<attr name="sensitive_media_warning_background_color" format="reference|color" />
<attr name="media_preview_unloaded_drawable" format="reference" />
<attr name="status_divider_drawable" format="reference" />
<attr name="conversation_thread_line_drawable" format="reference" />
<attr name="tab_icon_selected_tint" format="reference|color" />
<attr name="tab_page_margin_drawable" format="reference" />
<attr name="account_header_background_color" format="reference|color" />
@ -43,4 +45,6 @@
<attr name="card_background" format="reference|color" />
<attr name="card_image_background" format="reference|color" />
<attr name="play_indicator_drawable" format="reference" />
</resources>

View file

@ -1,11 +1,12 @@
<resources>
<dimen name="status_display_name_right_padding">4dp</dimen>
<dimen name="status_username_right_padding">4dp</dimen>
<dimen name="status_avatar_padding">8dp</dimen>
<dimen name="status_avatar_padding">10dp</dimen>
<dimen name="status_reblogged_bar_top_padding">8dp</dimen>
<dimen name="status_reblogged_icon_left_padding">40dp</dimen>
<dimen name="status_media_preview_top_margin">4dp</dimen>
<dimen name="status_media_preview_height">96dp</dimen>
<dimen name="status_media_preview_height">100dp</dimen>
<dimen name="status_detail_media_preview_height">130dp</dimen>
<dimen name="footer_text_padding">8dp</dimen>
<dimen name="compose_media_preview_margin">8dp</dimen>
<dimen name="compose_media_preview_margin_bottom">0dp</dimen>
@ -16,9 +17,9 @@
<dimen name="notification_avatar_column_width">64dp</dimen>
<dimen name="follow_icon_left_padding">40dp</dimen>
<dimen name="account_note_margin">8dp</dimen>
<dimen name="account_avatar_margin">8dp</dimen>
<dimen name="account_avatar_margin">14dp</dimen>
<dimen name="tab_page_margin">8dp</dimen>
<dimen name="status_left_line_margin">38dp</dimen>
<dimen name="status_left_line_margin">36dp</dimen>
<dimen name="text_content_margin">16dp</dimen>
<dimen name="status_sensitive_media_button_padding">5dp</dimen>

View file

@ -7,6 +7,8 @@
<string name="oauth_redirect_host" translatable="false">com.keylesspalace.tusky</string>
<string name="preferences_file_key" translatable="false">com.keylesspalace.tusky.PREFERENCES</string>
<string name="status_sensitive_media_template">&lt;b>%1$s&lt;/b>&lt;br>%2$s</string>
<string-array name="pull_notification_check_intervals" inputType="integer">
<item>15</item>
<item>20</item>

View file

@ -39,7 +39,8 @@
<string name="status_username_format">\@%s</string>
<string name="status_boosted_format">%s boosted</string>
<string name="status_sensitive_media_title">Sensitive Media</string>
<string name="status_sensitive_media_title">Sensitive content</string>
<string name="status_media_hidden_title">Media hidden</string>
<string name="status_sensitive_media_directions">Click to view</string>
<string name="status_content_warning_show_more">Show More</string>
<string name="status_content_warning_show_less">Show Less</string>

View file

@ -19,6 +19,7 @@
<!--Base Application Theme Styles (Dark)-->
<style name="AppTheme" parent="Theme.AppCompat.NoActionBar">
<item name="android:textSize">15sp</item>
<item name="colorPrimary">@color/color_primary_dark</item>
<item name="colorPrimaryDark">@color/color_primary_dark_dark</item>
<item name="colorAccent">@color/color_accent_dark</item>
@ -40,6 +41,7 @@
<item name="toolbar_background_color">@color/toolbar_background_dark</item>
<item name="toolbar_icon_tint">@color/toolbar_icon_dark</item>
<item name="image_button_style">@style/AppTheme.ImageButton.Dark</item>
<item name="status_reblog_small_drawable">@drawable/ic_reblog_dark_18dp</item>
<item name="status_reblog_inactive_drawable">@drawable/reblog_inactive_dark</item>
<item name="status_reblog_disabled_drawable">@drawable/reblog_disabled_dark</item>
<item name="status_reblog_direct_drawable">@drawable/reblog_direct_dark</item>
@ -49,6 +51,7 @@
<item name="sensitive_media_warning_background_color">@color/color_background_dark</item>
<item name="media_preview_unloaded_drawable">@drawable/media_preview_unloaded_dark</item>
<item name="status_divider_drawable">@drawable/status_divider_dark</item>
<item name="conversation_thread_line_drawable">@drawable/conversation_thread_line_dark</item>
<item name="tab_icon_selected_tint">@color/color_accent_dark</item>
<item name="tab_page_margin_drawable">@drawable/tab_page_margin_dark</item>
<item name="account_header_background_color">@color/account_header_background_dark</item>
@ -81,6 +84,8 @@
<item name="card_background">@drawable/card_frame_dark</item>
<item name="card_image_background">@color/text_color_tertiary_dark</item>
<item name="play_indicator_drawable">@drawable/ic_play_indicator_dark</item>
</style>
<style name="AppTheme.ImageButton.Dark" parent="@style/Widget.AppCompat.Button.Borderless.Colored">
@ -101,6 +106,8 @@
<!--Light Application Theme Styles-->
<style name="AppTheme.Light" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:textSize">15sp</item>
<item name="colorPrimary">@color/color_primary_light</item>
<item name="colorPrimaryDark">@color/color_primary_dark_light</item>
<item name="colorAccent">@color/color_accent_light</item>
@ -122,6 +129,7 @@
<item name="toolbar_background_color">@color/toolbar_background_light</item>
<item name="toolbar_icon_tint">@color/toolbar_icon_light</item>
<item name="image_button_style">@style/AppTheme.ImageButton.Light</item>
<item name="status_reblog_small_drawable">@drawable/ic_reblog_light_18dp</item>
<item name="status_reblog_inactive_drawable">@drawable/reblog_inactive_light</item>
<item name="status_reblog_disabled_drawable">@drawable/reblog_disabled_light</item>
<item name="status_reblog_direct_drawable">@drawable/reblog_direct_light</item>
@ -131,6 +139,7 @@
<item name="sensitive_media_warning_background_color">@color/sensitive_media_warning_background_light</item>
<item name="media_preview_unloaded_drawable">@drawable/media_preview_unloaded_light</item>
<item name="status_divider_drawable">@drawable/status_divider_light</item>
<item name="conversation_thread_line_drawable">@drawable/conversation_thread_line_light</item>
<item name="tab_icon_selected_tint">@color/color_accent_light</item>
<item name="tab_page_margin_drawable">@drawable/tab_page_margin_light</item>
<item name="account_header_background_color">@color/account_header_background_light</item>
@ -163,6 +172,8 @@
<item name="card_background">@drawable/card_frame_light</item>
<item name="card_image_background">@color/text_color_tertiary_light</item>
<item name="play_indicator_drawable">@drawable/ic_play_indicator_light</item>
</style>
<style name="AppTheme.ImageButton.Light" parent="Widget.AppCompat.Button.Borderless.Colored">

View file

@ -21,6 +21,7 @@
<CheckBoxPreference
android:defaultValue="false"
android:dependency="mediaPreviewEnabled"
android:key="alwaysShowSensitiveMedia"
android:title="@string/pref_title_alway_show_sensitive_media" />