Merge remote-tracking branch 'origin/feature_collapse_status' into feature_collapse_status

This commit is contained in:
HellPie 2018-09-13 21:18:03 +02:00
commit 7056ba5e92
40 changed files with 232 additions and 152 deletions

View File

@ -101,7 +101,6 @@ import com.keylesspalace.tusky.network.ProgressRequestBody;
import com.keylesspalace.tusky.service.SendTootService; import com.keylesspalace.tusky.service.SendTootService;
import com.keylesspalace.tusky.util.CountUpDownLatch; import com.keylesspalace.tusky.util.CountUpDownLatch;
import com.keylesspalace.tusky.util.DownsizeImageTask; import com.keylesspalace.tusky.util.DownsizeImageTask;
import com.keylesspalace.tusky.util.IOUtils;
import com.keylesspalace.tusky.util.ListUtils; import com.keylesspalace.tusky.util.ListUtils;
import com.keylesspalace.tusky.util.MediaUtils; import com.keylesspalace.tusky.util.MediaUtils;
import com.keylesspalace.tusky.util.MentionTokenizer; import com.keylesspalace.tusky.util.MentionTokenizer;
@ -124,7 +123,6 @@ import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@ -159,8 +157,9 @@ public final class ComposeActivity
private static final String TAG = "ComposeActivity"; // logging tag private static final String TAG = "ComposeActivity"; // logging tag
static final int STATUS_CHARACTER_LIMIT = 500; static final int STATUS_CHARACTER_LIMIT = 500;
private static final int STATUS_MEDIA_SIZE_LIMIT = 8388608; // 8MiB private static final int STATUS_IMAGE_SIZE_LIMIT = 8388608; // 8MiB
private static final int STATUS_MEDIA_PIXEL_SIZE_LIMIT = 16777216; // 4096^2 Pixels private static final int STATUS_VIDEO_SIZE_LIMIT = 41943040; // 40MiB
private static final int STATUS_IMAGE_PIXEL_SIZE_LIMIT = 16777216; // 4096^2 Pixels
private static final int MEDIA_PICK_RESULT = 1; private static final int MEDIA_PICK_RESULT = 1;
private static final int MEDIA_TAKE_PHOTO_RESULT = 2; private static final int MEDIA_TAKE_PHOTO_RESULT = 2;
private static final int PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1; private static final int PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1;
@ -995,8 +994,8 @@ public final class ComposeActivity
@NonNull @NonNull
private File createNewImageFile() throws IOException { private File createNewImageFile() throws IOException {
// Create an image file name // Create an image file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date()); String randomId = StringUtils.randomAlphanumericString(12);
String imageFileName = "Tusky_" + timeStamp + "_"; String imageFileName = "Tusky_" + randomId + "_";
File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES); File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
return File.createTempFile( return File.createTempFile(
imageFileName, /* prefix */ imageFileName, /* prefix */
@ -1088,12 +1087,12 @@ public final class ComposeActivity
try { try {
if (type == QueuedMedia.Type.IMAGE && if (type == QueuedMedia.Type.IMAGE &&
(mediaSize > STATUS_MEDIA_SIZE_LIMIT || MediaUtils.getImageSquarePixels(getContentResolver(), item.uri) > STATUS_MEDIA_PIXEL_SIZE_LIMIT)) { (mediaSize > STATUS_IMAGE_SIZE_LIMIT || MediaUtils.getImageSquarePixels(getContentResolver(), item.uri) > STATUS_IMAGE_PIXEL_SIZE_LIMIT)) {
downsizeMedia(item); downsizeMedia(item);
} else { } else {
uploadMedia(item); uploadMedia(item);
} }
} catch (FileNotFoundException e) { } catch (IOException e) {
onUploadFailure(item, false); onUploadFailure(item, false);
} }
} }
@ -1223,14 +1222,17 @@ public final class ComposeActivity
} }
} }
private void downsizeMedia(final QueuedMedia item) { private void downsizeMedia(final QueuedMedia item) throws IOException {
item.readyStage = QueuedMedia.ReadyStage.DOWNSIZING; item.readyStage = QueuedMedia.ReadyStage.DOWNSIZING;
new DownsizeImageTask(STATUS_MEDIA_SIZE_LIMIT, getContentResolver(), new DownsizeImageTask(STATUS_IMAGE_SIZE_LIMIT, getContentResolver(), createNewImageFile(),
new DownsizeImageTask.Listener() { new DownsizeImageTask.Listener() {
@Override @Override
public void onSuccess(List<byte[]> contentList) { public void onSuccess(File tempFile) {
item.content = contentList.get(0); item.uri = FileProvider.getUriForFile(
ComposeActivity.this,
BuildConfig.APPLICATION_ID+".fileprovider",
tempFile);
uploadMedia(item); uploadMedia(item);
} }
@ -1242,7 +1244,7 @@ public final class ComposeActivity
} }
private void onMediaDownsizeFailure(QueuedMedia item) { private void onMediaDownsizeFailure(QueuedMedia item) {
displayTransientError(R.string.error_media_upload_size); displayTransientError(R.string.error_image_upload_size);
removeMediaFromQueue(item); removeMediaFromQueue(item);
} }
@ -1258,32 +1260,20 @@ public final class ComposeActivity
StringUtils.randomAlphanumericString(10), StringUtils.randomAlphanumericString(10),
fileExtension); fileExtension);
byte[] content = item.content; InputStream stream;
if (content == null) { try {
InputStream stream; stream = getContentResolver().openInputStream(item.uri);
} catch (FileNotFoundException e) {
try { Log.w(TAG, e);
stream = getContentResolver().openInputStream(item.uri); return;
} catch (FileNotFoundException e) {
Log.d(TAG, Log.getStackTraceString(e));
return;
}
content = MediaUtils.inputStreamGetBytes(stream);
IOUtils.closeQuietly(stream);
if (content == null) {
return;
}
} }
if (mimeType == null) mimeType = "multipart/form-data"; if (mimeType == null) mimeType = "multipart/form-data";
item.preview.setProgress(0); item.preview.setProgress(0);
ProgressRequestBody fileBody = new ProgressRequestBody(content, MediaType.parse(mimeType), ProgressRequestBody fileBody = new ProgressRequestBody(stream, MediaUtils.getMediaSize(getContentResolver(), item.uri), MediaType.parse(mimeType),
false, // If request body logging is enabled, pass true
new ProgressRequestBody.UploadCallback() { // may reference activity longer than I would like to new ProgressRequestBody.UploadCallback() { // may reference activity longer than I would like to
int lastProgress = -1; int lastProgress = -1;
@ -1378,8 +1368,8 @@ public final class ComposeActivity
String topLevelType = mimeType.substring(0, mimeType.indexOf('/')); String topLevelType = mimeType.substring(0, mimeType.indexOf('/'));
switch (topLevelType) { switch (topLevelType) {
case "video": { case "video": {
if (mediaSize > STATUS_MEDIA_SIZE_LIMIT) { if (mediaSize > STATUS_VIDEO_SIZE_LIMIT) {
displayTransientError(R.string.error_media_upload_size); displayTransientError(R.string.error_image_upload_size);
return; return;
} }
if (mediaQueued.size() > 0 if (mediaQueued.size() > 0
@ -1543,7 +1533,6 @@ public final class ComposeActivity
String id; String id;
Call<Attachment> uploadRequest; Call<Attachment> uploadRequest;
ReadyStage readyStage; ReadyStage readyStage;
byte[] content;
long mediaSize; long mediaSize;
String description; String description;

View File

@ -53,8 +53,8 @@ class EditProfileActivity : BaseActivity(), Injectable {
companion object { companion object {
const val AVATAR_SIZE = 400 const val AVATAR_SIZE = 400
const val HEADER_WIDTH = 700 const val HEADER_WIDTH = 1500
const val HEADER_HEIGHT = 335 const val HEADER_HEIGHT = 500
private const val AVATAR_PICK_RESULT = 1 private const val AVATAR_PICK_RESULT = 1
private const val HEADER_PICK_RESULT = 2 private const val HEADER_PICK_RESULT = 2

View File

@ -61,7 +61,7 @@ public class PreferencesActivity extends BaseActivity
preferences.registerOnSharedPreferenceChangeListener(this); preferences.registerOnSharedPreferenceChangeListener(this);
if(savedInstanceState == null) { if (savedInstanceState == null) {
currentPreferences = R.xml.preferences; currentPreferences = R.xml.preferences;
currentTitle = R.string.action_view_preferences; currentTitle = R.string.action_view_preferences;
} else { } else {
@ -124,6 +124,10 @@ public class PreferencesActivity extends BaseActivity
restartActivitiesOnExit = true; restartActivitiesOnExit = true;
break; break;
} }
case "absoluteTimeView": {
restartActivitiesOnExit = true;
break;
}
case "notificationsEnabled": { case "notificationsEnabled": {
boolean enabled = sharedPreferences.getBoolean("notificationsEnabled", true); boolean enabled = sharedPreferences.getBoolean("notificationsEnabled", true);
if (enabled) { if (enabled) {
@ -145,14 +149,14 @@ public class PreferencesActivity extends BaseActivity
@Override @Override
public void onBackPressed() { public void onBackPressed() {
//if we are not on the top level, show the top level. Else exit the activity //if we are not on the top level, show the top level. Else exit the activity
if(currentPreferences != R.xml.preferences) { if (currentPreferences != R.xml.preferences) {
showFragment(R.xml.preferences, R.string.action_view_preferences); showFragment(R.xml.preferences, R.string.action_view_preferences);
} else { } else {
/* Switching themes won't actually change the theme of activities on the back stack. /* Switching themes won't actually change the theme of activities on the back stack.
* Either the back stack activities need to all be recreated, or do the easier thing, which * Either the back stack activities need to all be recreated, or do the easier thing, which
* is hijack the back button press and use it to launch a new MainActivity and clear the * is hijack the back button press and use it to launch a new MainActivity and clear the
* back stack. */ * back stack. */
if (restartActivitiesOnExit) { if (restartActivitiesOnExit) {
Intent intent = new Intent(this, MainActivity.class); Intent intent = new Intent(this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);

View File

@ -52,9 +52,11 @@ import com.keylesspalace.tusky.viewdata.NotificationViewData;
import com.keylesspalace.tusky.viewdata.StatusViewData; import com.keylesspalace.tusky.viewdata.StatusViewData;
import com.squareup.picasso.Picasso; import com.squareup.picasso.Picasso;
import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale;
public class NotificationsAdapter extends RecyclerView.Adapter { public class NotificationsAdapter extends RecyclerView.Adapter {
private static final int VIEW_TYPE_MENTION = 0; private static final int VIEW_TYPE_MENTION = 0;
@ -66,6 +68,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
private StatusActionListener statusListener; private StatusActionListener statusListener;
private NotificationActionListener notificationActionListener; private NotificationActionListener notificationActionListener;
private boolean mediaPreviewEnabled; private boolean mediaPreviewEnabled;
private boolean useAbsoluteTime;
private BidiFormatter bidiFormatter; private BidiFormatter bidiFormatter;
public NotificationsAdapter(StatusActionListener statusListener, public NotificationsAdapter(StatusActionListener statusListener,
@ -75,6 +78,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
this.statusListener = statusListener; this.statusListener = statusListener;
this.notificationActionListener = notificationActionListener; this.notificationActionListener = notificationActionListener;
mediaPreviewEnabled = true; mediaPreviewEnabled = true;
useAbsoluteTime = false;
bidiFormatter = BidiFormatter.getInstance(); bidiFormatter = BidiFormatter.getInstance();
} }
@ -86,12 +90,12 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
case VIEW_TYPE_MENTION: { case VIEW_TYPE_MENTION: {
View view = LayoutInflater.from(parent.getContext()) View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_status, parent, false); .inflate(R.layout.item_status, parent, false);
return new StatusViewHolder(view); return new StatusViewHolder(view, useAbsoluteTime);
} }
case VIEW_TYPE_STATUS_NOTIFICATION: { case VIEW_TYPE_STATUS_NOTIFICATION: {
View view = LayoutInflater.from(parent.getContext()) View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_status_notification, parent, false); .inflate(R.layout.item_status_notification, parent, false);
return new StatusNotificationViewHolder(view); return new StatusNotificationViewHolder(view, useAbsoluteTime);
} }
case VIEW_TYPE_FOLLOW: { case VIEW_TYPE_FOLLOW: {
View view = LayoutInflater.from(parent.getContext()) View view = LayoutInflater.from(parent.getContext())
@ -132,7 +136,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
StatusNotificationViewHolder holder = (StatusNotificationViewHolder) viewHolder; StatusNotificationViewHolder holder = (StatusNotificationViewHolder) viewHolder;
StatusViewData.Concrete statusViewData = concreteNotificaton.getStatusViewData(); StatusViewData.Concrete statusViewData = concreteNotificaton.getStatusViewData();
if(statusViewData == null) { if (statusViewData == null) {
holder.showNotificationContent(false); holder.showNotificationContent(false);
} else { } else {
holder.showNotificationContent(true); holder.showNotificationContent(true);
@ -230,6 +234,10 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
return mediaPreviewEnabled; return mediaPreviewEnabled;
} }
public void setUseAbsoluteTime(boolean useAbsoluteTime) {
this.useAbsoluteTime = useAbsoluteTime;
}
public interface NotificationActionListener { public interface NotificationActionListener {
void onViewAccount(String id); void onViewAccount(String id);
@ -317,7 +325,11 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
private NotificationActionListener notificationActionListener; private NotificationActionListener notificationActionListener;
private StatusViewData.Concrete statusViewData; private StatusViewData.Concrete statusViewData;
StatusNotificationViewHolder(View itemView) { private boolean useAbsoluteTime;
private SimpleDateFormat shortSdf;
private SimpleDateFormat longSdf;
StatusNotificationViewHolder(View itemView, boolean useAbsoluteTime) {
super(itemView); super(itemView);
message = itemView.findViewById(R.id.notification_top_text); message = itemView.findViewById(R.id.notification_top_text);
statusNameBar = itemView.findViewById(R.id.status_name_bar); statusNameBar = itemView.findViewById(R.id.status_name_bar);
@ -340,6 +352,10 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
message.setOnClickListener(this); message.setOnClickListener(this);
statusContent.setOnClickListener(this); statusContent.setOnClickListener(this);
contentWarningButton.setOnCheckedChangeListener(this); contentWarningButton.setOnCheckedChangeListener(this);
this.useAbsoluteTime = useAbsoluteTime;
shortSdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());
longSdf = new SimpleDateFormat("MM/dd HH:mm:ss", Locale.getDefault());
} }
private void showNotificationContent(boolean show) { private void showNotificationContent(boolean show) {
@ -363,26 +379,40 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
username.setText(usernameText); username.setText(usernameText);
} }
private void setCreatedAt(@Nullable Date createdAt) { protected void setCreatedAt(@Nullable Date createdAt) {
// This is the visible timestampInfo. if (useAbsoluteTime) {
String readout; String time;
/* This one is for screen-readers. Frequently, they would mispronounce timestamps like "17m" if (createdAt != null) {
* as 17 meters instead of minutes. */ if (System.currentTimeMillis() - createdAt.getTime() > 86400000L) {
CharSequence readoutAloud; time = longSdf.format(createdAt);
if (createdAt != null) { } else {
long then = createdAt.getTime(); time = shortSdf.format(createdAt);
long now = new Date().getTime(); }
readout = DateUtils.getRelativeTimeSpanString(timestampInfo.getContext(), then, now); } else {
readoutAloud = android.text.format.DateUtils.getRelativeTimeSpanString(then, now, time = "??:??:??";
android.text.format.DateUtils.SECOND_IN_MILLIS, }
android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE); timestampInfo.setText(time);
} else { } else {
// unknown minutes~ // This is the visible timestampInfo.
readout = "?m"; String readout;
readoutAloud = "? minutes"; /* 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);
} }
timestampInfo.setText(readout);
timestampInfo.setContentDescription(readoutAloud);
} }
void setMessage(NotificationViewData.Concrete notificationViewData, LinkListener listener, BidiFormatter bidiFormatter) { void setMessage(NotificationViewData.Concrete notificationViewData, LinkListener listener, BidiFormatter bidiFormatter) {
@ -408,7 +438,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
} }
case REBLOG: { case REBLOG: {
icon = ContextCompat.getDrawable(context, R.drawable.ic_repeat_24dp); icon = ContextCompat.getDrawable(context, R.drawable.ic_repeat_24dp);
if(icon != null) { if (icon != null) {
icon.setColorFilter(ContextCompat.getColor(context, icon.setColorFilter(ContextCompat.getColor(context,
R.color.color_accent_dark), PorterDuff.Mode.SRC_ATOP); R.color.color_accent_dark), PorterDuff.Mode.SRC_ATOP);
} }
@ -469,10 +499,12 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
switch (v.getId()) { switch (v.getId()) {
case R.id.notification_container: case R.id.notification_container:
case R.id.notification_content: case R.id.notification_content:
if (notificationActionListener != null) notificationActionListener.onViewStatusForNotificationId(notificationId); if (notificationActionListener != null)
notificationActionListener.onViewStatusForNotificationId(notificationId);
break; break;
case R.id.notification_top_text: case R.id.notification_top_text:
if (notificationActionListener != null) notificationActionListener.onViewAccount(accountId); if (notificationActionListener != null)
notificationActionListener.onViewAccount(accountId);
break; break;
} }
} }

View File

@ -49,6 +49,7 @@ public class SearchResultsAdapter extends RecyclerView.Adapter {
private boolean mediaPreviewsEnabled; private boolean mediaPreviewsEnabled;
private boolean alwaysShowSensitiveMedia; private boolean alwaysShowSensitiveMedia;
private boolean collapseLongStatusContent; private boolean collapseLongStatusContent;
private boolean useAbsoluteTime;
private LinkListener linkListener; private LinkListener linkListener;
private StatusActionListener statusListener; private StatusActionListener statusListener;
@ -57,7 +58,8 @@ public class SearchResultsAdapter extends RecyclerView.Adapter {
boolean alwaysShowSensitiveMedia, boolean alwaysShowSensitiveMedia,
boolean collapseLongStatusContent, boolean collapseLongStatusContent,
LinkListener linkListener, LinkListener linkListener,
StatusActionListener statusListener) { StatusActionListener statusListener,
boolean useAbsoluteTime) {
this.accountList = Collections.emptyList(); this.accountList = Collections.emptyList();
this.statusList = Collections.emptyList(); this.statusList = Collections.emptyList();
@ -67,6 +69,7 @@ public class SearchResultsAdapter extends RecyclerView.Adapter {
this.mediaPreviewsEnabled = mediaPreviewsEnabled; this.mediaPreviewsEnabled = mediaPreviewsEnabled;
this.alwaysShowSensitiveMedia = alwaysShowSensitiveMedia; this.alwaysShowSensitiveMedia = alwaysShowSensitiveMedia;
this.collapseLongStatusContent = collapseLongStatusContent; this.collapseLongStatusContent = collapseLongStatusContent;
this.useAbsoluteTime = useAbsoluteTime;
this.linkListener = linkListener; this.linkListener = linkListener;
this.statusListener = statusListener; this.statusListener = statusListener;
@ -91,7 +94,7 @@ public class SearchResultsAdapter extends RecyclerView.Adapter {
case VIEW_TYPE_STATUS: { case VIEW_TYPE_STATUS: {
View view = LayoutInflater.from(parent.getContext()) View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_status, parent, false); .inflate(R.layout.item_status, parent, false);
return new StatusViewHolder(view); return new StatusViewHolder(view, useAbsoluteTime);
} }
} }
} }

View File

@ -32,8 +32,10 @@ import com.keylesspalace.tusky.viewdata.StatusViewData;
import com.mikepenz.iconics.utils.Utils; import com.mikepenz.iconics.utils.Utils;
import com.squareup.picasso.Picasso; import com.squareup.picasso.Picasso;
import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale;
import at.connyduck.sparkbutton.SparkButton; import at.connyduck.sparkbutton.SparkButton;
import at.connyduck.sparkbutton.SparkEventListener; import at.connyduck.sparkbutton.SparkEventListener;
@ -67,7 +69,11 @@ abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
TextView content; TextView content;
TextView contentWarningDescription; TextView contentWarningDescription;
StatusBaseViewHolder(View itemView) { private boolean useAbsoluteTime;
private SimpleDateFormat shortSdf;
private SimpleDateFormat longSdf;
StatusBaseViewHolder(View itemView, boolean useAbsoluteTime) {
super(itemView); super(itemView);
container = itemView.findViewById(R.id.status_container); container = itemView.findViewById(R.id.status_container);
displayName = itemView.findViewById(R.id.status_display_name); displayName = itemView.findViewById(R.id.status_display_name);
@ -95,6 +101,10 @@ abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
contentWarningDescription = itemView.findViewById(R.id.status_content_warning_description); contentWarningDescription = itemView.findViewById(R.id.status_content_warning_description);
contentWarningButton = itemView.findViewById(R.id.status_content_warning_button); contentWarningButton = itemView.findViewById(R.id.status_content_warning_button);
contentCollapseButton = itemView.findViewById(R.id.button_toggle_content); contentCollapseButton = itemView.findViewById(R.id.button_toggle_content);
this.useAbsoluteTime = useAbsoluteTime;
shortSdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());
longSdf = new SimpleDateFormat("MM/dd HH:mm:ss", Locale.getDefault());
} }
protected abstract int getMediaPreviewHeight(Context context); protected abstract int getMediaPreviewHeight(Context context);
@ -130,25 +140,39 @@ abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
} }
protected void setCreatedAt(@Nullable Date createdAt) { protected void setCreatedAt(@Nullable Date createdAt) {
// This is the visible timestampInfo. if (useAbsoluteTime) {
String readout; String time;
/* This one is for screen-readers. Frequently, they would mispronounce timestamps like "17m" if (createdAt != null) {
* as 17 meters instead of minutes. */ if (System.currentTimeMillis() - createdAt.getTime() > 86400000L) {
CharSequence readoutAloud; time = longSdf.format(createdAt);
if (createdAt != null) { } else {
long then = createdAt.getTime(); time = shortSdf.format(createdAt);
long now = new Date().getTime(); }
readout = DateUtils.getRelativeTimeSpanString(timestampInfo.getContext(), then, now); } else {
readoutAloud = android.text.format.DateUtils.getRelativeTimeSpanString(then, now, time = "??:??:??";
android.text.format.DateUtils.SECOND_IN_MILLIS, }
android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE); timestampInfo.setText(time);
} else { } else {
// unknown minutes~ // This is the visible timestampInfo.
readout = "?m"; String readout;
readoutAloud = "? minutes"; /* 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);
} }
timestampInfo.setText(readout);
timestampInfo.setContentDescription(readoutAloud);
} }
protected void showContent(boolean show) { protected void showContent(boolean show) {
@ -260,7 +284,7 @@ abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
final int urlIndex = i; final int urlIndex = i;
previews[i].setOnClickListener(v -> { previews[i].setOnClickListener(v -> {
if(getAdapterPosition() != RecyclerView.NO_POSITION) { if (getAdapterPosition() != RecyclerView.NO_POSITION) {
listener.onViewMedia(getAdapterPosition(), urlIndex, v); listener.onViewMedia(getAdapterPosition(), urlIndex, v);
} }
}); });

View File

@ -41,7 +41,7 @@ class StatusDetailedViewHolder extends StatusBaseViewHolder {
private TextView cardUrl; private TextView cardUrl;
StatusDetailedViewHolder(View view) { StatusDetailedViewHolder(View view) {
super(view); super(view, false);
reblogs = view.findViewById(R.id.status_reblogs); reblogs = view.findViewById(R.id.status_reblogs);
favourites = view.findViewById(R.id.status_favourites); favourites = view.findViewById(R.id.status_favourites);
cardView = view.findViewById(R.id.card_view); cardView = view.findViewById(R.id.card_view);

View File

@ -34,8 +34,8 @@ public class StatusViewHolder extends StatusBaseViewHolder {
private ImageView avatarReblog; private ImageView avatarReblog;
private TextView rebloggedBar; private TextView rebloggedBar;
StatusViewHolder(View itemView) { StatusViewHolder(View itemView, boolean useAbsoluteTime) {
super(itemView); super(itemView, useAbsoluteTime);
avatarReblog = itemView.findViewById(R.id.status_avatar_reblog); avatarReblog = itemView.findViewById(R.id.status_avatar_reblog);
rebloggedBar = itemView.findViewById(R.id.status_reblogged); rebloggedBar = itemView.findViewById(R.id.status_reblogged);
//workaround because Android < API 21 does not support setting drawableLeft from xml when it is a vector image //workaround because Android < API 21 does not support setting drawableLeft from xml when it is a vector image

View File

@ -36,12 +36,14 @@ public class ThreadAdapter extends RecyclerView.Adapter {
private List<StatusViewData.Concrete> statuses; private List<StatusViewData.Concrete> statuses;
private StatusActionListener statusActionListener; private StatusActionListener statusActionListener;
private boolean mediaPreviewEnabled; private boolean mediaPreviewEnabled;
private boolean useAbsoluteTime;
private int detailedStatusPosition; private int detailedStatusPosition;
public ThreadAdapter(StatusActionListener listener) { public ThreadAdapter(StatusActionListener listener) {
this.statusActionListener = listener; this.statusActionListener = listener;
this.statuses = new ArrayList<>(); this.statuses = new ArrayList<>();
mediaPreviewEnabled = true; mediaPreviewEnabled = true;
useAbsoluteTime = false;
detailedStatusPosition = RecyclerView.NO_POSITION; detailedStatusPosition = RecyclerView.NO_POSITION;
} }
@ -53,7 +55,7 @@ public class ThreadAdapter extends RecyclerView.Adapter {
case VIEW_TYPE_STATUS: { case VIEW_TYPE_STATUS: {
View view = LayoutInflater.from(parent.getContext()) View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_status, parent, false); .inflate(R.layout.item_status, parent, false);
return new StatusViewHolder(view); return new StatusViewHolder(view, useAbsoluteTime);
} }
case VIEW_TYPE_STATUS_DETAILED: { case VIEW_TYPE_STATUS_DETAILED: {
View view = LayoutInflater.from(parent.getContext()) View view = LayoutInflater.from(parent.getContext())
@ -149,6 +151,10 @@ public class ThreadAdapter extends RecyclerView.Adapter {
mediaPreviewEnabled = enabled; mediaPreviewEnabled = enabled;
} }
public void setUseAbsoluteTime(boolean useAbsoluteTime) {
this.useAbsoluteTime = useAbsoluteTime;
}
public void setDetailedStatusPosition(int position) { public void setDetailedStatusPosition(int position) {
if (position != detailedStatusPosition if (position != detailedStatusPosition
&& detailedStatusPosition != RecyclerView.NO_POSITION) { && detailedStatusPosition != RecyclerView.NO_POSITION) {

View File

@ -39,6 +39,7 @@ public final class TimelineAdapter extends RecyclerView.Adapter {
private final AdapterDataSource<StatusViewData> dataSource; private final AdapterDataSource<StatusViewData> dataSource;
private final StatusActionListener statusListener; private final StatusActionListener statusListener;
private boolean mediaPreviewEnabled; private boolean mediaPreviewEnabled;
private boolean useAbsoluteTime;
public TimelineAdapter(AdapterDataSource<StatusViewData> dataSource, public TimelineAdapter(AdapterDataSource<StatusViewData> dataSource,
StatusActionListener statusListener) { StatusActionListener statusListener) {
@ -46,6 +47,7 @@ public final class TimelineAdapter extends RecyclerView.Adapter {
this.dataSource = dataSource; this.dataSource = dataSource;
this.statusListener = statusListener; this.statusListener = statusListener;
mediaPreviewEnabled = true; mediaPreviewEnabled = true;
useAbsoluteTime = false;
} }
@NonNull @NonNull
@ -56,7 +58,7 @@ public final class TimelineAdapter extends RecyclerView.Adapter {
case VIEW_TYPE_STATUS: { case VIEW_TYPE_STATUS: {
View view = LayoutInflater.from(viewGroup.getContext()) View view = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.item_status, viewGroup, false); .inflate(R.layout.item_status, viewGroup, false);
return new StatusViewHolder(view); return new StatusViewHolder(view, useAbsoluteTime);
} }
case VIEW_TYPE_PLACEHOLDER: { case VIEW_TYPE_PLACEHOLDER: {
View view = LayoutInflater.from(viewGroup.getContext()) View view = LayoutInflater.from(viewGroup.getContext())
@ -97,6 +99,10 @@ public final class TimelineAdapter extends RecyclerView.Adapter {
mediaPreviewEnabled = enabled; mediaPreviewEnabled = enabled;
} }
public void setUseAbsoluteTime(boolean useAbsoluteTime){
this.useAbsoluteTime=useAbsoluteTime;
}
public boolean getMediaPreviewEnabled() { public boolean getMediaPreviewEnabled() {
return mediaPreviewEnabled; return mediaPreviewEnabled;
} }

View File

@ -202,6 +202,8 @@ public class NotificationsFragment extends SFragment implements
collapseLongStatusContent = preferences.getBoolean("collapseLongStatuses", true); collapseLongStatusContent = preferences.getBoolean("collapseLongStatuses", true);
boolean mediaPreviewEnabled = preferences.getBoolean("mediaPreviewEnabled", true); boolean mediaPreviewEnabled = preferences.getBoolean("mediaPreviewEnabled", true);
adapter.setMediaPreviewEnabled(mediaPreviewEnabled); adapter.setMediaPreviewEnabled(mediaPreviewEnabled);
boolean useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false);
adapter.setUseAbsoluteTime(useAbsoluteTime);
recyclerView.setAdapter(adapter); recyclerView.setAdapter(adapter);
notifications.clear(); notifications.clear();

View File

@ -51,7 +51,7 @@ class SearchFragment : SFragment(), StatusActionListener, Injectable {
private var alwaysShowSensitiveMedia = false private var alwaysShowSensitiveMedia = false
private var mediaPreviewEnabled = true private var mediaPreviewEnabled = true
private var collapseLongStatusContent = true; private var collapseLongStatusContent = true;
private var useAbsoluteTime = false
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_search, container, false) return inflater.inflate(R.layout.fragment_search, container, false)
@ -61,6 +61,7 @@ class SearchFragment : SFragment(), StatusActionListener, Injectable {
val preferences = PreferenceManager.getDefaultSharedPreferences(view.context) val preferences = PreferenceManager.getDefaultSharedPreferences(view.context)
alwaysShowSensitiveMedia = preferences.getBoolean("alwaysShowSensitiveMedia", false) alwaysShowSensitiveMedia = preferences.getBoolean("alwaysShowSensitiveMedia", false)
mediaPreviewEnabled = preferences.getBoolean("mediaPreviewEnabled", true) mediaPreviewEnabled = preferences.getBoolean("mediaPreviewEnabled", true)
useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false)
collapseLongStatusContent = preferences.getBoolean("collapseLongStatuses", true); collapseLongStatusContent = preferences.getBoolean("collapseLongStatuses", true);
@ -71,8 +72,7 @@ class SearchFragment : SFragment(), StatusActionListener, Injectable {
alwaysShowSensitiveMedia, alwaysShowSensitiveMedia,
collapseLongStatusContent, collapseLongStatusContent,
this, this,
this useAbsoluteTime)
)
searchRecyclerView.adapter = searchAdapter searchRecyclerView.adapter = searchAdapter
} }

View File

@ -249,6 +249,8 @@ public class TimelineFragment extends SFragment implements
alwaysShowSensitiveMedia = preferences.getBoolean("alwaysShowSensitiveMedia", false); alwaysShowSensitiveMedia = preferences.getBoolean("alwaysShowSensitiveMedia", false);
boolean mediaPreviewEnabled = preferences.getBoolean("mediaPreviewEnabled", true); boolean mediaPreviewEnabled = preferences.getBoolean("mediaPreviewEnabled", true);
adapter.setMediaPreviewEnabled(mediaPreviewEnabled); adapter.setMediaPreviewEnabled(mediaPreviewEnabled);
boolean useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false);
adapter.setUseAbsoluteTime(useAbsoluteTime);
boolean filter = preferences.getBoolean("tabFilterHomeReplies", true); boolean filter = preferences.getBoolean("tabFilterHomeReplies", true);
filterRemoveReplies = kind == Kind.HOME && !filter; filterRemoveReplies = kind == Kind.HOME && !filter;
@ -638,7 +640,7 @@ public class TimelineFragment extends SFragment implements
case "mediaPreviewEnabled": { case "mediaPreviewEnabled": {
boolean enabled = sharedPreferences.getBoolean("mediaPreviewEnabled", true); boolean enabled = sharedPreferences.getBoolean("mediaPreviewEnabled", true);
boolean oldMediaPreviewEnabled = adapter.getMediaPreviewEnabled(); boolean oldMediaPreviewEnabled = adapter.getMediaPreviewEnabled();
if(enabled != oldMediaPreviewEnabled) { if (enabled != oldMediaPreviewEnabled) {
adapter.setMediaPreviewEnabled(enabled); adapter.setMediaPreviewEnabled(enabled);
fullyRefresh(); fullyRefresh();
} }
@ -867,7 +869,7 @@ public class TimelineFragment extends SFragment implements
} }
private void onFetchTimelineFailure(Exception exception, FetchEnd fetchEnd, int position) { private void onFetchTimelineFailure(Exception exception, FetchEnd fetchEnd, int position) {
if(isAdded()) { if (isAdded()) {
swipeRefreshLayout.setRefreshing(false); swipeRefreshLayout.setRefreshing(false);
if (fetchEnd == FetchEnd.MIDDLE && !statuses.get(position).isRight()) { if (fetchEnd == FetchEnd.MIDDLE && !statuses.get(position).isRight()) {
@ -1089,7 +1091,7 @@ public class TimelineFragment extends SFragment implements
private final ListUpdateCallback listUpdateCallback = new ListUpdateCallback() { private final ListUpdateCallback listUpdateCallback = new ListUpdateCallback() {
@Override @Override
public void onInserted(int position, int count) { public void onInserted(int position, int count) {
if(isAdded()) { if (isAdded()) {
adapter.notifyItemRangeInserted(position, count); adapter.notifyItemRangeInserted(position, count);
Context context = getContext(); Context context = getContext();
if (position == 0 && context != null) { if (position == 0 && context != null) {

View File

@ -163,6 +163,8 @@ public final class ViewThreadFragment extends SFragment implements
collapseLongStatusContent = preferences.getBoolean("collapseLongStatuses", true); collapseLongStatusContent = preferences.getBoolean("collapseLongStatuses", true);
boolean mediaPreviewEnabled = preferences.getBoolean("mediaPreviewEnabled", true); boolean mediaPreviewEnabled = preferences.getBoolean("mediaPreviewEnabled", true);
adapter.setMediaPreviewEnabled(mediaPreviewEnabled); adapter.setMediaPreviewEnabled(mediaPreviewEnabled);
boolean useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false);
adapter.setUseAbsoluteTime(useAbsoluteTime);
recyclerView.setAdapter(adapter); recyclerView.setAdapter(adapter);
statuses.clear(); statuses.clear();

View File

@ -17,18 +17,18 @@ package com.keylesspalace.tusky.network;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import okhttp3.MediaType; import okhttp3.MediaType;
import okhttp3.RequestBody; import okhttp3.RequestBody;
import okio.BufferedSink; import okio.BufferedSink;
public final class ProgressRequestBody extends RequestBody { public final class ProgressRequestBody extends RequestBody {
private final byte[] content; private final InputStream content;
private final UploadCallback mListener; private final long contentLength;
private final UploadCallback uploadListener;
private final MediaType mediaType; private final MediaType mediaType;
private boolean shouldIgnoreThisPass;
private static final int DEFAULT_BUFFER_SIZE = 2048; private static final int DEFAULT_BUFFER_SIZE = 2048;
@ -36,11 +36,11 @@ public final class ProgressRequestBody extends RequestBody {
void onProgressUpdate(int percentage); void onProgressUpdate(int percentage);
} }
public ProgressRequestBody(final byte[] content, final MediaType mediaType, boolean shouldIgnoreFirst, final UploadCallback listener) { public ProgressRequestBody(final InputStream content, long contentLength, final MediaType mediaType, final UploadCallback listener) {
this.content = content; this.content = content;
this.contentLength = contentLength;
this.mediaType = mediaType; this.mediaType = mediaType;
mListener = listener; this.uploadListener = listener;
shouldIgnoreThisPass = shouldIgnoreFirst;
} }
@Override @Override
@ -50,29 +50,25 @@ public final class ProgressRequestBody extends RequestBody {
@Override @Override
public long contentLength() { public long contentLength() {
return content.length; return contentLength;
} }
@Override @Override
public void writeTo(@NonNull BufferedSink sink) throws IOException { public void writeTo(@NonNull BufferedSink sink) throws IOException {
long length = content.length;
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
ByteArrayInputStream in = new ByteArrayInputStream(content);
long uploaded = 0; long uploaded = 0;
try { try {
int read; int read;
while ((read = in.read(buffer)) != -1) { while ((read = content.read(buffer)) != -1) {
if (!shouldIgnoreThisPass) { uploadListener.onProgressUpdate((int)(100 * uploaded / contentLength));
mListener.onProgressUpdate((int)(100 * uploaded / length));
}
uploaded += read; uploaded += read;
sink.write(buffer, 0, read); sink.write(buffer, 0, read);
} }
} finally { } finally {
in.close(); content.close();
} }
shouldIgnoreThisPass = false;
} }
} }

View File

@ -21,11 +21,11 @@ import android.graphics.BitmapFactory;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import java.io.ByteArrayOutputStream; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.io.OutputStream;
import java.util.List;
/** /**
* Reduces the file size of images to fit under a given limit by resizing them, maintaining both * Reduces the file size of images to fit under a given limit by resizing them, maintaining both
@ -35,22 +35,23 @@ public class DownsizeImageTask extends AsyncTask<Uri, Void, Boolean> {
private int sizeLimit; private int sizeLimit;
private ContentResolver contentResolver; private ContentResolver contentResolver;
private Listener listener; private Listener listener;
private List<byte[]> resultList; private File tempFile;
/** /**
* @param sizeLimit the maximum number of bytes each image can take * @param sizeLimit the maximum number of bytes each image can take
* @param contentResolver to resolve the specified images' URIs * @param contentResolver to resolve the specified images' URIs
* @param tempFile the file where the result will be stored
* @param listener to whom the results are given * @param listener to whom the results are given
*/ */
public DownsizeImageTask(int sizeLimit, ContentResolver contentResolver, Listener listener) { public DownsizeImageTask(int sizeLimit, ContentResolver contentResolver, File tempFile, Listener listener) {
this.sizeLimit = sizeLimit; this.sizeLimit = sizeLimit;
this.contentResolver = contentResolver; this.contentResolver = contentResolver;
this.tempFile = tempFile;
this.listener = listener; this.listener = listener;
} }
@Override @Override
protected Boolean doInBackground(Uri... uris) { protected Boolean doInBackground(Uri... uris) {
resultList = new ArrayList<>();
for (Uri uri : uris) { for (Uri uri : uris) {
InputStream inputStream; InputStream inputStream;
try { try {
@ -65,8 +66,6 @@ public class DownsizeImageTask extends AsyncTask<Uri, Void, Boolean> {
IOUtils.closeQuietly(inputStream); IOUtils.closeQuietly(inputStream);
// Get EXIF data, for orientation info. // Get EXIF data, for orientation info.
int orientation = MediaUtils.getImageOrientation(uri, contentResolver); int orientation = MediaUtils.getImageOrientation(uri, contentResolver);
// Then use that information to determine how much to compress.
ByteArrayOutputStream stream = new ByteArrayOutputStream();
/* Unfortunately, there isn't a determined worst case compression ratio for image /* Unfortunately, there isn't a determined worst case compression ratio for image
* formats. So, the only way to tell if they're too big is to compress them and * formats. So, the only way to tell if they're too big is to compress them and
* test, and keep trying at smaller sizes. The initial estimate should be good for * test, and keep trying at smaller sizes. The initial estimate should be good for
@ -74,7 +73,12 @@ public class DownsizeImageTask extends AsyncTask<Uri, Void, Boolean> {
* sure it gets downsized to below the limit. */ * sure it gets downsized to below the limit. */
int scaledImageSize = 1024; int scaledImageSize = 1024;
do { do {
stream.reset(); OutputStream stream;
try {
stream = new FileOutputStream(tempFile);
} catch (FileNotFoundException e) {
return false;
}
try { try {
inputStream = contentResolver.openInputStream(uri); inputStream = contentResolver.openInputStream(uri);
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
@ -109,9 +113,8 @@ public class DownsizeImageTask extends AsyncTask<Uri, Void, Boolean> {
reorientedBitmap.compress(format, 85, stream); reorientedBitmap.compress(format, 85, stream);
reorientedBitmap.recycle(); reorientedBitmap.recycle();
scaledImageSize /= 2; scaledImageSize /= 2;
} while (stream.size() > sizeLimit); } while (tempFile.length() > sizeLimit);
resultList.add(stream.toByteArray());
if (isCancelled()) { if (isCancelled()) {
return false; return false;
} }
@ -122,7 +125,7 @@ public class DownsizeImageTask extends AsyncTask<Uri, Void, Boolean> {
@Override @Override
protected void onPostExecute(Boolean successful) { protected void onPostExecute(Boolean successful) {
if (successful) { if (successful) {
listener.onSuccess(resultList); listener.onSuccess(tempFile);
} else { } else {
listener.onFailure(); listener.onFailure();
} }
@ -131,7 +134,7 @@ public class DownsizeImageTask extends AsyncTask<Uri, Void, Boolean> {
/** Used to communicate the results of the task. */ /** Used to communicate the results of the task. */
public interface Listener { public interface Listener {
void onSuccess(List<byte[]> contentList); void onSuccess(File file);
void onFailure(); void onFailure();
} }
} }

View File

@ -9,7 +9,7 @@
<string name="error_authorization_denied">تم رفض التصريح.</string> <string name="error_authorization_denied">تم رفض التصريح.</string>
<string name="error_retrieving_oauth_token">فشل الحصول على رمز الدخول.</string> <string name="error_retrieving_oauth_token">فشل الحصول على رمز الدخول.</string>
<string name="error_compose_character_limit">المنشور طويل جدا !</string> <string name="error_compose_character_limit">المنشور طويل جدا !</string>
<string name="error_media_upload_size">يجب أن يكون حجم الملف أقل من 4 ميغابايت.</string> <string name="error_image_upload_size">يجب أن يكون حجم الملف أقل من 4 ميغابايت.</string>
<string name="error_media_upload_type">لا يمكن رفع هذا النوع من الملفات.</string> <string name="error_media_upload_type">لا يمكن رفع هذا النوع من الملفات.</string>
<string name="error_media_upload_opening">تعذر فتح ذاك الملف.</string> <string name="error_media_upload_opening">تعذر فتح ذاك الملف.</string>
<string name="error_media_upload_permission">التصريح لازم لقراءة الوسائط</string> <string name="error_media_upload_permission">التصريح لازم لقراءة الوسائط</string>

View File

@ -9,7 +9,7 @@
<string name="error_authorization_denied">L\'autorització s\'ha denegat.</string> <string name="error_authorization_denied">L\'autorització s\'ha denegat.</string>
<string name="error_retrieving_oauth_token">L\'obtenció del testimoni d\'inici de sessió ha fallat.</string> <string name="error_retrieving_oauth_token">L\'obtenció del testimoni d\'inici de sessió ha fallat.</string>
<string name="error_compose_character_limit">L\'estat és massa llarg!</string> <string name="error_compose_character_limit">L\'estat és massa llarg!</string>
<string name="error_media_upload_size">El fitxer ha de ser inferior a 8MB.</string> <string name="error_image_upload_size">El fitxer ha de ser inferior a 8MB.</string>
<string name="error_media_upload_type">Aquest tipus de fitxer no es pot pujar.</string> <string name="error_media_upload_type">Aquest tipus de fitxer no es pot pujar.</string>
<string name="error_media_upload_opening">Aquest tipus de fitxer no es pot obrir.</string> <string name="error_media_upload_opening">Aquest tipus de fitxer no es pot obrir.</string>
<string name="error_media_upload_permission">Cal permís de lectura del mitjà.</string> <string name="error_media_upload_permission">Cal permís de lectura del mitjà.</string>

View File

@ -9,7 +9,7 @@
<string name="error_authorization_denied">Autorisierung fehlgeschlagen.</string> <string name="error_authorization_denied">Autorisierung fehlgeschlagen.</string>
<string name="error_retrieving_oauth_token">Es konnte kein Login-Token abgerufen werden.</string> <string name="error_retrieving_oauth_token">Es konnte kein Login-Token abgerufen werden.</string>
<string name="error_compose_character_limit">Der Beitrag ist zu lang!</string> <string name="error_compose_character_limit">Der Beitrag ist zu lang!</string>
<string name="error_media_upload_size">Die Datei muss kleiner als 8MB sein.</string> <string name="error_image_upload_size">Die Datei muss kleiner als 8MB sein.</string>
<string name="error_media_upload_type">Dieser Dateityp darf nicht hochgeladen werden.</string> <string name="error_media_upload_type">Dieser Dateityp darf nicht hochgeladen werden.</string>
<string name="error_media_upload_opening">Die Datei konnte nicht geöffnet werden.</string> <string name="error_media_upload_opening">Die Datei konnte nicht geöffnet werden.</string>
<string name="error_media_upload_permission">Eine Leseberechtigung wird für das Hochladen der Mediendatei benötigt.</string> <string name="error_media_upload_permission">Eine Leseberechtigung wird für das Hochladen der Mediendatei benötigt.</string>

View File

@ -9,7 +9,7 @@
<string name="error_authorization_denied">La autorización falló.</string> <string name="error_authorization_denied">La autorización falló.</string>
<string name="error_retrieving_oauth_token">Fallo al obtener identificador de login.</string> <string name="error_retrieving_oauth_token">Fallo al obtener identificador de login.</string>
<string name="error_compose_character_limit">¡El estado es demasiado largo!</string> <string name="error_compose_character_limit">¡El estado es demasiado largo!</string>
<string name="error_media_upload_size">El archivo debe ser inferior a 8MB.</string> <string name="error_image_upload_size">El archivo debe ser inferior a 8MB.</string>
<string name="error_media_upload_type">No se admite este tipo de archivo.</string> <string name="error_media_upload_type">No se admite este tipo de archivo.</string>
<string name="error_media_upload_opening">No pudo abrirse el fichero.</string> <string name="error_media_upload_opening">No pudo abrirse el fichero.</string>
<string name="error_media_upload_permission">Se requiere permiso para acceder al almacenamiento.</string> <string name="error_media_upload_permission">Se requiere permiso para acceder al almacenamiento.</string>

View File

@ -9,7 +9,7 @@
<string name="error_authorization_denied">Authentification refusée.</string> <string name="error_authorization_denied">Authentification refusée.</string>
<string name="error_retrieving_oauth_token">Impossible de récupérer le jeton dauthentification.</string> <string name="error_retrieving_oauth_token">Impossible de récupérer le jeton dauthentification.</string>
<string name="error_compose_character_limit">Votre pouet est trop long !</string> <string name="error_compose_character_limit">Votre pouet est trop long !</string>
<string name="error_media_upload_size">Le fichier doit peser moins de 8 Mo.</string> <string name="error_image_upload_size">Le fichier doit peser moins de 8 Mo.</string>
<string name="error_media_upload_type">Ce type de fichier nest pas accepté.</string> <string name="error_media_upload_type">Ce type de fichier nest pas accepté.</string>
<string name="error_media_upload_opening">Le fichier ne peut pas être ouvert.</string> <string name="error_media_upload_opening">Le fichier ne peut pas être ouvert.</string>
<string name="error_media_upload_permission">Permission requise pour lire ce média.</string> <string name="error_media_upload_permission">Permission requise pour lire ce média.</string>

View File

@ -9,7 +9,7 @@
<string name="error_authorization_denied">Engedélyezés letiltva.</string> <string name="error_authorization_denied">Engedélyezés letiltva.</string>
<string name="error_retrieving_oauth_token">Bejelentkezési token megszerzése sikertelen.</string> <string name="error_retrieving_oauth_token">Bejelentkezési token megszerzése sikertelen.</string>
<string name="error_compose_character_limit">Túl hosszú a tülkölés!</string> <string name="error_compose_character_limit">Túl hosszú a tülkölés!</string>
<string name="error_media_upload_size">A fájl kisebb kell legyen mint 8MB.</string> <string name="error_image_upload_size">A fájl kisebb kell legyen mint 8MB.</string>
<string name="error_media_upload_type">Fájl feltöltése sikertelen.</string> <string name="error_media_upload_type">Fájl feltöltése sikertelen.</string>
<string name="error_media_upload_opening">Fájl megnyitása sikertelen.</string> <string name="error_media_upload_opening">Fájl megnyitása sikertelen.</string>
<string name="error_media_upload_permission">Média olvasási engedély szükséges.</string> <string name="error_media_upload_permission">Média olvasási engedély szükséges.</string>

View File

@ -9,7 +9,7 @@
<string name="error_authorization_denied">L\'autorizzazione è stata negata.</string> <string name="error_authorization_denied">L\'autorizzazione è stata negata.</string>
<string name="error_retrieving_oauth_token">Errore nell\'acquisizione del token di accesso.</string> <string name="error_retrieving_oauth_token">Errore nell\'acquisizione del token di accesso.</string>
<string name="error_compose_character_limit">Lo stato è troppo lungo!</string> <string name="error_compose_character_limit">Lo stato è troppo lungo!</string>
<string name="error_media_upload_size">La dimensione del file deve essere inferiore a 8MB.</string> <string name="error_image_upload_size">La dimensione del file deve essere inferiore a 8MB.</string>
<string name="error_media_upload_type">Questo tipo di file non può essere caricato.</string> <string name="error_media_upload_type">Questo tipo di file non può essere caricato.</string>
<string name="error_media_upload_opening">Questo file non può essere aperto.</string> <string name="error_media_upload_opening">Questo file non può essere aperto.</string>
<string name="error_media_upload_permission">Il permesso di lettura della scheda sd è richiesto.</string> <string name="error_media_upload_permission">Il permesso di lettura della scheda sd è richiesto.</string>

View File

@ -9,7 +9,7 @@
<string name="error_authorization_denied">承認が拒否されました。</string> <string name="error_authorization_denied">承認が拒否されました。</string>
<string name="error_retrieving_oauth_token">ログイントークンの取得に失敗しました。</string> <string name="error_retrieving_oauth_token">ログイントークンの取得に失敗しました。</string>
<string name="error_compose_character_limit">投稿文が長すぎます!</string> <string name="error_compose_character_limit">投稿文が長すぎます!</string>
<string name="error_media_upload_size">ファイルは4MB未満にしてください。</string> <string name="error_image_upload_size">ファイルは4MB未満にしてください。</string>
<string name="error_media_upload_type">その形式のファイルはアップロードできません。</string> <string name="error_media_upload_type">その形式のファイルはアップロードできません。</string>
<string name="error_media_upload_opening">ファイルを開けませんでした。</string> <string name="error_media_upload_opening">ファイルを開けませんでした。</string>
<string name="error_media_upload_permission">メディアの読み取り許可が必要です。</string> <string name="error_media_upload_permission">メディアの読み取り許可が必要です。</string>
@ -277,4 +277,6 @@
<string name="action_set_caption">説明を設定</string> <string name="action_set_caption">説明を設定</string>
<string name="action_remove_media">消去</string> <string name="action_remove_media">消去</string>
<string name="pref_title_absolute_time">絶対時間で表示</string>
</resources> </resources>

View File

@ -10,7 +10,7 @@
<string name="error_authorization_denied">인증이 거부되었습니다.</string> <string name="error_authorization_denied">인증이 거부되었습니다.</string>
<string name="error_retrieving_oauth_token">로그인 토큰을 가져오는 데 실패했습니다.</string> <string name="error_retrieving_oauth_token">로그인 토큰을 가져오는 데 실패했습니다.</string>
<string name="error_compose_character_limit">툿이 너무 깁니다!</string> <string name="error_compose_character_limit">툿이 너무 깁니다!</string>
<string name="error_media_upload_size">파일은 8MB보다 작아야 합니다.</string> <string name="error_image_upload_size">파일은 8MB보다 작아야 합니다.</string>
<string name="error_media_upload_type">이 형태의 파일은 업로드될 수 없습니다.</string> <string name="error_media_upload_type">이 형태의 파일은 업로드될 수 없습니다.</string>
<string name="error_media_upload_opening">그 파일은 열 수 없습니다.</string> <string name="error_media_upload_opening">그 파일은 열 수 없습니다.</string>
<string name="error_media_upload_permission">미디어를 읽기 위한 권한이 필요합니다.</string> <string name="error_media_upload_permission">미디어를 읽기 위한 권한이 필요합니다.</string>

View File

@ -9,7 +9,7 @@
<string name="error_authorization_denied">Autorisatie werd geweigerd.</string> <string name="error_authorization_denied">Autorisatie werd geweigerd.</string>
<string name="error_retrieving_oauth_token">Kon geen inlogsleutel verkrijgen.</string> <string name="error_retrieving_oauth_token">Kon geen inlogsleutel verkrijgen.</string>
<string name="error_compose_character_limit">Tekst van deze toot is te lang!</string> <string name="error_compose_character_limit">Tekst van deze toot is te lang!</string>
<string name="error_media_upload_size">Bestand moet kleiner zijn dan 8MB.</string> <string name="error_image_upload_size">Bestand moet kleiner zijn dan 8MB.</string>
<string name="error_media_upload_type">Bestandstype kan niet worden geüpload.</string> <string name="error_media_upload_type">Bestandstype kan niet worden geüpload.</string>
<string name="error_media_upload_opening">Bestand kon niet worden geopend.</string> <string name="error_media_upload_opening">Bestand kon niet worden geopend.</string>
<string name="error_media_upload_permission">Er is toestemming nodig om deze media te lezen.</string> <string name="error_media_upload_permission">Er is toestemming nodig om deze media te lezen.</string>

View File

@ -9,7 +9,7 @@
<string name="error_authorization_denied">L\'autoritzacion es estada regetada.</string> <string name="error_authorization_denied">L\'autoritzacion es estada regetada.</string>
<string name="error_retrieving_oauth_token">Fracàs de lobtencion del testimoni d\'iniciacion de session.</string> <string name="error_retrieving_oauth_token">Fracàs de lobtencion del testimoni d\'iniciacion de session.</string>
<string name="error_compose_character_limit">L\'estatut es tròp long !</string> <string name="error_compose_character_limit">L\'estatut es tròp long !</string>
<string name="error_media_upload_size">Lo fichièr a dèsser inferior a 8Mo.</string> <string name="error_image_upload_size">Lo fichièr a dèsser inferior a 8Mo.</string>
<string name="error_media_upload_type">Aqueste tip de fichièr se pòt pas mandar.</string> <string name="error_media_upload_type">Aqueste tip de fichièr se pòt pas mandar.</string>
<string name="error_media_upload_opening">Aqueste tip de fichièr se pòt pas dobrir.</string> <string name="error_media_upload_opening">Aqueste tip de fichièr se pòt pas dobrir.</string>
<string name="error_media_upload_permission">Cal permís de lectura del mèdia.</string> <string name="error_media_upload_permission">Cal permís de lectura del mèdia.</string>

View File

@ -9,7 +9,7 @@
<string name="error_authorization_denied">Odmówiono autoryzacji.</string> <string name="error_authorization_denied">Odmówiono autoryzacji.</string>
<string name="error_retrieving_oauth_token">Nie udało się uzyskać tokenu logowania.</string> <string name="error_retrieving_oauth_token">Nie udało się uzyskać tokenu logowania.</string>
<string name="error_compose_character_limit">Zbyt długi wpis!</string> <string name="error_compose_character_limit">Zbyt długi wpis!</string>
<string name="error_media_upload_size">Plik może mieć maksymalnie 8 MB.</string> <string name="error_image_upload_size">Plik może mieć maksymalnie 8 MB.</string>
<string name="error_media_upload_type">Ten format pliku nie może zostać wysłany.</string> <string name="error_media_upload_type">Ten format pliku nie może zostać wysłany.</string>
<string name="error_media_upload_opening">Nie można otworzyć tego pliku.</string> <string name="error_media_upload_opening">Nie można otworzyć tego pliku.</string>
<string name="error_media_upload_permission">Wymagane jest pozwolenie na dostęp do plików z urządzenia.</string> <string name="error_media_upload_permission">Wymagane jest pozwolenie na dostęp do plików z urządzenia.</string>

View File

@ -9,7 +9,7 @@
<string name="error_authorization_denied">Autorização negada.</string> <string name="error_authorization_denied">Autorização negada.</string>
<string name="error_retrieving_oauth_token">Falha ao adquirir token de entrada.</string> <string name="error_retrieving_oauth_token">Falha ao adquirir token de entrada.</string>
<string name="error_compose_character_limit">A postagem é muito longa!</string> <string name="error_compose_character_limit">A postagem é muito longa!</string>
<string name="error_media_upload_size">O arquivo deve ser menor que 8MB.</string> <string name="error_image_upload_size">O arquivo deve ser menor que 8MB.</string>
<string name="error_media_upload_type">Esse tipo de arquivo não pode ser enviado.</string> <string name="error_media_upload_type">Esse tipo de arquivo não pode ser enviado.</string>
<string name="error_media_upload_opening">Esse arquvo não pode ser aberto.</string> <string name="error_media_upload_opening">Esse arquvo não pode ser aberto.</string>
<string name="error_media_upload_permission">Permissão para ler mídia é necessária.</string> <string name="error_media_upload_permission">Permissão para ler mídia é necessária.</string>

View File

@ -9,7 +9,7 @@
<string name="error_authorization_denied">Авторизация была отклонена.</string> <string name="error_authorization_denied">Авторизация была отклонена.</string>
<string name="error_retrieving_oauth_token">Не удалось получить токен авторизации.</string> <string name="error_retrieving_oauth_token">Не удалось получить токен авторизации.</string>
<string name="error_compose_character_limit">Статус слишком длинный!</string> <string name="error_compose_character_limit">Статус слишком длинный!</string>
<string name="error_media_upload_size">Файл должен быть не больше 8 Мбайт.</string> <string name="error_image_upload_size">Файл должен быть не больше 8 Мбайт.</string>
<string name="error_media_upload_type">Данный тип файла не может быть загружен.</string> <string name="error_media_upload_type">Данный тип файла не может быть загружен.</string>
<string name="error_media_upload_opening">Файл не может быть открыт.</string> <string name="error_media_upload_opening">Файл не может быть открыт.</string>
<string name="error_media_upload_permission">Необходимо разрешение на чтение медиаконтента.</string> <string name="error_media_upload_permission">Необходимо разрешение на чтение медиаконтента.</string>

View File

@ -10,7 +10,7 @@
<string name="error_authorization_denied">Ingen behörighet.</string> <string name="error_authorization_denied">Ingen behörighet.</string>
<string name="error_retrieving_oauth_token">Misslyckades med att få en inloggnings-token.</string> <string name="error_retrieving_oauth_token">Misslyckades med att få en inloggnings-token.</string>
<string name="error_compose_character_limit">Statusen är för lång!</string> <string name="error_compose_character_limit">Statusen är för lång!</string>
<string name="error_media_upload_size">Filen måste vara mindre än 8MB.</string> <string name="error_image_upload_size">Filen måste vara mindre än 8MB.</string>
<string name="error_media_upload_type">Den typen av fil kan inte laddas upp.</string> <string name="error_media_upload_type">Den typen av fil kan inte laddas upp.</string>
<string name="error_media_upload_opening">Den filen kunde inte öppnas.</string> <string name="error_media_upload_opening">Den filen kunde inte öppnas.</string>
<string name="error_media_upload_permission">Tillstånd att läsa media krävs.</string> <string name="error_media_upload_permission">Tillstånd att läsa media krävs.</string>

View File

@ -8,7 +8,7 @@
<string name="error_authorization_denied">அங்கீகாரம் மறுக்கப்பட்டுள்ளது</string> <string name="error_authorization_denied">அங்கீகாரம் மறுக்கப்பட்டுள்ளது</string>
<string name="error_retrieving_oauth_token">உள்நுழைவு டோக்கனைப் பெறுவதில் தோல்வி.</string> <string name="error_retrieving_oauth_token">உள்நுழைவு டோக்கனைப் பெறுவதில் தோல்வி.</string>
<string name="error_compose_character_limit">நிலை மிக நீளமாக உள்ளது!</string> <string name="error_compose_character_limit">நிலை மிக நீளமாக உள்ளது!</string>
<string name="error_media_upload_size">கோப்பு 4MB-க்கும் குறைவாக இருக்க வேண்டும்.</string> <string name="error_image_upload_size">கோப்பு 4MB-க்கும் குறைவாக இருக்க வேண்டும்.</string>
<string name="error_media_upload_type">இந்த வகை கோப்பை பதிவேற்ற முடியாது.</string> <string name="error_media_upload_type">இந்த வகை கோப்பை பதிவேற்ற முடியாது.</string>
<string name="error_media_upload_opening">அந்த கோப்பை திறக்க முடியவில்லை.</string> <string name="error_media_upload_opening">அந்த கோப்பை திறக்க முடியவில்லை.</string>
<string name="error_media_upload_permission">ஊடகத்தை படிக்க அனுமதி தேவை.</string> <string name="error_media_upload_permission">ஊடகத்தை படிக்க அனுமதி தேவை.</string>

View File

@ -9,7 +9,7 @@
<string name="error_authorization_denied">Kimlik doğrulama reddedildi.</string> <string name="error_authorization_denied">Kimlik doğrulama reddedildi.</string>
<string name="error_retrieving_oauth_token">Giriş jetonu alınamadı.</string> <string name="error_retrieving_oauth_token">Giriş jetonu alınamadı.</string>
<string name="error_compose_character_limit">İleti fazlasıyla uzun!</string> <string name="error_compose_character_limit">İleti fazlasıyla uzun!</string>
<string name="error_media_upload_size">Dosya 8MB\'ten küçük olmalı.</string> <string name="error_image_upload_size">Dosya 8MB\'ten küçük olmalı.</string>
<string name="error_media_upload_type">O biçim dosya yüklenmez.</string> <string name="error_media_upload_type">O biçim dosya yüklenmez.</string>
<string name="error_media_upload_opening">O dosya açılamadı.</string> <string name="error_media_upload_opening">O dosya açılamadı.</string>
<string name="error_media_upload_permission">Medya okuma izni gerekiyor.</string> <string name="error_media_upload_permission">Medya okuma izni gerekiyor.</string>

View File

@ -9,7 +9,7 @@
<string name="error_authorization_denied">授权被拒绝。</string> <string name="error_authorization_denied">授权被拒绝。</string>
<string name="error_retrieving_oauth_token">无法获取登录信息。</string> <string name="error_retrieving_oauth_token">无法获取登录信息。</string>
<string name="error_compose_character_limit">嘟文太长了!</string> <string name="error_compose_character_limit">嘟文太长了!</string>
<string name="error_media_upload_size">文件大小限制 8MB。</string> <string name="error_image_upload_size">文件大小限制 8MB。</string>
<string name="error_media_upload_type">无法上传此类型的文件。</string> <string name="error_media_upload_type">无法上传此类型的文件。</string>
<string name="error_media_upload_opening">此文件无法打开。</string> <string name="error_media_upload_opening">此文件无法打开。</string>
<string name="error_media_upload_permission">需要授予 Tusky 读取媒体文件的权限。</string> <string name="error_media_upload_permission">需要授予 Tusky 读取媒体文件的权限。</string>

View File

@ -9,7 +9,7 @@
<string name="error_authorization_denied">授權被拒絕。</string> <string name="error_authorization_denied">授權被拒絕。</string>
<string name="error_retrieving_oauth_token">無法獲取登錄信息。</string> <string name="error_retrieving_oauth_token">無法獲取登錄信息。</string>
<string name="error_compose_character_limit">嘟文太長了!</string> <string name="error_compose_character_limit">嘟文太長了!</string>
<string name="error_media_upload_size">文件大小限制 8MB。</string> <string name="error_image_upload_size">文件大小限制 8MB。</string>
<string name="error_media_upload_type">無法上傳此類型的文件。</string> <string name="error_media_upload_type">無法上傳此類型的文件。</string>
<string name="error_media_upload_opening">此文件無法打開。</string> <string name="error_media_upload_opening">此文件無法打開。</string>
<string name="error_media_upload_permission">需要授予 Tusky 讀取媒體文件的權限。</string> <string name="error_media_upload_permission">需要授予 Tusky 讀取媒體文件的權限。</string>

View File

@ -9,7 +9,7 @@
<string name="error_authorization_denied">授權被拒絕。</string> <string name="error_authorization_denied">授權被拒絕。</string>
<string name="error_retrieving_oauth_token">無法獲取登錄信息。</string> <string name="error_retrieving_oauth_token">無法獲取登錄信息。</string>
<string name="error_compose_character_limit">嘟文太長了!</string> <string name="error_compose_character_limit">嘟文太長了!</string>
<string name="error_media_upload_size">文件大小限制 8MB。</string> <string name="error_image_upload_size">文件大小限制 8MB。</string>
<string name="error_media_upload_type">無法上傳此類型的文件。</string> <string name="error_media_upload_type">無法上傳此類型的文件。</string>
<string name="error_media_upload_opening">此文件無法打開。</string> <string name="error_media_upload_opening">此文件無法打開。</string>
<string name="error_media_upload_permission">需要授予 Tusky 讀取媒體文件的權限。</string> <string name="error_media_upload_permission">需要授予 Tusky 讀取媒體文件的權限。</string>

View File

@ -9,7 +9,7 @@
<string name="error_authorization_denied">授权被拒绝。</string> <string name="error_authorization_denied">授权被拒绝。</string>
<string name="error_retrieving_oauth_token">无法获取登录信息。</string> <string name="error_retrieving_oauth_token">无法获取登录信息。</string>
<string name="error_compose_character_limit">嘟文太长了!</string> <string name="error_compose_character_limit">嘟文太长了!</string>
<string name="error_media_upload_size">文件大小限制 8MB。</string> <string name="error_image_upload_size">文件大小限制 8MB。</string>
<string name="error_media_upload_type">无法上传此类型的文件。</string> <string name="error_media_upload_type">无法上传此类型的文件。</string>
<string name="error_media_upload_opening">此文件无法打开。</string> <string name="error_media_upload_opening">此文件无法打开。</string>
<string name="error_media_upload_permission">需要授予 Tusky 读取媒体文件的权限。</string> <string name="error_media_upload_permission">需要授予 Tusky 读取媒体文件的权限。</string>

View File

@ -9,7 +9,7 @@
<string name="error_authorization_denied">授權被拒絕。</string> <string name="error_authorization_denied">授權被拒絕。</string>
<string name="error_retrieving_oauth_token">無法獲取登錄信息。</string> <string name="error_retrieving_oauth_token">無法獲取登錄信息。</string>
<string name="error_compose_character_limit">嘟文太長了!</string> <string name="error_compose_character_limit">嘟文太長了!</string>
<string name="error_media_upload_size">文件大小限制 8MB。</string> <string name="error_image_upload_size">文件大小限制 8MB。</string>
<string name="error_media_upload_type">無法上傳此類型的文件。</string> <string name="error_media_upload_type">無法上傳此類型的文件。</string>
<string name="error_media_upload_opening">此文件無法打開。</string> <string name="error_media_upload_opening">此文件無法打開。</string>
<string name="error_media_upload_permission">需要授予 Tusky 讀取媒體文件的權限。</string> <string name="error_media_upload_permission">需要授予 Tusky 讀取媒體文件的權限。</string>

View File

@ -9,7 +9,8 @@
<string name="error_authorization_denied">Authorization was denied.</string> <string name="error_authorization_denied">Authorization was denied.</string>
<string name="error_retrieving_oauth_token">Failed getting a login token.</string> <string name="error_retrieving_oauth_token">Failed getting a login token.</string>
<string name="error_compose_character_limit">The status is too long!</string> <string name="error_compose_character_limit">The status is too long!</string>
<string name="error_media_upload_size">The file must be less than 8MB.</string> <string name="error_image_upload_size">The file must be less than 8MB.</string>
<string name="error_video_upload_size">Video files must be less than 40MB.</string>
<string name="error_media_upload_type">That type of file cannot be uploaded.</string> <string name="error_media_upload_type">That type of file cannot be uploaded.</string>
<string name="error_media_upload_opening">That file could not be opened.</string> <string name="error_media_upload_opening">That file could not be opened.</string>
<string name="error_media_upload_permission">Permission to read media is required.</string> <string name="error_media_upload_permission">Permission to read media is required.</string>
@ -354,4 +355,6 @@
<string name="profile_metadata_label_label">Label</string> <string name="profile_metadata_label_label">Label</string>
<string name="profile_metadata_content_label">Content</string> <string name="profile_metadata_content_label">Content</string>
<string name="pref_title_absolute_time">Use absolute time</string>
</resources> </resources>

View File

@ -48,6 +48,12 @@
android:title="@string/pref_appearance_long_posts_title" android:title="@string/pref_appearance_long_posts_title"
android:summaryOn="@string/pref_appearance_long_posts_enabled" android:summaryOn="@string/pref_appearance_long_posts_enabled"
android:summaryOff="@string/pref_appearance_long_posts_disabled"/> android:summaryOff="@string/pref_appearance_long_posts_disabled"/>
<CheckBoxPreference
android:defaultValue="false"
android:key="absoluteTimeView"
android:title="@string/pref_title_absolute_time" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="@string/pref_publishing"> <PreferenceCategory android:title="@string/pref_publishing">