diff --git a/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java b/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java index f36cff2ac..8cbfb7292 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java @@ -101,7 +101,6 @@ import com.keylesspalace.tusky.network.ProgressRequestBody; import com.keylesspalace.tusky.service.SendTootService; import com.keylesspalace.tusky.util.CountUpDownLatch; import com.keylesspalace.tusky.util.DownsizeImageTask; -import com.keylesspalace.tusky.util.IOUtils; import com.keylesspalace.tusky.util.ListUtils; import com.keylesspalace.tusky.util.MediaUtils; import com.keylesspalace.tusky.util.MentionTokenizer; @@ -124,7 +123,6 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -159,8 +157,9 @@ public final class ComposeActivity private static final String TAG = "ComposeActivity"; // logging tag static final int STATUS_CHARACTER_LIMIT = 500; - private static final int STATUS_MEDIA_SIZE_LIMIT = 8388608; // 8MiB - private static final int STATUS_MEDIA_PIXEL_SIZE_LIMIT = 16777216; // 4096^2 Pixels + private static final int STATUS_IMAGE_SIZE_LIMIT = 8388608; // 8MiB + 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_TAKE_PHOTO_RESULT = 2; private static final int PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1; @@ -995,8 +994,8 @@ public final class ComposeActivity @NonNull private File createNewImageFile() throws IOException { // Create an image file name - String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date()); - String imageFileName = "Tusky_" + timeStamp + "_"; + String randomId = StringUtils.randomAlphanumericString(12); + String imageFileName = "Tusky_" + randomId + "_"; File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES); return File.createTempFile( imageFileName, /* prefix */ @@ -1088,12 +1087,12 @@ public final class ComposeActivity try { 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); } else { uploadMedia(item); } - } catch (FileNotFoundException e) { + } catch (IOException e) { 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; - new DownsizeImageTask(STATUS_MEDIA_SIZE_LIMIT, getContentResolver(), + new DownsizeImageTask(STATUS_IMAGE_SIZE_LIMIT, getContentResolver(), createNewImageFile(), new DownsizeImageTask.Listener() { @Override - public void onSuccess(List contentList) { - item.content = contentList.get(0); + public void onSuccess(File tempFile) { + item.uri = FileProvider.getUriForFile( + ComposeActivity.this, + BuildConfig.APPLICATION_ID+".fileprovider", + tempFile); uploadMedia(item); } @@ -1242,7 +1244,7 @@ public final class ComposeActivity } private void onMediaDownsizeFailure(QueuedMedia item) { - displayTransientError(R.string.error_media_upload_size); + displayTransientError(R.string.error_image_upload_size); removeMediaFromQueue(item); } @@ -1258,32 +1260,20 @@ public final class ComposeActivity StringUtils.randomAlphanumericString(10), fileExtension); - byte[] content = item.content; + InputStream stream; - if (content == null) { - InputStream stream; - - try { - stream = getContentResolver().openInputStream(item.uri); - } catch (FileNotFoundException e) { - Log.d(TAG, Log.getStackTraceString(e)); - return; - } - - content = MediaUtils.inputStreamGetBytes(stream); - IOUtils.closeQuietly(stream); - - if (content == null) { - return; - } + try { + stream = getContentResolver().openInputStream(item.uri); + } catch (FileNotFoundException e) { + Log.w(TAG, e); + return; } if (mimeType == null) mimeType = "multipart/form-data"; item.preview.setProgress(0); - ProgressRequestBody fileBody = new ProgressRequestBody(content, MediaType.parse(mimeType), - false, // If request body logging is enabled, pass true + ProgressRequestBody fileBody = new ProgressRequestBody(stream, MediaUtils.getMediaSize(getContentResolver(), item.uri), MediaType.parse(mimeType), new ProgressRequestBody.UploadCallback() { // may reference activity longer than I would like to int lastProgress = -1; @@ -1378,8 +1368,8 @@ public final class ComposeActivity String topLevelType = mimeType.substring(0, mimeType.indexOf('/')); switch (topLevelType) { case "video": { - if (mediaSize > STATUS_MEDIA_SIZE_LIMIT) { - displayTransientError(R.string.error_media_upload_size); + if (mediaSize > STATUS_VIDEO_SIZE_LIMIT) { + displayTransientError(R.string.error_image_upload_size); return; } if (mediaQueued.size() > 0 @@ -1543,7 +1533,6 @@ public final class ComposeActivity String id; Call uploadRequest; ReadyStage readyStage; - byte[] content; long mediaSize; String description; diff --git a/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt b/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt index 1e426df0a..c24128b3c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt @@ -53,8 +53,8 @@ class EditProfileActivity : BaseActivity(), Injectable { companion object { const val AVATAR_SIZE = 400 - const val HEADER_WIDTH = 700 - const val HEADER_HEIGHT = 335 + const val HEADER_WIDTH = 1500 + const val HEADER_HEIGHT = 500 private const val AVATAR_PICK_RESULT = 1 private const val HEADER_PICK_RESULT = 2 diff --git a/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.java b/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.java index 671310893..c4f002efe 100644 --- a/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.java @@ -61,7 +61,7 @@ public class PreferencesActivity extends BaseActivity preferences.registerOnSharedPreferenceChangeListener(this); - if(savedInstanceState == null) { + if (savedInstanceState == null) { currentPreferences = R.xml.preferences; currentTitle = R.string.action_view_preferences; } else { @@ -124,6 +124,10 @@ public class PreferencesActivity extends BaseActivity restartActivitiesOnExit = true; break; } + case "absoluteTimeView": { + restartActivitiesOnExit = true; + break; + } case "notificationsEnabled": { boolean enabled = sharedPreferences.getBoolean("notificationsEnabled", true); if (enabled) { @@ -145,14 +149,14 @@ public class PreferencesActivity extends BaseActivity @Override public void onBackPressed() { //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); } else { - /* 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 - * is hijack the back button press and use it to launch a new MainActivity and clear 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 + * is hijack the back button press and use it to launch a new MainActivity and clear the + * back stack. */ if (restartActivitiesOnExit) { Intent intent = new Intent(this, MainActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java index 47e719c3c..0a894eced 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java @@ -52,9 +52,11 @@ import com.keylesspalace.tusky.viewdata.NotificationViewData; import com.keylesspalace.tusky.viewdata.StatusViewData; import com.squareup.picasso.Picasso; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.Locale; public class NotificationsAdapter extends RecyclerView.Adapter { private static final int VIEW_TYPE_MENTION = 0; @@ -66,6 +68,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter { private StatusActionListener statusListener; private NotificationActionListener notificationActionListener; private boolean mediaPreviewEnabled; + private boolean useAbsoluteTime; private BidiFormatter bidiFormatter; public NotificationsAdapter(StatusActionListener statusListener, @@ -75,6 +78,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter { this.statusListener = statusListener; this.notificationActionListener = notificationActionListener; mediaPreviewEnabled = true; + useAbsoluteTime = false; bidiFormatter = BidiFormatter.getInstance(); } @@ -86,12 +90,12 @@ public class NotificationsAdapter extends RecyclerView.Adapter { case VIEW_TYPE_MENTION: { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_status, parent, false); - return new StatusViewHolder(view); + return new StatusViewHolder(view, useAbsoluteTime); } case VIEW_TYPE_STATUS_NOTIFICATION: { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_status_notification, parent, false); - return new StatusNotificationViewHolder(view); + return new StatusNotificationViewHolder(view, useAbsoluteTime); } case VIEW_TYPE_FOLLOW: { View view = LayoutInflater.from(parent.getContext()) @@ -132,7 +136,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter { StatusNotificationViewHolder holder = (StatusNotificationViewHolder) viewHolder; StatusViewData.Concrete statusViewData = concreteNotificaton.getStatusViewData(); - if(statusViewData == null) { + if (statusViewData == null) { holder.showNotificationContent(false); } else { holder.showNotificationContent(true); @@ -230,6 +234,10 @@ public class NotificationsAdapter extends RecyclerView.Adapter { return mediaPreviewEnabled; } + public void setUseAbsoluteTime(boolean useAbsoluteTime) { + this.useAbsoluteTime = useAbsoluteTime; + } + public interface NotificationActionListener { void onViewAccount(String id); @@ -317,7 +325,11 @@ public class NotificationsAdapter extends RecyclerView.Adapter { private NotificationActionListener notificationActionListener; private StatusViewData.Concrete statusViewData; - StatusNotificationViewHolder(View itemView) { + private boolean useAbsoluteTime; + private SimpleDateFormat shortSdf; + private SimpleDateFormat longSdf; + + StatusNotificationViewHolder(View itemView, boolean useAbsoluteTime) { super(itemView); message = itemView.findViewById(R.id.notification_top_text); statusNameBar = itemView.findViewById(R.id.status_name_bar); @@ -340,6 +352,10 @@ public class NotificationsAdapter extends RecyclerView.Adapter { message.setOnClickListener(this); statusContent.setOnClickListener(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) { @@ -363,26 +379,40 @@ public class NotificationsAdapter extends RecyclerView.Adapter { 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); + protected void setCreatedAt(@Nullable Date createdAt) { + if (useAbsoluteTime) { + String time; + if (createdAt != null) { + if (System.currentTimeMillis() - createdAt.getTime() > 86400000L) { + time = longSdf.format(createdAt); + } else { + time = shortSdf.format(createdAt); + } + } else { + time = "??:??:??"; + } + timestampInfo.setText(time); } else { - // unknown minutes~ - readout = "?m"; - readoutAloud = "? minutes"; + // 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); } - timestampInfo.setText(readout); - timestampInfo.setContentDescription(readoutAloud); } void setMessage(NotificationViewData.Concrete notificationViewData, LinkListener listener, BidiFormatter bidiFormatter) { @@ -408,7 +438,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter { } case REBLOG: { icon = ContextCompat.getDrawable(context, R.drawable.ic_repeat_24dp); - if(icon != null) { + if (icon != null) { icon.setColorFilter(ContextCompat.getColor(context, R.color.color_accent_dark), PorterDuff.Mode.SRC_ATOP); } @@ -469,10 +499,12 @@ public class NotificationsAdapter extends RecyclerView.Adapter { switch (v.getId()) { case R.id.notification_container: case R.id.notification_content: - if (notificationActionListener != null) notificationActionListener.onViewStatusForNotificationId(notificationId); + if (notificationActionListener != null) + notificationActionListener.onViewStatusForNotificationId(notificationId); break; case R.id.notification_top_text: - if (notificationActionListener != null) notificationActionListener.onViewAccount(accountId); + if (notificationActionListener != null) + notificationActionListener.onViewAccount(accountId); break; } } diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/SearchResultsAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/SearchResultsAdapter.java index ce3c718ce..b797c0888 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/SearchResultsAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/SearchResultsAdapter.java @@ -49,6 +49,7 @@ public class SearchResultsAdapter extends RecyclerView.Adapter { private boolean mediaPreviewsEnabled; private boolean alwaysShowSensitiveMedia; private boolean collapseLongStatusContent; + private boolean useAbsoluteTime; private LinkListener linkListener; private StatusActionListener statusListener; @@ -57,7 +58,8 @@ public class SearchResultsAdapter extends RecyclerView.Adapter { boolean alwaysShowSensitiveMedia, boolean collapseLongStatusContent, LinkListener linkListener, - StatusActionListener statusListener) { + StatusActionListener statusListener, + boolean useAbsoluteTime) { this.accountList = Collections.emptyList(); this.statusList = Collections.emptyList(); @@ -67,6 +69,7 @@ public class SearchResultsAdapter extends RecyclerView.Adapter { this.mediaPreviewsEnabled = mediaPreviewsEnabled; this.alwaysShowSensitiveMedia = alwaysShowSensitiveMedia; this.collapseLongStatusContent = collapseLongStatusContent; + this.useAbsoluteTime = useAbsoluteTime; this.linkListener = linkListener; this.statusListener = statusListener; @@ -91,7 +94,7 @@ public class SearchResultsAdapter extends RecyclerView.Adapter { case VIEW_TYPE_STATUS: { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_status, parent, false); - return new StatusViewHolder(view); + return new StatusViewHolder(view, useAbsoluteTime); } } } diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java index e39b49209..cffa51604 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java @@ -32,8 +32,10 @@ import com.keylesspalace.tusky.viewdata.StatusViewData; import com.mikepenz.iconics.utils.Utils; import com.squareup.picasso.Picasso; +import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; +import java.util.Locale; import at.connyduck.sparkbutton.SparkButton; import at.connyduck.sparkbutton.SparkEventListener; @@ -67,7 +69,11 @@ abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { TextView content; TextView contentWarningDescription; - StatusBaseViewHolder(View itemView) { + private boolean useAbsoluteTime; + private SimpleDateFormat shortSdf; + private SimpleDateFormat longSdf; + + StatusBaseViewHolder(View itemView, boolean useAbsoluteTime) { super(itemView); container = itemView.findViewById(R.id.status_container); 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); contentWarningButton = itemView.findViewById(R.id.status_content_warning_button); 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); @@ -130,25 +140,39 @@ abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { } protected 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); + if (useAbsoluteTime) { + String time; + if (createdAt != null) { + if (System.currentTimeMillis() - createdAt.getTime() > 86400000L) { + time = longSdf.format(createdAt); + } else { + time = shortSdf.format(createdAt); + } + } else { + time = "??:??:??"; + } + timestampInfo.setText(time); } else { - // unknown minutes~ - readout = "?m"; - readoutAloud = "? minutes"; + // 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); } - timestampInfo.setText(readout); - timestampInfo.setContentDescription(readoutAloud); } protected void showContent(boolean show) { @@ -260,7 +284,7 @@ abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { final int urlIndex = i; previews[i].setOnClickListener(v -> { - if(getAdapterPosition() != RecyclerView.NO_POSITION) { + if (getAdapterPosition() != RecyclerView.NO_POSITION) { listener.onViewMedia(getAdapterPosition(), urlIndex, v); } }); diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusDetailedViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusDetailedViewHolder.java index 88d5b7183..d0ab5b8ae 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusDetailedViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusDetailedViewHolder.java @@ -41,7 +41,7 @@ class StatusDetailedViewHolder extends StatusBaseViewHolder { private TextView cardUrl; StatusDetailedViewHolder(View view) { - super(view); + super(view, false); reblogs = view.findViewById(R.id.status_reblogs); favourites = view.findViewById(R.id.status_favourites); cardView = view.findViewById(R.id.card_view); diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java index fcd96e523..a1c940363 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java @@ -34,8 +34,8 @@ public class StatusViewHolder extends StatusBaseViewHolder { private ImageView avatarReblog; private TextView rebloggedBar; - StatusViewHolder(View itemView) { - super(itemView); + StatusViewHolder(View itemView, boolean useAbsoluteTime) { + super(itemView, useAbsoluteTime); avatarReblog = itemView.findViewById(R.id.status_avatar_reblog); 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 diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/ThreadAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/ThreadAdapter.java index cf2fadb15..609c495f0 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/ThreadAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/ThreadAdapter.java @@ -36,12 +36,14 @@ public class ThreadAdapter extends RecyclerView.Adapter { private List statuses; private StatusActionListener statusActionListener; private boolean mediaPreviewEnabled; + private boolean useAbsoluteTime; private int detailedStatusPosition; public ThreadAdapter(StatusActionListener listener) { this.statusActionListener = listener; this.statuses = new ArrayList<>(); mediaPreviewEnabled = true; + useAbsoluteTime = false; detailedStatusPosition = RecyclerView.NO_POSITION; } @@ -53,7 +55,7 @@ public class ThreadAdapter extends RecyclerView.Adapter { case VIEW_TYPE_STATUS: { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_status, parent, false); - return new StatusViewHolder(view); + return new StatusViewHolder(view, useAbsoluteTime); } case VIEW_TYPE_STATUS_DETAILED: { View view = LayoutInflater.from(parent.getContext()) @@ -149,6 +151,10 @@ public class ThreadAdapter extends RecyclerView.Adapter { mediaPreviewEnabled = enabled; } + public void setUseAbsoluteTime(boolean useAbsoluteTime) { + this.useAbsoluteTime = useAbsoluteTime; + } + public void setDetailedStatusPosition(int position) { if (position != detailedStatusPosition && detailedStatusPosition != RecyclerView.NO_POSITION) { diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/TimelineAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/TimelineAdapter.java index db7a519c6..29be60513 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/TimelineAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/TimelineAdapter.java @@ -39,6 +39,7 @@ public final class TimelineAdapter extends RecyclerView.Adapter { private final AdapterDataSource dataSource; private final StatusActionListener statusListener; private boolean mediaPreviewEnabled; + private boolean useAbsoluteTime; public TimelineAdapter(AdapterDataSource dataSource, StatusActionListener statusListener) { @@ -46,6 +47,7 @@ public final class TimelineAdapter extends RecyclerView.Adapter { this.dataSource = dataSource; this.statusListener = statusListener; mediaPreviewEnabled = true; + useAbsoluteTime = false; } @NonNull @@ -56,7 +58,7 @@ public final class TimelineAdapter extends RecyclerView.Adapter { case VIEW_TYPE_STATUS: { View view = LayoutInflater.from(viewGroup.getContext()) .inflate(R.layout.item_status, viewGroup, false); - return new StatusViewHolder(view); + return new StatusViewHolder(view, useAbsoluteTime); } case VIEW_TYPE_PLACEHOLDER: { View view = LayoutInflater.from(viewGroup.getContext()) @@ -97,6 +99,10 @@ public final class TimelineAdapter extends RecyclerView.Adapter { mediaPreviewEnabled = enabled; } + public void setUseAbsoluteTime(boolean useAbsoluteTime){ + this.useAbsoluteTime=useAbsoluteTime; + } + public boolean getMediaPreviewEnabled() { return mediaPreviewEnabled; } diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java index 859335536..fac113f55 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java @@ -202,6 +202,8 @@ public class NotificationsFragment extends SFragment implements collapseLongStatusContent = preferences.getBoolean("collapseLongStatuses", true); boolean mediaPreviewEnabled = preferences.getBoolean("mediaPreviewEnabled", true); adapter.setMediaPreviewEnabled(mediaPreviewEnabled); + boolean useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false); + adapter.setUseAbsoluteTime(useAbsoluteTime); recyclerView.setAdapter(adapter); notifications.clear(); diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/SearchFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/SearchFragment.kt index ff40159d5..2ce9ae537 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/SearchFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/SearchFragment.kt @@ -51,7 +51,7 @@ class SearchFragment : SFragment(), StatusActionListener, Injectable { private var alwaysShowSensitiveMedia = false private var mediaPreviewEnabled = true private var collapseLongStatusContent = true; - + private var useAbsoluteTime = false override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_search, container, false) @@ -61,6 +61,7 @@ class SearchFragment : SFragment(), StatusActionListener, Injectable { val preferences = PreferenceManager.getDefaultSharedPreferences(view.context) alwaysShowSensitiveMedia = preferences.getBoolean("alwaysShowSensitiveMedia", false) mediaPreviewEnabled = preferences.getBoolean("mediaPreviewEnabled", true) + useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false) collapseLongStatusContent = preferences.getBoolean("collapseLongStatuses", true); @@ -71,8 +72,7 @@ class SearchFragment : SFragment(), StatusActionListener, Injectable { alwaysShowSensitiveMedia, collapseLongStatusContent, this, - this - ) + useAbsoluteTime) searchRecyclerView.adapter = searchAdapter } diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java index 8e92bb283..bc5cd4a2a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java @@ -249,6 +249,8 @@ public class TimelineFragment extends SFragment implements alwaysShowSensitiveMedia = preferences.getBoolean("alwaysShowSensitiveMedia", false); boolean mediaPreviewEnabled = preferences.getBoolean("mediaPreviewEnabled", true); adapter.setMediaPreviewEnabled(mediaPreviewEnabled); + boolean useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false); + adapter.setUseAbsoluteTime(useAbsoluteTime); boolean filter = preferences.getBoolean("tabFilterHomeReplies", true); filterRemoveReplies = kind == Kind.HOME && !filter; @@ -638,7 +640,7 @@ public class TimelineFragment extends SFragment implements case "mediaPreviewEnabled": { boolean enabled = sharedPreferences.getBoolean("mediaPreviewEnabled", true); boolean oldMediaPreviewEnabled = adapter.getMediaPreviewEnabled(); - if(enabled != oldMediaPreviewEnabled) { + if (enabled != oldMediaPreviewEnabled) { adapter.setMediaPreviewEnabled(enabled); fullyRefresh(); } @@ -867,7 +869,7 @@ public class TimelineFragment extends SFragment implements } private void onFetchTimelineFailure(Exception exception, FetchEnd fetchEnd, int position) { - if(isAdded()) { + if (isAdded()) { swipeRefreshLayout.setRefreshing(false); if (fetchEnd == FetchEnd.MIDDLE && !statuses.get(position).isRight()) { @@ -1089,7 +1091,7 @@ public class TimelineFragment extends SFragment implements private final ListUpdateCallback listUpdateCallback = new ListUpdateCallback() { @Override public void onInserted(int position, int count) { - if(isAdded()) { + if (isAdded()) { adapter.notifyItemRangeInserted(position, count); Context context = getContext(); if (position == 0 && context != null) { diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java index b80c64bc5..9031b49ac 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java @@ -163,6 +163,8 @@ public final class ViewThreadFragment extends SFragment implements collapseLongStatusContent = preferences.getBoolean("collapseLongStatuses", true); boolean mediaPreviewEnabled = preferences.getBoolean("mediaPreviewEnabled", true); adapter.setMediaPreviewEnabled(mediaPreviewEnabled); + boolean useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false); + adapter.setUseAbsoluteTime(useAbsoluteTime); recyclerView.setAdapter(adapter); statuses.clear(); diff --git a/app/src/main/java/com/keylesspalace/tusky/network/ProgressRequestBody.java b/app/src/main/java/com/keylesspalace/tusky/network/ProgressRequestBody.java index 2851d47e7..69b314281 100644 --- a/app/src/main/java/com/keylesspalace/tusky/network/ProgressRequestBody.java +++ b/app/src/main/java/com/keylesspalace/tusky/network/ProgressRequestBody.java @@ -17,18 +17,18 @@ package com.keylesspalace.tusky.network; import android.support.annotation.NonNull; -import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; import okhttp3.MediaType; import okhttp3.RequestBody; import okio.BufferedSink; public final class ProgressRequestBody extends RequestBody { - private final byte[] content; - private final UploadCallback mListener; + private final InputStream content; + private final long contentLength; + private final UploadCallback uploadListener; private final MediaType mediaType; - private boolean shouldIgnoreThisPass; private static final int DEFAULT_BUFFER_SIZE = 2048; @@ -36,11 +36,11 @@ public final class ProgressRequestBody extends RequestBody { 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.contentLength = contentLength; this.mediaType = mediaType; - mListener = listener; - shouldIgnoreThisPass = shouldIgnoreFirst; + this.uploadListener = listener; } @Override @@ -50,29 +50,25 @@ public final class ProgressRequestBody extends RequestBody { @Override public long contentLength() { - return content.length; + return contentLength; } @Override public void writeTo(@NonNull BufferedSink sink) throws IOException { - long length = content.length; byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; - ByteArrayInputStream in = new ByteArrayInputStream(content); long uploaded = 0; try { int read; - while ((read = in.read(buffer)) != -1) { - if (!shouldIgnoreThisPass) { - mListener.onProgressUpdate((int)(100 * uploaded / length)); - } + while ((read = content.read(buffer)) != -1) { + uploadListener.onProgressUpdate((int)(100 * uploaded / contentLength)); + uploaded += read; sink.write(buffer, 0, read); } } finally { - in.close(); + content.close(); } - shouldIgnoreThisPass = false; } } \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/util/DownsizeImageTask.java b/app/src/main/java/com/keylesspalace/tusky/util/DownsizeImageTask.java index 3b9f04d08..9a48d5992 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/DownsizeImageTask.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/DownsizeImageTask.java @@ -21,11 +21,11 @@ import android.graphics.BitmapFactory; import android.net.Uri; import android.os.AsyncTask; -import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; +import java.io.OutputStream; /** * 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 { private int sizeLimit; private ContentResolver contentResolver; private Listener listener; - private List resultList; + private File tempFile; /** * @param sizeLimit the maximum number of bytes each image can take * @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 */ - public DownsizeImageTask(int sizeLimit, ContentResolver contentResolver, Listener listener) { + public DownsizeImageTask(int sizeLimit, ContentResolver contentResolver, File tempFile, Listener listener) { this.sizeLimit = sizeLimit; this.contentResolver = contentResolver; + this.tempFile = tempFile; this.listener = listener; } @Override protected Boolean doInBackground(Uri... uris) { - resultList = new ArrayList<>(); for (Uri uri : uris) { InputStream inputStream; try { @@ -65,8 +66,6 @@ public class DownsizeImageTask extends AsyncTask { IOUtils.closeQuietly(inputStream); // Get EXIF data, for orientation info. 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 * 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 @@ -74,7 +73,12 @@ public class DownsizeImageTask extends AsyncTask { * sure it gets downsized to below the limit. */ int scaledImageSize = 1024; do { - stream.reset(); + OutputStream stream; + try { + stream = new FileOutputStream(tempFile); + } catch (FileNotFoundException e) { + return false; + } try { inputStream = contentResolver.openInputStream(uri); } catch (FileNotFoundException e) { @@ -109,9 +113,8 @@ public class DownsizeImageTask extends AsyncTask { reorientedBitmap.compress(format, 85, stream); reorientedBitmap.recycle(); scaledImageSize /= 2; - } while (stream.size() > sizeLimit); + } while (tempFile.length() > sizeLimit); - resultList.add(stream.toByteArray()); if (isCancelled()) { return false; } @@ -122,7 +125,7 @@ public class DownsizeImageTask extends AsyncTask { @Override protected void onPostExecute(Boolean successful) { if (successful) { - listener.onSuccess(resultList); + listener.onSuccess(tempFile); } else { listener.onFailure(); } @@ -131,7 +134,7 @@ public class DownsizeImageTask extends AsyncTask { /** Used to communicate the results of the task. */ public interface Listener { - void onSuccess(List contentList); + void onSuccess(File file); void onFailure(); } } diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 019de718f..fa445b395 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -9,7 +9,7 @@ تم رفض التصريح. فشل الحصول على رمز الدخول. المنشور طويل جدا ! - يجب أن يكون حجم الملف أقل من 4 ميغابايت. + يجب أن يكون حجم الملف أقل من 4 ميغابايت. لا يمكن رفع هذا النوع من الملفات. تعذر فتح ذاك الملف. التصريح لازم لقراءة الوسائط diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 4be8342ed..453d31a4c 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -9,7 +9,7 @@ L\'autorització s\'ha denegat. L\'obtenció del testimoni d\'inici de sessió ha fallat. L\'estat és massa llarg! - El fitxer ha de ser inferior a 8MB. + El fitxer ha de ser inferior a 8MB. Aquest tipus de fitxer no es pot pujar. Aquest tipus de fitxer no es pot obrir. Cal permís de lectura del mitjà. diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index ffe0a9f50..7fef4dd99 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -9,7 +9,7 @@ Autorisierung fehlgeschlagen. Es konnte kein Login-Token abgerufen werden. Der Beitrag ist zu lang! - Die Datei muss kleiner als 8MB sein. + Die Datei muss kleiner als 8MB sein. Dieser Dateityp darf nicht hochgeladen werden. Die Datei konnte nicht geöffnet werden. Eine Leseberechtigung wird für das Hochladen der Mediendatei benötigt. diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index d025f080c..ae4d7ecbd 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -9,7 +9,7 @@ La autorización falló. Fallo al obtener identificador de login. ¡El estado es demasiado largo! - El archivo debe ser inferior a 8MB. + El archivo debe ser inferior a 8MB. No se admite este tipo de archivo. No pudo abrirse el fichero. Se requiere permiso para acceder al almacenamiento. diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 115b3bf8f..78b4d5773 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -9,7 +9,7 @@ Authentification refusée. Impossible de récupérer le jeton d’authentification. Votre pouet est trop long ! - Le fichier doit peser moins de 8 Mo. + Le fichier doit peser moins de 8 Mo. Ce type de fichier n’est pas accepté. Le fichier ne peut pas être ouvert. Permission requise pour lire ce média. diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index e3dc74e8a..f0809fba2 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -9,7 +9,7 @@ Engedélyezés letiltva. Bejelentkezési token megszerzése sikertelen. Túl hosszú a tülkölés! - A fájl kisebb kell legyen mint 8MB. + A fájl kisebb kell legyen mint 8MB. Fájl feltöltése sikertelen. Fájl megnyitása sikertelen. Média olvasási engedély szükséges. diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 94a6b68a9..dd42c4327 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -9,7 +9,7 @@ L\'autorizzazione è stata negata. Errore nell\'acquisizione del token di accesso. Lo stato è troppo lungo! - La dimensione del file deve essere inferiore a 8MB. + La dimensione del file deve essere inferiore a 8MB. Questo tipo di file non può essere caricato. Questo file non può essere aperto. Il permesso di lettura della scheda sd è richiesto. diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 5032f0527..00d2ee68d 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -9,7 +9,7 @@ 承認が拒否されました。 ログイントークンの取得に失敗しました。 投稿文が長すぎます! - ファイルは4MB未満にしてください。 + ファイルは4MB未満にしてください。 その形式のファイルはアップロードできません。 ファイルを開けませんでした。 メディアの読み取り許可が必要です。 @@ -277,4 +277,6 @@ 説明を設定 消去 + 絶対時間で表示 + diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index f6aeb3641..af122bcb6 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -10,7 +10,7 @@ 인증이 거부되었습니다. 로그인 토큰을 가져오는 데 실패했습니다. 툿이 너무 깁니다! - 파일은 8MB보다 작아야 합니다. + 파일은 8MB보다 작아야 합니다. 이 형태의 파일은 업로드될 수 없습니다. 그 파일은 열 수 없습니다. 미디어를 읽기 위한 권한이 필요합니다. diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 4a362838e..348dc3679 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -9,7 +9,7 @@ Autorisatie werd geweigerd. Kon geen inlogsleutel verkrijgen. Tekst van deze toot is te lang! - Bestand moet kleiner zijn dan 8MB. + Bestand moet kleiner zijn dan 8MB. Bestandstype kan niet worden geüpload. Bestand kon niet worden geopend. Er is toestemming nodig om deze media te lezen. diff --git a/app/src/main/res/values-oc/strings.xml b/app/src/main/res/values-oc/strings.xml index 31fd635e9..0cf465aa3 100644 --- a/app/src/main/res/values-oc/strings.xml +++ b/app/src/main/res/values-oc/strings.xml @@ -9,7 +9,7 @@ L\'autoritzacion es estada regetada. Fracàs de l’obtencion del testimoni d\'iniciacion de session. L\'estatut es tròp long ! - Lo fichièr a d’èsser inferior a 8Mo. + Lo fichièr a d’èsser inferior a 8Mo. Aqueste tip de fichièr se pòt pas mandar. Aqueste tip de fichièr se pòt pas dobrir. Cal permís de lectura del mèdia. diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index ca39bb568..5e280745f 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -9,7 +9,7 @@ Odmówiono autoryzacji. Nie udało się uzyskać tokenu logowania. Zbyt długi wpis! - Plik może mieć maksymalnie 8 MB. + Plik może mieć maksymalnie 8 MB. Ten format pliku nie może zostać wysłany. Nie można otworzyć tego pliku. Wymagane jest pozwolenie na dostęp do plików z urządzenia. diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 6851467e5..ba4926245 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -9,7 +9,7 @@ Autorização negada. Falha ao adquirir token de entrada. A postagem é muito longa! - O arquivo deve ser menor que 8MB. + O arquivo deve ser menor que 8MB. Esse tipo de arquivo não pode ser enviado. Esse arquvo não pode ser aberto. Permissão para ler mídia é necessária. diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 61cce0b42..569a98752 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -9,7 +9,7 @@ Авторизация была отклонена. Не удалось получить токен авторизации. Статус слишком длинный! - Файл должен быть не больше 8 Мбайт. + Файл должен быть не больше 8 Мбайт. Данный тип файла не может быть загружен. Файл не может быть открыт. Необходимо разрешение на чтение медиаконтента. diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 47ee4b6fb..73edf34f3 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -10,7 +10,7 @@ Ingen behörighet. Misslyckades med att få en inloggnings-token. Statusen är för lång! - Filen måste vara mindre än 8MB. + Filen måste vara mindre än 8MB. Den typen av fil kan inte laddas upp. Den filen kunde inte öppnas. Tillstånd att läsa media krävs. diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml index ae2d4444a..bec9addb0 100644 --- a/app/src/main/res/values-ta/strings.xml +++ b/app/src/main/res/values-ta/strings.xml @@ -8,7 +8,7 @@ அங்கீகாரம் மறுக்கப்பட்டுள்ளது உள்நுழைவு டோக்கனைப் பெறுவதில் தோல்வி. நிலை மிக நீளமாக உள்ளது! - கோப்பு 4MB-க்கும் குறைவாக இருக்க வேண்டும். + கோப்பு 4MB-க்கும் குறைவாக இருக்க வேண்டும். இந்த வகை கோப்பை பதிவேற்ற முடியாது. அந்த கோப்பை திறக்க முடியவில்லை. ஊடகத்தை படிக்க அனுமதி தேவை. diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index ce36046f3..ebbc4a4fd 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -9,7 +9,7 @@ Kimlik doğrulama reddedildi. Giriş jetonu alınamadı. İleti fazlasıyla uzun! - Dosya 8MB\'ten küçük olmalı. + Dosya 8MB\'ten küçük olmalı. O biçim dosya yüklenmez. O dosya açılamadı. Medya okuma izni gerekiyor. diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 51476d4ab..9c54ce8ab 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -9,7 +9,7 @@ 授权被拒绝。 无法获取登录信息。 嘟文太长了! - 文件大小限制 8MB。 + 文件大小限制 8MB。 无法上传此类型的文件。 此文件无法打开。 需要授予 Tusky 读取媒体文件的权限。 diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index c29f2836a..18cb7b33d 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -9,7 +9,7 @@ 授權被拒絕。 無法獲取登錄信息。 嘟文太長了! - 文件大小限制 8MB。 + 文件大小限制 8MB。 無法上傳此類型的文件。 此文件無法打開。 需要授予 Tusky 讀取媒體文件的權限。 diff --git a/app/src/main/res/values-zh-rMO/strings.xml b/app/src/main/res/values-zh-rMO/strings.xml index c29f2836a..18cb7b33d 100644 --- a/app/src/main/res/values-zh-rMO/strings.xml +++ b/app/src/main/res/values-zh-rMO/strings.xml @@ -9,7 +9,7 @@ 授權被拒絕。 無法獲取登錄信息。 嘟文太長了! - 文件大小限制 8MB。 + 文件大小限制 8MB。 無法上傳此類型的文件。 此文件無法打開。 需要授予 Tusky 讀取媒體文件的權限。 diff --git a/app/src/main/res/values-zh-rSG/strings.xml b/app/src/main/res/values-zh-rSG/strings.xml index 51476d4ab..9c54ce8ab 100644 --- a/app/src/main/res/values-zh-rSG/strings.xml +++ b/app/src/main/res/values-zh-rSG/strings.xml @@ -9,7 +9,7 @@ 授权被拒绝。 无法获取登录信息。 嘟文太长了! - 文件大小限制 8MB。 + 文件大小限制 8MB。 无法上传此类型的文件。 此文件无法打开。 需要授予 Tusky 读取媒体文件的权限。 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 20191fe72..e93adc35d 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -9,7 +9,7 @@ 授權被拒絕。 無法獲取登錄信息。 嘟文太長了! - 文件大小限制 8MB。 + 文件大小限制 8MB。 無法上傳此類型的文件。 此文件無法打開。 需要授予 Tusky 讀取媒體文件的權限。 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8ae41f9b0..675cf464b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -9,7 +9,8 @@ Authorization was denied. Failed getting a login token. The status is too long! - The file must be less than 8MB. + The file must be less than 8MB. + Video files must be less than 40MB. That type of file cannot be uploaded. That file could not be opened. Permission to read media is required. @@ -354,4 +355,6 @@ Label Content + Use absolute time + diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 8b209c614..e022498ec 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -48,6 +48,12 @@ android:title="@string/pref_appearance_long_posts_title" android:summaryOn="@string/pref_appearance_long_posts_enabled" android:summaryOff="@string/pref_appearance_long_posts_disabled"/> + + +