From ca3a5791e3f2454b9a94625889881c978adfb502 Mon Sep 17 00:00:00 2001 From: kyori Date: Thu, 16 Aug 2018 22:51:23 +0900 Subject: [PATCH 01/23] Show absolute time --- .../tusky/PreferencesActivity.java | 22 +++++--- .../tusky/adapter/StatusBaseViewHolder.java | 55 +++++++++++++------ app/src/main/res/values-ja/strings.xml | 2 + app/src/main/res/values/strings.xml | 2 + app/src/main/res/xml/preferences.xml | 5 ++ 5 files changed, 60 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.java b/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.java index 671310893..36a4cd321 100644 --- a/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.java @@ -34,8 +34,10 @@ public class PreferencesActivity extends BaseActivity implements SharedPreferences.OnSharedPreferenceChangeListener { private boolean restartActivitiesOnExit; - private @XmlRes int currentPreferences; - private @StringRes int currentTitle; + private @XmlRes + int currentPreferences; + private @StringRes + int currentTitle; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { @@ -61,7 +63,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 +126,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 +151,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/StatusBaseViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java index 4a920c07f..a1e2bce02 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java @@ -1,7 +1,9 @@ package com.keylesspalace.tusky.adapter; import android.content.Context; +import android.content.SharedPreferences; import android.graphics.drawable.Drawable; +import android.preference.PreferenceManager; import android.support.annotation.DrawableRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -30,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; @@ -126,25 +130,40 @@ 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); + SharedPreferences defPrefs = PreferenceManager.getDefaultSharedPreferences(timestampInfo.getContext()); + if (defPrefs.getBoolean("absoluteTimeView", true)) { + String time = "ERROR!"; + if (createdAt != null) { + SimpleDateFormat sdf; + if (new Date().getTime() - createdAt.getTime() > 86400000L) { + sdf = new SimpleDateFormat("MM/dd HH:mm:ss", Locale.getDefault()); + } else { + sdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault()); + } + time = sdf.format(createdAt); + 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) { @@ -256,7 +275,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/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 5032f0527..f3e3a2f63 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -277,4 +277,6 @@ 説明を設定 消去 + 絶対時間で表示 + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1a1ff7d01..1cac71b2e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -347,4 +347,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 1dd472b85..c9b125d61 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -42,6 +42,11 @@ android:key="alwaysShowSensitiveMedia" android:title="@string/pref_title_alway_show_sensitive_media" /> + + From 1d657a65a7760eb9efc859fbd21290400e2044ba Mon Sep 17 00:00:00 2001 From: kyori Date: Thu, 16 Aug 2018 23:10:21 +0900 Subject: [PATCH 02/23] Show absolute time in Notifications tab --- .../tusky/adapter/NotificationsAdapter.java | 55 +++++++++++++------ 1 file changed, 37 insertions(+), 18 deletions(-) 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 136805fae..a014c58f0 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java @@ -16,10 +16,12 @@ package com.keylesspalace.tusky.adapter; import android.content.Context; +import android.content.SharedPreferences; import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.Typeface; import android.graphics.drawable.Drawable; +import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.content.ContextCompat; @@ -50,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; @@ -365,26 +369,41 @@ 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) { + SharedPreferences defPrefs = PreferenceManager.getDefaultSharedPreferences(timestampInfo.getContext()); + if (defPrefs.getBoolean("absoluteTimeView", true)) { + String time = "ERROR!"; + if (createdAt != null) { + SimpleDateFormat sdf; + if (new Date().getTime() - createdAt.getTime() > 86400000L) { + sdf = new SimpleDateFormat("MM/dd HH:mm:ss", Locale.getDefault()); + } else { + sdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault()); + } + time = sdf.format(createdAt); + 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) { From e83dd16942e7fab747e01569fc6631f27302b7a9 Mon Sep 17 00:00:00 2001 From: kyori Date: Fri, 17 Aug 2018 10:19:50 +0900 Subject: [PATCH 03/23] Do not use absolute time by default. --- app/src/main/res/xml/preferences.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index c9b125d61..d53d0f1a1 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -43,7 +43,7 @@ android:title="@string/pref_title_alway_show_sensitive_media" /> From 6d1ec789840a247032fa80317c14e0808d92196e Mon Sep 17 00:00:00 2001 From: kyori Date: Fri, 17 Aug 2018 10:21:55 +0900 Subject: [PATCH 04/23] Annotate on same line. --- .../java/com/keylesspalace/tusky/PreferencesActivity.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.java b/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.java index 36a4cd321..c4f002efe 100644 --- a/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.java @@ -34,10 +34,8 @@ public class PreferencesActivity extends BaseActivity implements SharedPreferences.OnSharedPreferenceChangeListener { private boolean restartActivitiesOnExit; - private @XmlRes - int currentPreferences; - private @StringRes - int currentTitle; + private @XmlRes int currentPreferences; + private @StringRes int currentTitle; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { From 219eafe6fce2f55569ec6dad16d0c5db8f876021 Mon Sep 17 00:00:00 2001 From: kyori Date: Fri, 17 Aug 2018 11:53:38 +0900 Subject: [PATCH 05/23] Prevent query SharedPreference in adapters. --- .../tusky/adapter/NotificationsAdapter.java | 31 ++++++++++++------- .../tusky/adapter/SearchResultsAdapter.java | 7 +++-- .../tusky/adapter/StatusBaseViewHolder.java | 11 ++++--- .../adapter/StatusDetailedViewHolder.java | 2 +- .../tusky/adapter/StatusViewHolder.java | 4 +-- .../tusky/adapter/ThreadAdapter.java | 8 ++++- .../tusky/adapter/TimelineAdapter.java | 8 ++++- .../tusky/fragment/NotificationsFragment.java | 2 ++ .../tusky/fragment/SearchFragment.kt | 4 ++- .../tusky/fragment/TimelineFragment.java | 8 +++-- .../tusky/fragment/ViewThreadFragment.java | 2 ++ 11 files changed, 60 insertions(+), 27 deletions(-) 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 a014c58f0..ee69a5e48 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java @@ -16,12 +16,10 @@ package com.keylesspalace.tusky.adapter; import android.content.Context; -import android.content.SharedPreferences; import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.Typeface; import android.graphics.drawable.Drawable; -import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.content.ContextCompat; @@ -70,6 +68,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter { private NotificationActionListener notificationActionListener; private FooterViewHolder.State footerState; private boolean mediaPreviewEnabled; + private boolean useAbsoluteTime; private BidiFormatter bidiFormatter; public NotificationsAdapter(StatusActionListener statusListener, @@ -80,6 +79,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter { this.notificationActionListener = notificationActionListener; footerState = FooterViewHolder.State.END; mediaPreviewEnabled = true; + useAbsoluteTime = false; bidiFormatter = BidiFormatter.getInstance(); } @@ -91,7 +91,7 @@ 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_FOOTER: { View view = LayoutInflater.from(parent.getContext()) @@ -101,7 +101,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter { 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()) @@ -142,7 +142,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); @@ -245,6 +245,10 @@ public class NotificationsAdapter extends RecyclerView.Adapter { mediaPreviewEnabled = enabled; } + public void setUseAbsoluteTime(boolean useAbsoluteTime) { + this.useAbsoluteTime = useAbsoluteTime; + } + public interface NotificationActionListener { void onViewAccount(String id); @@ -323,7 +327,9 @@ public class NotificationsAdapter extends RecyclerView.Adapter { private NotificationActionListener notificationActionListener; private StatusViewData.Concrete statusViewData; - StatusNotificationViewHolder(View itemView) { + private boolean useAbsoluteTime; + + StatusNotificationViewHolder(View itemView, boolean useAbsoluteTime) { super(itemView); message = itemView.findViewById(R.id.notification_top_text); statusNameBar = itemView.findViewById(R.id.status_name_bar); @@ -345,6 +351,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter { message.setOnClickListener(this); statusContent.setOnClickListener(this); contentWarningButton.setOnCheckedChangeListener(this); + + this.useAbsoluteTime = useAbsoluteTime; } private void showNotificationContent(boolean show) { @@ -370,8 +378,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter { } protected void setCreatedAt(@Nullable Date createdAt) { - SharedPreferences defPrefs = PreferenceManager.getDefaultSharedPreferences(timestampInfo.getContext()); - if (defPrefs.getBoolean("absoluteTimeView", true)) { + if (useAbsoluteTime) { String time = "ERROR!"; if (createdAt != null) { SimpleDateFormat sdf; @@ -429,7 +436,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); } @@ -490,10 +497,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 24aea0bec..662e64f4a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/SearchResultsAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/SearchResultsAdapter.java @@ -48,12 +48,14 @@ public class SearchResultsAdapter extends RecyclerView.Adapter { private boolean mediaPreviewsEnabled; private boolean alwaysShowSensitiveMedia; + private boolean useAbsoluteTime; private LinkListener linkListener; private StatusActionListener statusListener; public SearchResultsAdapter(boolean mediaPreviewsEnabled, boolean alwaysShowSensitiveMedia, - LinkListener linkListener, StatusActionListener statusListener) { + LinkListener linkListener, StatusActionListener statusListener, + boolean useAbsoluteTime) { this.accountList = Collections.emptyList(); this.statusList = Collections.emptyList(); @@ -62,6 +64,7 @@ public class SearchResultsAdapter extends RecyclerView.Adapter { this.mediaPreviewsEnabled = mediaPreviewsEnabled; this.alwaysShowSensitiveMedia = alwaysShowSensitiveMedia; + this.useAbsoluteTime = useAbsoluteTime; this.linkListener = linkListener; this.statusListener = statusListener; @@ -86,7 +89,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 a1e2bce02..b9191144c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java @@ -1,9 +1,7 @@ package com.keylesspalace.tusky.adapter; import android.content.Context; -import android.content.SharedPreferences; import android.graphics.drawable.Drawable; -import android.preference.PreferenceManager; import android.support.annotation.DrawableRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -68,7 +66,9 @@ abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { TextView content; TextView contentWarningDescription; - StatusBaseViewHolder(View itemView) { + private boolean useAbsoluteTime; + + StatusBaseViewHolder(View itemView, boolean useAbsoluteTime) { super(itemView); container = itemView.findViewById(R.id.status_container); displayName = itemView.findViewById(R.id.status_display_name); @@ -95,6 +95,8 @@ abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { mediaLabel = itemView.findViewById(R.id.status_media_label); contentWarningDescription = itemView.findViewById(R.id.status_content_warning_description); contentWarningButton = itemView.findViewById(R.id.status_content_warning_button); + + this.useAbsoluteTime = useAbsoluteTime; } protected abstract int getMediaPreviewHeight(Context context); @@ -130,8 +132,7 @@ abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { } protected void setCreatedAt(@Nullable Date createdAt) { - SharedPreferences defPrefs = PreferenceManager.getDefaultSharedPreferences(timestampInfo.getContext()); - if (defPrefs.getBoolean("absoluteTimeView", true)) { + if (useAbsoluteTime) { String time = "ERROR!"; if (createdAt != null) { SimpleDateFormat sdf; 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 9c6ef9d46..6bf1d44b9 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()) @@ -98,6 +100,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 a2c3c9006..37418218b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java @@ -198,6 +198,8 @@ public class NotificationsFragment 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); 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 9aca783e7..81bb71b8a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/SearchFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/SearchFragment.kt @@ -50,6 +50,7 @@ class SearchFragment : SFragment(), StatusActionListener, Injectable { private var alwaysShowSensitiveMedia = false private var mediaPreviewEnabled = true + private var useAbsoluteTime = false override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { @@ -60,10 +61,11 @@ 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) searchRecyclerView.addItemDecoration(DividerItemDecoration(view.context, DividerItemDecoration.VERTICAL)) searchRecyclerView.layoutManager = LinearLayoutManager(view.context) - searchAdapter = SearchResultsAdapter(mediaPreviewEnabled, alwaysShowSensitiveMedia, this, this) + searchAdapter = SearchResultsAdapter(mediaPreviewEnabled, alwaysShowSensitiveMedia, 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 62fe57c87..cac56e1d1 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java @@ -248,6 +248,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; @@ -605,7 +607,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(); } @@ -827,7 +829,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()) { @@ -1049,7 +1051,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 93cb00717..376fab607 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java @@ -156,6 +156,8 @@ public final class ViewThreadFragment 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); recyclerView.setAdapter(adapter); statuses.clear(); From b4bb80c4d539369cab9bc936ba7131e3cac3bac3 Mon Sep 17 00:00:00 2001 From: kyori Date: Mon, 20 Aug 2018 17:47:19 +0900 Subject: [PATCH 06/23] Stop initializing String. --- .../com/keylesspalace/tusky/adapter/NotificationsAdapter.java | 2 +- .../com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 ee69a5e48..5f9f52614 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java @@ -379,7 +379,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter { protected void setCreatedAt(@Nullable Date createdAt) { if (useAbsoluteTime) { - String time = "ERROR!"; + String time; if (createdAt != null) { SimpleDateFormat sdf; if (new Date().getTime() - createdAt.getTime() > 86400000L) { 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 b9191144c..2384de8ab 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java @@ -133,7 +133,7 @@ abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { protected void setCreatedAt(@Nullable Date createdAt) { if (useAbsoluteTime) { - String time = "ERROR!"; + String time; if (createdAt != null) { SimpleDateFormat sdf; if (new Date().getTime() - createdAt.getTime() > 86400000L) { From 4264fc42d61136bf46bbc7bbc5a7db6159f47e0c Mon Sep 17 00:00:00 2001 From: kyori Date: Mon, 20 Aug 2018 19:29:58 +0900 Subject: [PATCH 07/23] Add process for when the createdAt is null. --- .../com/keylesspalace/tusky/adapter/NotificationsAdapter.java | 4 +++- .../com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) 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 5f9f52614..4c937c4d9 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java @@ -388,8 +388,10 @@ public class NotificationsAdapter extends RecyclerView.Adapter { sdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault()); } time = sdf.format(createdAt); - timestampInfo.setText(time); + } else { + time = "??:??:??"; } + timestampInfo.setText(time); } else { // This is the visible timestampInfo. String readout; 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 2384de8ab..185156f9d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java @@ -142,8 +142,10 @@ abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { sdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault()); } time = sdf.format(createdAt); - timestampInfo.setText(time); + } else { + time = "??:??:??"; } + timestampInfo.setText(time); } else { // This is the visible timestampInfo. String readout; From ad888854bc34ea5f78745266a29b27552d06e0d7 Mon Sep 17 00:00:00 2001 From: kyori Date: Mon, 20 Aug 2018 20:56:22 +0900 Subject: [PATCH 08/23] Use SimpleDateFormatter repeatedly. --- .../tusky/adapter/NotificationsAdapter.java | 14 +++++++++----- .../tusky/adapter/StatusBaseViewHolder.java | 14 +++++++++----- 2 files changed, 18 insertions(+), 10 deletions(-) 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 4c937c4d9..248aa047e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java @@ -328,6 +328,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter { private StatusViewData.Concrete statusViewData; private boolean useAbsoluteTime; + private SimpleDateFormat sdf; + private boolean passed1day; StatusNotificationViewHolder(View itemView, boolean useAbsoluteTime) { super(itemView); @@ -353,6 +355,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter { contentWarningButton.setOnCheckedChangeListener(this); this.useAbsoluteTime = useAbsoluteTime; + sdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault()); + passed1day = false; } private void showNotificationContent(boolean show) { @@ -381,11 +385,11 @@ public class NotificationsAdapter extends RecyclerView.Adapter { if (useAbsoluteTime) { String time; if (createdAt != null) { - SimpleDateFormat sdf; - if (new Date().getTime() - createdAt.getTime() > 86400000L) { - sdf = new SimpleDateFormat("MM/dd HH:mm:ss", Locale.getDefault()); - } else { - sdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault()); + if (!passed1day) { + if (new Date().getTime() - createdAt.getTime() > 86400000L) { + passed1day = true; + sdf = new SimpleDateFormat("MM/dd HH:mm:ss", Locale.getDefault()); + } } time = sdf.format(createdAt); } else { 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 185156f9d..d3e0772b9 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java @@ -67,6 +67,8 @@ abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { TextView contentWarningDescription; private boolean useAbsoluteTime; + private SimpleDateFormat sdf; + private boolean passed1day; StatusBaseViewHolder(View itemView, boolean useAbsoluteTime) { super(itemView); @@ -97,6 +99,8 @@ abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { contentWarningButton = itemView.findViewById(R.id.status_content_warning_button); this.useAbsoluteTime = useAbsoluteTime; + sdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault()); + passed1day = false; } protected abstract int getMediaPreviewHeight(Context context); @@ -135,11 +139,11 @@ abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { if (useAbsoluteTime) { String time; if (createdAt != null) { - SimpleDateFormat sdf; - if (new Date().getTime() - createdAt.getTime() > 86400000L) { - sdf = new SimpleDateFormat("MM/dd HH:mm:ss", Locale.getDefault()); - } else { - sdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault()); + if (!passed1day) { + if (new Date().getTime() - createdAt.getTime() > 86400000L) { + passed1day = true; + sdf = new SimpleDateFormat("MM/dd HH:mm:ss", Locale.getDefault()); + } } time = sdf.format(createdAt); } else { From 589188e27dcbdf383087d76c4defde1ad6a57653 Mon Sep 17 00:00:00 2001 From: kyori Date: Mon, 20 Aug 2018 22:51:30 +0900 Subject: [PATCH 09/23] Prevent creating new object in setCreatedAt --- .../tusky/adapter/NotificationsAdapter.java | 18 ++++++++---------- .../tusky/adapter/StatusBaseViewHolder.java | 18 ++++++++---------- 2 files changed, 16 insertions(+), 20 deletions(-) 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 248aa047e..8c2eb221b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java @@ -328,8 +328,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter { private StatusViewData.Concrete statusViewData; private boolean useAbsoluteTime; - private SimpleDateFormat sdf; - private boolean passed1day; + private SimpleDateFormat shortSdf; + private SimpleDateFormat longSdf; StatusNotificationViewHolder(View itemView, boolean useAbsoluteTime) { super(itemView); @@ -355,8 +355,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter { contentWarningButton.setOnCheckedChangeListener(this); this.useAbsoluteTime = useAbsoluteTime; - sdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault()); - passed1day = false; + shortSdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault()); + longSdf = new SimpleDateFormat("MM/dd HH:mm:ss", Locale.getDefault()); } private void showNotificationContent(boolean show) { @@ -385,13 +385,11 @@ public class NotificationsAdapter extends RecyclerView.Adapter { if (useAbsoluteTime) { String time; if (createdAt != null) { - if (!passed1day) { - if (new Date().getTime() - createdAt.getTime() > 86400000L) { - passed1day = true; - sdf = new SimpleDateFormat("MM/dd HH:mm:ss", Locale.getDefault()); - } + if (System.currentTimeMillis() - createdAt.getTime() > 86400000L) { + time = longSdf.format(createdAt); + } else { + time = shortSdf.format(createdAt); } - time = sdf.format(createdAt); } else { time = "??:??:??"; } 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 d3e0772b9..2d7f0fa77 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java @@ -67,8 +67,8 @@ abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { TextView contentWarningDescription; private boolean useAbsoluteTime; - private SimpleDateFormat sdf; - private boolean passed1day; + private SimpleDateFormat shortSdf; + private SimpleDateFormat longSdf; StatusBaseViewHolder(View itemView, boolean useAbsoluteTime) { super(itemView); @@ -99,8 +99,8 @@ abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { contentWarningButton = itemView.findViewById(R.id.status_content_warning_button); this.useAbsoluteTime = useAbsoluteTime; - sdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault()); - passed1day = false; + shortSdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault()); + longSdf = new SimpleDateFormat("MM/dd HH:mm:ss", Locale.getDefault()); } protected abstract int getMediaPreviewHeight(Context context); @@ -139,13 +139,11 @@ abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { if (useAbsoluteTime) { String time; if (createdAt != null) { - if (!passed1day) { - if (new Date().getTime() - createdAt.getTime() > 86400000L) { - passed1day = true; - sdf = new SimpleDateFormat("MM/dd HH:mm:ss", Locale.getDefault()); - } + if (System.currentTimeMillis() - createdAt.getTime() > 86400000L) { + time = longSdf.format(createdAt); + } else { + time = shortSdf.format(createdAt); } - time = sdf.format(createdAt); } else { time = "??:??:??"; } From ff4a8ff4b8e251199f44b492dc3c1cb61039af06 Mon Sep 17 00:00:00 2001 From: Conny Duck Date: Fri, 31 Aug 2018 20:35:30 +0200 Subject: [PATCH 10/23] update deps --- app/build.gradle | 16 ++++++++-------- .../com/keylesspalace/tusky/di/AppComponent.kt | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index d9137fe2a..8b30363d1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,7 +1,7 @@ apply plugin: 'com.android.application' -apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-kapt' def getGitSha = { -> def stdout = new ByteArrayOutputStream() @@ -60,7 +60,7 @@ android { } ext.supportLibraryVersion = '27.1.1' -ext.daggerVersion = '2.16' +ext.daggerVersion = '2.17' // if libraries are changed here, they should also be changed in LicenseActivity @@ -82,13 +82,13 @@ dependencies { implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0' implementation 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0' implementation 'com.github.connyduck:sparkbutton:1.0.1' - implementation 'com.github.chrisbanes:PhotoView:2.1.3' + implementation 'com.github.chrisbanes:PhotoView:2.1.4' implementation 'com.mikepenz:google-material-typeface:3.0.1.2.original@aar' implementation('com.theartofdev.edmodo:android-image-cropper:2.7.0') { exclude group: 'com.android.support' } implementation 'com.evernote:android-job:1.2.6' - implementation 'com.android.support.constraint:constraint-layout:1.1.2' + implementation 'com.android.support.constraint:constraint-layout:1.1.3' // EmojiCompat implementation "com.android.support:support-emoji:$supportLibraryVersion" implementation "com.android.support:support-emoji-appcompat:$supportLibraryVersion" @@ -111,8 +111,8 @@ dependencies { exclude group: 'com.android.support', module: 'support-annotations' }) debugImplementation 'im.dino:dbinspector:3.4.1@aar' - implementation 'io.reactivex.rxjava2:rxjava:2.2.0' - implementation 'io.reactivex.rxjava2:rxandroid:2.0.2' - implementation 'com.uber.autodispose:autodispose-android-archcomponents:0.8.0' - implementation 'com.uber.autodispose:autodispose-kotlin:0.8.0' + implementation 'io.reactivex.rxjava2:rxjava:2.2.1' + implementation 'io.reactivex.rxjava2:rxandroid:2.1.0' + implementation 'com.uber.autodispose:autodispose-android-archcomponents:1.0.0-RC2' + implementation 'com.uber.autodispose:autodispose-ktx:1.0.0-RC2' } diff --git a/app/src/main/java/com/keylesspalace/tusky/di/AppComponent.kt b/app/src/main/java/com/keylesspalace/tusky/di/AppComponent.kt index d47e879af..66da6efec 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/AppComponent.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/AppComponent.kt @@ -18,7 +18,7 @@ package com.keylesspalace.tusky.di import com.keylesspalace.tusky.TuskyApplication import dagger.BindsInstance import dagger.Component -import dagger.android.AndroidInjectionModule +import dagger.android.support.AndroidSupportInjectionModule import javax.inject.Singleton @@ -30,7 +30,7 @@ import javax.inject.Singleton @Component(modules = [ AppModule::class, NetworkModule::class, - AndroidInjectionModule::class, + AndroidSupportInjectionModule::class, ActivitiesModule::class, ServicesModule::class, BroadcastReceiverModule::class, From ca881af7c52e89f5133952fe1fc8309c09b9b9ba Mon Sep 17 00:00:00 2001 From: Conny Duck Date: Fri, 31 Aug 2018 20:39:28 +0200 Subject: [PATCH 11/23] add missing libraries to libraries screen --- app/src/main/res/layout/activity_license.xml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/app/src/main/res/layout/activity_license.xml b/app/src/main/res/layout/activity_license.xml index d8de0e97c..bf9eb6adb 100644 --- a/app/src/main/res/layout/activity_license.xml +++ b/app/src/main/res/layout/activity_license.xml @@ -91,6 +91,26 @@ license:link="https://google.github.io/dagger/" license:name="Dagger 2" /> + + + + Date: Fri, 31 Aug 2018 21:52:09 +0200 Subject: [PATCH 12/23] fix account list loading and clean up a lot of code (#823) * fix account list loading and clean up a lot of code * remove ACCESS_COARSE_LOCATION for API levels 23+ * small improvements --- .../tusky/adapter/AccountAdapter.java | 85 ++++----- .../tusky/adapter/BlocksAdapter.java | 20 +- .../tusky/adapter/FollowAdapter.java | 17 +- .../tusky/adapter/FollowRequestsAdapter.java | 20 +- .../tusky/adapter/FooterViewHolder.java | 80 -------- .../tusky/adapter/LoadingFooterViewHolder.kt | 21 +++ .../tusky/adapter/MutesAdapter.java | 19 +- .../tusky/adapter/NotificationsAdapter.java | 57 +++--- .../tusky/fragment/AccountListFragment.java | 174 +++++------------- app/src/main/res/layout/item_footer.xml | 20 +- 10 files changed, 135 insertions(+), 378 deletions(-) delete mode 100644 app/src/main/java/com/keylesspalace/tusky/adapter/FooterViewHolder.java create mode 100644 app/src/main/java/com/keylesspalace/tusky/adapter/LoadingFooterViewHolder.kt diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.java index 21578ba24..14afe76f5 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.java @@ -15,6 +15,7 @@ package com.keylesspalace.tusky.adapter; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.widget.RecyclerView; @@ -26,55 +27,40 @@ import java.util.ArrayList; import java.util.List; public abstract class AccountAdapter extends RecyclerView.Adapter { + static final int VIEW_TYPE_ACCOUNT = 0; + static final int VIEW_TYPE_FOOTER = 1; + + List accountList; AccountActionListener accountActionListener; - FooterViewHolder.State footerState; - - private String topId; - private String bottomId; + private boolean bottomLoading; AccountAdapter(AccountActionListener accountActionListener) { - super(); - accountList = new ArrayList<>(); + this.accountList = new ArrayList<>(); this.accountActionListener = accountActionListener; - footerState = FooterViewHolder.State.END; + bottomLoading = false; } @Override public int getItemCount() { - return accountList.size() + 1; + return accountList.size() + (bottomLoading ? 1 : 0); } - public void update(@Nullable List newAccounts, @Nullable String fromId, - @Nullable String uptoId) { - if (newAccounts == null || newAccounts.isEmpty()) { - return; - } - - bottomId = fromId; - topId = uptoId; - - if (accountList.isEmpty()) { - accountList = ListUtils.removeDuplicates(newAccounts); + @Override + public int getItemViewType(int position) { + if (position == accountList.size() && bottomLoading) { + return VIEW_TYPE_FOOTER; } else { - int index = accountList.indexOf(newAccounts.get(newAccounts.size() - 1)); - for (int i = 0; i < index; i++) { - accountList.remove(0); - } - int newIndex = newAccounts.indexOf(accountList.get(0)); - if (newIndex == -1) { - accountList.addAll(0, newAccounts); - } else { - accountList.addAll(0, newAccounts.subList(0, newIndex)); - } + return VIEW_TYPE_ACCOUNT; } + } + + public void update(@NonNull List newAccounts) { + accountList = ListUtils.removeDuplicates(newAccounts); notifyDataSetChanged(); } - public void addItems(List newAccounts, @Nullable String fromId) { - if (fromId != null) { - bottomId = fromId; - } + public void addItems(List newAccounts) { int end = accountList.size(); Account last = accountList.get(end - 1); if (last != null && !findAccount(newAccounts, last.getId())) { @@ -83,6 +69,19 @@ public abstract class AccountAdapter extends RecyclerView.Adapter { } } + public void setBottomLoading(boolean loading) { + boolean wasLoading = bottomLoading; + if(wasLoading == loading) { + return; + } + bottomLoading = loading; + if(loading) { + notifyItemInserted(accountList.size()); + } else { + notifyItemRemoved(accountList.size()); + } + } + private static boolean findAccount(List accounts, String id) { for (Account account : accounts) { if (account.getId().equals(id)) { @@ -110,25 +109,5 @@ public abstract class AccountAdapter extends RecyclerView.Adapter { notifyItemInserted(position); } - @Nullable - public Account getItem(int position) { - if (position >= 0 && position < accountList.size()) { - return accountList.get(position); - } - return null; - } - public void setFooterState(FooterViewHolder.State newFooterState) { - footerState = newFooterState; - } - - @Nullable - public String getBottomId() { - return bottomId; - } - - @Nullable - public String getTopId() { - return topId; - } } diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.java index 21689e91c..23354dba8 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.java @@ -31,8 +31,6 @@ import com.keylesspalace.tusky.util.CustomEmojiHelper; import com.squareup.picasso.Picasso; public class BlocksAdapter extends AccountAdapter { - private static final int VIEW_TYPE_BLOCKED_USER = 0; - private static final int VIEW_TYPE_FOOTER = 1; public BlocksAdapter(AccountActionListener accountActionListener) { super(accountActionListener); @@ -43,7 +41,7 @@ public class BlocksAdapter extends AccountAdapter { public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { switch (viewType) { default: - case VIEW_TYPE_BLOCKED_USER: { + case VIEW_TYPE_ACCOUNT: { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_blocked_user, parent, false); return new BlockedUserViewHolder(view); @@ -51,29 +49,17 @@ public class BlocksAdapter extends AccountAdapter { case VIEW_TYPE_FOOTER: { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_footer, parent, false); - return new FooterViewHolder(view); + return new LoadingFooterViewHolder(view); } } } @Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { - if (position < accountList.size()) { + if (getItemViewType(position) == VIEW_TYPE_ACCOUNT) { BlockedUserViewHolder holder = (BlockedUserViewHolder) viewHolder; holder.setupWithAccount(accountList.get(position)); holder.setupActionListener(accountActionListener); - } else { - FooterViewHolder holder = (FooterViewHolder) viewHolder; - holder.setState(footerState); - } - } - - @Override - public int getItemViewType(int position) { - if (position == accountList.size()) { - return VIEW_TYPE_FOOTER; - } else { - return VIEW_TYPE_BLOCKED_USER; } } diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.java index c52050de4..8b7aab71b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.java @@ -26,8 +26,6 @@ import com.keylesspalace.tusky.interfaces.AccountActionListener; /** Both for follows and following lists. */ public class FollowAdapter extends AccountAdapter { - private static final int VIEW_TYPE_ACCOUNT = 0; - private static final int VIEW_TYPE_FOOTER = 1; public FollowAdapter(AccountActionListener accountActionListener) { super(accountActionListener); @@ -46,29 +44,18 @@ public class FollowAdapter extends AccountAdapter { case VIEW_TYPE_FOOTER: { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_footer, parent, false); - return new FooterViewHolder(view); + return new LoadingFooterViewHolder(view); } } } @Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { - if (position < accountList.size()) { + if (getItemViewType(position) == VIEW_TYPE_ACCOUNT) { AccountViewHolder holder = (AccountViewHolder) viewHolder; holder.setupWithAccount(accountList.get(position)); holder.setupActionListener(accountActionListener); - } else { - FooterViewHolder holder = (FooterViewHolder) viewHolder; - holder.setState(footerState); } } - @Override - public int getItemViewType(int position) { - if (position == accountList.size()) { - return VIEW_TYPE_FOOTER; - } else { - return VIEW_TYPE_ACCOUNT; - } - } } diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.java index 31c191023..a3941c686 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.java @@ -31,8 +31,6 @@ import com.keylesspalace.tusky.util.CustomEmojiHelper; import com.squareup.picasso.Picasso; public class FollowRequestsAdapter extends AccountAdapter { - private static final int VIEW_TYPE_FOLLOW_REQUEST = 0; - private static final int VIEW_TYPE_FOOTER = 1; public FollowRequestsAdapter(AccountActionListener accountActionListener) { super(accountActionListener); @@ -43,7 +41,7 @@ public class FollowRequestsAdapter extends AccountAdapter { public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { switch (viewType) { default: - case VIEW_TYPE_FOLLOW_REQUEST: { + case VIEW_TYPE_ACCOUNT: { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_follow_request, parent, false); return new FollowRequestViewHolder(view); @@ -51,29 +49,17 @@ public class FollowRequestsAdapter extends AccountAdapter { case VIEW_TYPE_FOOTER: { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_footer, parent, false); - return new FooterViewHolder(view); + return new LoadingFooterViewHolder(view); } } } @Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { - if (position < accountList.size()) { + if (getItemViewType(position) == VIEW_TYPE_ACCOUNT) { FollowRequestViewHolder holder = (FollowRequestViewHolder) viewHolder; holder.setupWithAccount(accountList.get(position)); holder.setupActionListener(accountActionListener); - } else { - FooterViewHolder holder = (FooterViewHolder) viewHolder; - holder.setState(footerState); - } - } - - @Override - public int getItemViewType(int position) { - if (position == accountList.size()) { - return VIEW_TYPE_FOOTER; - } else { - return VIEW_TYPE_FOLLOW_REQUEST; } } diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/FooterViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/FooterViewHolder.java deleted file mode 100644 index b4e2be141..000000000 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/FooterViewHolder.java +++ /dev/null @@ -1,80 +0,0 @@ -/* Copyright 2017 Andrew Dawson - * - * This file is a part of Tusky. - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 3 of the - * License, or (at your option) any later version. - * - * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even - * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along with Tusky; if not, - * see . */ - -package com.keylesspalace.tusky.adapter; - -import android.graphics.drawable.Drawable; -import android.support.v7.content.res.AppCompatResources; -import android.support.v7.widget.RecyclerView; -import android.view.View; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.support.v7.widget.RecyclerView.LayoutParams; - -import com.keylesspalace.tusky.R; - -public class FooterViewHolder extends RecyclerView.ViewHolder { - public enum State { - EMPTY, - END, - LOADING - } - - private View container; - private ProgressBar progressBar; - private TextView endMessage; - - FooterViewHolder(View itemView) { - super(itemView); - container = itemView.findViewById(R.id.footer_container); - progressBar = itemView.findViewById(R.id.footer_progress_bar); - endMessage = itemView.findViewById(R.id.footer_end_message); - Drawable top = AppCompatResources.getDrawable(itemView.getContext(), - R.drawable.elephant_friend_empty); - endMessage.setCompoundDrawablesWithIntrinsicBounds(null, top, null, null); - } - - public void setState(State state) { - switch (state) { - case LOADING: { - RecyclerView.LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, - LayoutParams.MATCH_PARENT); - container.setLayoutParams(layoutParams); - container.setVisibility(View.VISIBLE); - progressBar.setVisibility(View.VISIBLE); - endMessage.setVisibility(View.GONE); - break; - } - case END: { - RecyclerView.LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, - LayoutParams.WRAP_CONTENT); - container.setLayoutParams(layoutParams); - container.setVisibility(View.GONE); - progressBar.setVisibility(View.GONE); - endMessage.setVisibility(View.GONE); - break; - } - case EMPTY: { - RecyclerView.LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, - LayoutParams.MATCH_PARENT); - container.setLayoutParams(layoutParams); - container.setVisibility(View.VISIBLE); - progressBar.setVisibility(View.GONE); - endMessage.setVisibility(View.VISIBLE); - break; - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/LoadingFooterViewHolder.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/LoadingFooterViewHolder.kt new file mode 100644 index 000000000..f848c1809 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/LoadingFooterViewHolder.kt @@ -0,0 +1,21 @@ +/* Copyright 2018 Conny Duck + * + * This file is a part of Tusky. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Tusky; if not, + * see . */ + +package com.keylesspalace.tusky.adapter + +import android.support.v7.widget.RecyclerView +import android.view.View + +class LoadingFooterViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.java index a2318a74c..7c64e8741 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.java @@ -16,8 +16,6 @@ import com.keylesspalace.tusky.util.CustomEmojiHelper; import com.squareup.picasso.Picasso; public class MutesAdapter extends AccountAdapter { - private static final int VIEW_TYPE_MUTED_USER = 0; - private static final int VIEW_TYPE_FOOTER = 1; public MutesAdapter(AccountActionListener accountActionListener) { super(accountActionListener); @@ -28,7 +26,7 @@ public class MutesAdapter extends AccountAdapter { public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { switch (viewType) { default: - case VIEW_TYPE_MUTED_USER: { + case VIEW_TYPE_ACCOUNT: { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_muted_user, parent, false); return new MutesAdapter.MutedUserViewHolder(view); @@ -36,31 +34,20 @@ public class MutesAdapter extends AccountAdapter { case VIEW_TYPE_FOOTER: { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_footer, parent, false); - return new FooterViewHolder(view); + return new LoadingFooterViewHolder(view); } } } @Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { - if (position < accountList.size()) { + if (getItemViewType(position) == VIEW_TYPE_ACCOUNT) { MutedUserViewHolder holder = (MutedUserViewHolder) viewHolder; holder.setupWithAccount(accountList.get(position)); holder.setupActionListener(accountActionListener); - } else { - FooterViewHolder holder = (FooterViewHolder) viewHolder; - holder.setState(footerState); } } - @Override - public int getItemViewType(int position) { - if (position == accountList.size()) { - return VIEW_TYPE_FOOTER; - } else { - return VIEW_TYPE_MUTED_USER; - } - } static class MutedUserViewHolder extends RecyclerView.ViewHolder { private ImageView avatar; 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 52d205552..c6c9acd6e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java @@ -56,10 +56,9 @@ import java.util.List; public class NotificationsAdapter extends RecyclerView.Adapter { private static final int VIEW_TYPE_MENTION = 0; - private static final int VIEW_TYPE_FOOTER = 1; - private static final int VIEW_TYPE_STATUS_NOTIFICATION = 2; - private static final int VIEW_TYPE_FOLLOW = 3; - private static final int VIEW_TYPE_PLACEHOLDER = 4; + private static final int VIEW_TYPE_STATUS_NOTIFICATION = 1; + private static final int VIEW_TYPE_FOLLOW = 2; + private static final int VIEW_TYPE_PLACEHOLDER = 3; private List notifications; private StatusActionListener statusListener; @@ -87,11 +86,6 @@ public class NotificationsAdapter extends RecyclerView.Adapter { .inflate(R.layout.item_status, parent, false); return new StatusViewHolder(view); } - case VIEW_TYPE_FOOTER: { - View view = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.item_footer, parent, false); - return new FooterViewHolder(view); - } case VIEW_TYPE_STATUS_NOTIFICATION: { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_status_notification, parent, false); @@ -172,31 +166,28 @@ public class NotificationsAdapter extends RecyclerView.Adapter { @Override public int getItemViewType(int position) { - if (position == notifications.size()) { - return VIEW_TYPE_FOOTER; - } else { - NotificationViewData notification = notifications.get(position); - if (notification instanceof NotificationViewData.Concrete) { - NotificationViewData.Concrete concrete = ((NotificationViewData.Concrete) notification); - switch (concrete.getType()) { - default: - case MENTION: { - return VIEW_TYPE_MENTION; - } - case FAVOURITE: - case REBLOG: { - return VIEW_TYPE_STATUS_NOTIFICATION; - } - case FOLLOW: { - return VIEW_TYPE_FOLLOW; - } + NotificationViewData notification = notifications.get(position); + if (notification instanceof NotificationViewData.Concrete) { + NotificationViewData.Concrete concrete = ((NotificationViewData.Concrete) notification); + switch (concrete.getType()) { + default: + case MENTION: { + return VIEW_TYPE_MENTION; + } + case FAVOURITE: + case REBLOG: { + return VIEW_TYPE_STATUS_NOTIFICATION; + } + case FOLLOW: { + return VIEW_TYPE_FOLLOW; } - } else if (notification instanceof NotificationViewData.Placeholder) { - return VIEW_TYPE_PLACEHOLDER; - } else { - throw new AssertionError("Unknown notification type"); } + } else if (notification instanceof NotificationViewData.Placeholder) { + return VIEW_TYPE_PLACEHOLDER; + } else { + throw new AssertionError("Unknown notification type"); } + } public void update(@Nullable List newNotifications) { @@ -364,8 +355,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter { 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. */ + /* 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(); diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.java index 017bc52ce..07ecdb36b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.java @@ -36,7 +36,6 @@ import com.keylesspalace.tusky.adapter.AccountAdapter; import com.keylesspalace.tusky.adapter.BlocksAdapter; import com.keylesspalace.tusky.adapter.FollowAdapter; import com.keylesspalace.tusky.adapter.FollowRequestsAdapter; -import com.keylesspalace.tusky.adapter.FooterViewHolder; import com.keylesspalace.tusky.adapter.MutesAdapter; import com.keylesspalace.tusky.di.Injectable; import com.keylesspalace.tusky.entity.Account; @@ -79,10 +78,8 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi private RecyclerView recyclerView; private EndlessOnScrollListener scrollListener; private AccountAdapter adapter; - private boolean bottomLoading; - private int bottomFetches; - private boolean topLoading; - private int topFetches; + private boolean fetching = false; + private String bottomId; public static AccountListFragment newInstance(Type type) { Bundle arguments = new Bundle(); @@ -140,10 +137,6 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi } recyclerView.setAdapter(adapter); - bottomLoading = false; - bottomFetches = 0; - topLoading = false; - topFetches = 0; return rootView; } @@ -155,13 +148,13 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi scrollListener = new EndlessOnScrollListener(layoutManager) { @Override public void onLoadMore(int totalItemsCount, RecyclerView view) { - AccountListFragment.this.onLoadMore(view); + AccountListFragment.this.onLoadMore(); } }; recyclerView.addOnScrollListener(scrollListener); - fetchAccounts(null, null, FetchEnd.BOTTOM); + fetchAccounts(null); } @@ -176,14 +169,6 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi @Override public void onMute(final boolean mute, final String id, final int position) { - if (api == null) { - /* If somehow an unmute button is clicked after onCreateView but before - * onActivityCreated, then this would get called with a null api object, so this eats - * that input. */ - Log.d(TAG, "MastodonApi isn't initialised so this mute can't occur."); - return; - } - Callback callback = new Callback() { @Override public void onResponse(@NonNull Call call, @NonNull Response response) { @@ -237,14 +222,6 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi @Override public void onBlock(final boolean block, final String id, final int position) { - if (api == null) { - /* If somehow an unblock button is clicked after onCreateView but before - * onActivityCreated, then this would get called with a null api object, so this eats - * that input. */ - Log.d(TAG, "MastodonApi isn't initialised so this block can't occur."); - return; - } - Callback cb = new Callback() { @Override public void onResponse(@NonNull Call call, @NonNull Response response) { @@ -299,13 +276,6 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi @Override public void onRespondToFollowRequest(final boolean accept, final String accountId, final int position) { - if (api == null) { - /* If somehow an response button is clicked after onCreateView but before - * onActivityCreated, then this would get called with a null api object, so this eats - * that input. */ - Log.d(TAG, "MastodonApi isn't initialised, so follow requests can't be responded to."); - return; - } Callback callback = new Callback() { @Override @@ -349,44 +319,30 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi Log.e(TAG, message); } - private enum FetchEnd { - TOP, - BOTTOM - } - - private Call> getFetchCallByListType(Type type, String fromId, String uptoId) { + private Call> getFetchCallByListType(Type type, String fromId) { switch (type) { default: case FOLLOWS: - return api.accountFollowing(accountId, fromId, uptoId, null); + return api.accountFollowing(accountId, fromId, null, null); case FOLLOWERS: - return api.accountFollowers(accountId, fromId, uptoId, null); + return api.accountFollowers(accountId, fromId, null, null); case BLOCKS: - return api.blocks(fromId, uptoId, null); + return api.blocks(fromId, null, null); case MUTES: - return api.mutes(fromId, uptoId, null); + return api.mutes(fromId, null, null); case FOLLOW_REQUESTS: - return api.followRequests(fromId, uptoId, null); + return api.followRequests(fromId, null, null); } } - private void fetchAccounts(String fromId, String uptoId, final FetchEnd fetchEnd) { - /* If there is a fetch already ongoing, record however many fetches are requested and - * fulfill them after it's complete. */ - if (fetchEnd == FetchEnd.TOP && topLoading) { - topFetches++; - return; - } - if (fetchEnd == FetchEnd.BOTTOM && bottomLoading) { - bottomFetches++; + private void fetchAccounts(String id) { + if (fetching) { return; } + fetching = true; - if (fromId != null || adapter.getItemCount() <= 1) { - /* When this is called by the EndlessScrollListener it cannot refresh the footer state - * using adapter.notifyItemChanged. So its necessary to postpone doing so until a - * convenient time for the UI thread using a Runnable. */ - recyclerView.post(() -> adapter.setFooterState(FooterViewHolder.State.LOADING)); + if (id != null) { + recyclerView.post(() -> adapter.setBottomLoading(true)); } Callback> cb = new Callback>() { @@ -394,99 +350,55 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi public void onResponse(@NonNull Call> call, @NonNull Response> response) { if (response.isSuccessful()) { String linkHeader = response.headers().get("Link"); - onFetchAccountsSuccess(response.body(), linkHeader, fetchEnd); + onFetchAccountsSuccess(response.body(), linkHeader); } else { - onFetchAccountsFailure(new Exception(response.message()), fetchEnd); + onFetchAccountsFailure(new Exception(response.message())); } } @Override public void onFailure(@NonNull Call> call, @NonNull Throwable t) { - onFetchAccountsFailure((Exception) t, fetchEnd); + onFetchAccountsFailure((Exception) t); } }; - Call> listCall = getFetchCallByListType(type, fromId, uptoId); + Call> listCall = getFetchCallByListType(type, id); callList.add(listCall); listCall.enqueue(cb); } - private void onFetchAccountsSuccess(List accounts, String linkHeader, - FetchEnd fetchEnd) { + private void onFetchAccountsSuccess(List accounts, String linkHeader) { + adapter.setBottomLoading(false); + + List links = HttpHeaderLink.parse(linkHeader); - switch (fetchEnd) { - case TOP: { - HttpHeaderLink previous = HttpHeaderLink.findByRelationType(links, "prev"); - String uptoId = null; - if (previous != null) { - uptoId = previous.uri.getQueryParameter("since_id"); - } - adapter.update(accounts, null, uptoId); - break; - } - case BOTTOM: { - HttpHeaderLink next = HttpHeaderLink.findByRelationType(links, "next"); - String fromId = null; - if (next != null) { - fromId = next.uri.getQueryParameter("max_id"); - } - if (adapter.getItemCount() > 1) { - adapter.addItems(accounts, fromId); - } else { - /* If this is the first fetch, also save the id from the "previous" link and - * treat this operation as a refresh so the scroll position doesn't get pushed - * down to the end. */ - HttpHeaderLink previous = HttpHeaderLink.findByRelationType(links, "prev"); - String uptoId = null; - if (previous != null) { - uptoId = previous.uri.getQueryParameter("since_id"); - } - adapter.update(accounts, fromId, uptoId); - } - break; - } + HttpHeaderLink next = HttpHeaderLink.findByRelationType(links, "next"); + String fromId = null; + if (next != null) { + fromId = next.uri.getQueryParameter("max_id"); } - fulfillAnyQueuedFetches(fetchEnd); - if (accounts.size() == 0 && adapter.getItemCount() == 1) { - adapter.setFooterState(FooterViewHolder.State.EMPTY); + if (adapter.getItemCount() > 1) { + adapter.addItems(accounts); } else { - adapter.setFooterState(FooterViewHolder.State.END); + adapter.update(accounts); } + + bottomId = fromId; + + fetching = false; + + adapter.setBottomLoading(false); } - private void onFetchAccountsFailure(Exception exception, FetchEnd fetchEnd) { + private void onFetchAccountsFailure(Exception exception) { + fetching = false; Log.e(TAG, "Fetch failure: " + exception.getMessage()); - fulfillAnyQueuedFetches(fetchEnd); } - private void onRefresh() { - fetchAccounts(null, adapter.getTopId(), FetchEnd.TOP); - } - - private void onLoadMore(RecyclerView recyclerView) { - AccountAdapter adapter = (AccountAdapter) recyclerView.getAdapter(); - //if we do not have a bottom id, we know we do not need to load more - if (adapter.getBottomId() == null) return; - fetchAccounts(adapter.getBottomId(), null, FetchEnd.BOTTOM); - } - - private void fulfillAnyQueuedFetches(FetchEnd fetchEnd) { - switch (fetchEnd) { - case BOTTOM: { - bottomLoading = false; - if (bottomFetches > 0) { - bottomFetches--; - onLoadMore(recyclerView); - } - break; - } - case TOP: { - topLoading = false; - if (topFetches > 0) { - topFetches--; - onRefresh(); - } - break; - } + private void onLoadMore() { + if(bottomId == null) { + return; } + fetchAccounts(bottomId); } + } diff --git a/app/src/main/res/layout/item_footer.xml b/app/src/main/res/layout/item_footer.xml index 48fa72c52..bdf3bdde7 100644 --- a/app/src/main/res/layout/item_footer.xml +++ b/app/src/main/res/layout/item_footer.xml @@ -1,24 +1,12 @@ - + android:layout_height="72dp"> - - - \ No newline at end of file + \ No newline at end of file From 5131c44e93ba071afd709b520ebacc48e52b46b4 Mon Sep 17 00:00:00 2001 From: Conny Duck Date: Sun, 2 Sep 2018 13:23:36 +0200 Subject: [PATCH 13/23] fix empty message view showing/hiding at the wrong times --- .../com/keylesspalace/tusky/fragment/TimelineFragment.java | 5 +++++ 1 file changed, 5 insertions(+) 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 dc8644d69..1ea0e2564 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java @@ -300,6 +300,9 @@ public class TimelineFragment extends SFragment implements break; } } + if(statuses.size() == 0) { + nothingMessageView.setVisibility(View.VISIBLE); + } } @Override @@ -820,6 +823,8 @@ public class TimelineFragment extends SFragment implements swipeRefreshLayout.setRefreshing(false); if (this.statuses.size() == 0) { nothingMessageView.setVisibility(View.VISIBLE); + } else { + nothingMessageView.setVisibility(View.GONE); } } From 95b1a7c61aea6a5866def00fc3e68a33be10e116 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Mon, 3 Sep 2018 16:24:39 +0200 Subject: [PATCH 14/23] Update bitrise badge (#833) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6d4754d71..1e89f9363 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Translate - with Stringlate](https://img.shields.io/badge/translate%20with-stringlate-green.svg)](https://lonamiwebs.github.io/stringlate/translate?git=https%3A%2F%2Fgithub.com%2Ftuskyapp%2FTusky) [![Build Status](https://app.bitrise.io/app/55b2f0c77c4bba74/status.svg?token=elUl9fieM5K34iLRL0rpoA&branch=master)](https://app.bitrise.io/app/55b2f0c77c4bba74) [![CircleCI](https://circleci.com/gh/tuskyapp/Tusky.svg?style=svg)](https://circleci.com/gh/tuskyapp/Tusky) +[![Translate - with Stringlate](https://img.shields.io/badge/translate%20with-stringlate-green.svg)](https://lonamiwebs.github.io/stringlate/translate?git=https%3A%2F%2Fgithub.com%2Ftuskyapp%2FTusky) [![Build Status](https://app.bitrise.io/app/a3e773c3c57a894c/status.svg?token=qLu_Ti4Gp2LWcYT4eo2INQ&branch=master)](https://app.bitrise.io/app/a3e773c3c57a894c#/builds) [![CircleCI](https://circleci.com/gh/tuskyapp/Tusky.svg?style=svg)](https://circleci.com/gh/tuskyapp/Tusky) # Tusky ![](/fastlane/metadata/android/en-US/images/icon.png) From 0b3bee0d15e53de52dedce4e6627f5f7267a7673 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Mon, 3 Sep 2018 20:16:12 +0200 Subject: [PATCH 15/23] fix account switching when offline/error (#830) --- .../com/keylesspalace/tusky/MainActivity.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java index 3a062fcf0..dcbce5194 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java @@ -420,7 +420,7 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut Intent intent = new Intent(this, MainActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - startActivityWithSlideInAnimation(intent); + startActivity(intent); finishWithoutSlideOutAnimation(); overridePendingTransition(R.anim.explode, R.anim.explode); @@ -510,21 +510,15 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut List allAccounts = accountManager.getAllAccountsOrderedByActive(); - // reuse the already existing "add account" item List profiles = new ArrayList<>(allAccounts.size()+1); - for (IProfile profile: headerResult.getProfiles()) { - if (profile.getIdentifier() == DRAWER_ITEM_ADD_ACCOUNT) { - profiles.add(profile); - break; - } - } for (AccountEntity acc : allAccounts) { CharSequence emojifiedName = CustomEmojiHelper.emojifyString(acc.getDisplayName(), acc.getEmojis(), headerResult.getView()); emojifiedName = EmojiCompat.get().process(emojifiedName); - profiles.add(0, + profiles.add( new ProfileDrawerItem() + .withSetSelected(acc.isActive()) .withName(emojifiedName) .withIcon(acc.getProfilePictureUrl()) .withNameShown(true) @@ -532,6 +526,15 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut .withEmail(acc.getFullName())); } + + // reuse the already existing "add account" item + for (IProfile profile: headerResult.getProfiles()) { + if (profile.getIdentifier() == DRAWER_ITEM_ADD_ACCOUNT) { + profiles.add(profile); + break; + } + } + headerResult.clear(); headerResult.setProfiles(profiles); } From aac63441d70e1c7782daebb18fb6d832533c8b96 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Mon, 3 Sep 2018 20:26:35 +0200 Subject: [PATCH 16/23] fix image preview rotation in ComposeActivity (#831) --- .../keylesspalace/tusky/ComposeActivity.java | 32 +++++-- .../tusky/util/DownsizeImageTask.java | 87 +------------------ .../keylesspalace/tusky/util/MediaUtils.java | 83 +++++++++++++++++- 3 files changed, 111 insertions(+), 91 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java b/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java index 4662fbbd6..f36cff2ac 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java @@ -18,6 +18,7 @@ package com.keylesspalace.tusky; import android.Manifest; import android.annotation.SuppressLint; import android.app.ProgressDialog; +import android.arch.lifecycle.Lifecycle; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; @@ -135,12 +136,20 @@ import java.util.Locale; import javax.inject.Inject; import at.connyduck.sparkbutton.helpers.Utils; +import io.reactivex.Single; +import io.reactivex.SingleObserver; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; import okhttp3.MediaType; import okhttp3.MultipartBody; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; +import static com.uber.autodispose.AutoDispose.autoDisposable; +import static com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from; + public final class ComposeActivity extends BaseActivity implements ComposeOptionsListener, @@ -1121,11 +1130,24 @@ public final class ComposeActivity DisplayMetrics displayMetrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); - Picasso.with(this) - .load(item.uri) - .resize(displayMetrics.widthPixels, displayMetrics.heightPixels) - .onlyScaleDown() - .into(imageView); + Single.fromCallable(() -> + MediaUtils.getSampledBitmap(getContentResolver(), item.uri, displayMetrics.widthPixels, displayMetrics.heightPixels)) + .subscribeOn(Schedulers.computation()) + .observeOn(AndroidSchedulers.mainThread()) + .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY))) + .subscribe(new SingleObserver() { + @Override + public void onSubscribe(Disposable d) {} + + @Override + public void onSuccess(Bitmap bitmap) { + imageView.setImageBitmap(bitmap); + } + + @Override + public void onError(Throwable e) { } + }); + int margin = Utils.dpToPx(this, 4); dialogLayout.addView(imageView); 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 be0c989dd..3b9f04d08 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/DownsizeImageTask.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/DownsizeImageTask.java @@ -18,16 +18,11 @@ package com.keylesspalace.tusky.util; import android.content.ContentResolver; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.graphics.Matrix; import android.net.Uri; import android.os.AsyncTask; -import android.support.annotation.Nullable; -import android.support.media.ExifInterface; -import android.util.Log; import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; -import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; @@ -37,7 +32,6 @@ import java.util.List; * aspect ratio and orientation. */ public class DownsizeImageTask extends AsyncTask { - private static final String TAG = "DownsizeImageTask"; private int sizeLimit; private ContentResolver contentResolver; private Listener listener; @@ -54,83 +48,6 @@ public class DownsizeImageTask extends AsyncTask { this.listener = listener; } - @Nullable - private static Bitmap reorientBitmap(Bitmap bitmap, int orientation) { - Matrix matrix = new Matrix(); - switch (orientation) { - default: - case ExifInterface.ORIENTATION_NORMAL: { - return bitmap; - } - case ExifInterface.ORIENTATION_FLIP_HORIZONTAL: { - matrix.setScale(-1, 1); - break; - } - case ExifInterface.ORIENTATION_ROTATE_180: { - matrix.setRotate(180); - break; - } - case ExifInterface.ORIENTATION_FLIP_VERTICAL: { - matrix.setRotate(180); - matrix.postScale(-1, 1); - break; - } - case ExifInterface.ORIENTATION_TRANSPOSE: { - matrix.setRotate(90); - matrix.postScale(-1, 1); - break; - } - case ExifInterface.ORIENTATION_ROTATE_90: { - matrix.setRotate(90); - break; - } - case ExifInterface.ORIENTATION_TRANSVERSE: { - matrix.setRotate(-90); - matrix.postScale(-1, 1); - break; - } - case ExifInterface.ORIENTATION_ROTATE_270: { - matrix.setRotate(-90); - break; - } - } - try { - Bitmap result = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), - bitmap.getHeight(), matrix, true); - if (!bitmap.sameAs(result)) { - bitmap.recycle(); - } - return result; - } catch (OutOfMemoryError e) { - return null; - } - } - - private static int getOrientation(Uri uri, ContentResolver contentResolver) { - InputStream inputStream; - try { - inputStream = contentResolver.openInputStream(uri); - } catch (FileNotFoundException e) { - Log.d(TAG, Log.getStackTraceString(e)); - return ExifInterface.ORIENTATION_UNDEFINED; - } - if (inputStream == null) { - return ExifInterface.ORIENTATION_UNDEFINED; - } - ExifInterface exifInterface; - try { - exifInterface = new ExifInterface(inputStream); - } catch (IOException e) { - Log.d(TAG, Log.getStackTraceString(e)); - IOUtils.closeQuietly(inputStream); - return ExifInterface.ORIENTATION_UNDEFINED; - } - int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, - ExifInterface.ORIENTATION_NORMAL); - IOUtils.closeQuietly(inputStream); - return orientation; - } - @Override protected Boolean doInBackground(Uri... uris) { resultList = new ArrayList<>(); @@ -147,7 +64,7 @@ public class DownsizeImageTask extends AsyncTask { BitmapFactory.decodeStream(inputStream, null, options); IOUtils.closeQuietly(inputStream); // Get EXIF data, for orientation info. - int orientation = getOrientation(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 @@ -176,7 +93,7 @@ public class DownsizeImageTask extends AsyncTask { if (scaledBitmap == null) { return false; } - Bitmap reorientedBitmap = reorientBitmap(scaledBitmap, orientation); + Bitmap reorientedBitmap = MediaUtils.reorientBitmap(scaledBitmap, orientation); if (reorientedBitmap == null) { scaledBitmap.recycle(); return false; diff --git a/app/src/main/java/com/keylesspalace/tusky/util/MediaUtils.java b/app/src/main/java/com/keylesspalace/tusky/util/MediaUtils.java index 2e93fc925..0ad3adb19 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/MediaUtils.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/MediaUtils.java @@ -20,6 +20,7 @@ import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.Matrix; import android.media.MediaMetadataRetriever; import android.media.ThumbnailUtils; import android.net.Uri; @@ -27,6 +28,7 @@ import android.provider.OpenableColumns; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.Px; +import android.support.media.ExifInterface; import android.util.Log; import java.io.ByteArrayOutputStream; @@ -111,7 +113,9 @@ public class MediaUtils { options.inJustDecodeBounds = false; try { stream = contentResolver.openInputStream(uri); - return BitmapFactory.decodeStream(stream, null, options); + Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options); + int orientation = getImageOrientation(uri, contentResolver); + return reorientBitmap(bitmap, orientation); } catch (FileNotFoundException e) { Log.w(TAG, e); return null; @@ -176,4 +180,81 @@ public class MediaUtils { return inSampleSize; } + + @Nullable + public static Bitmap reorientBitmap(Bitmap bitmap, int orientation) { + Matrix matrix = new Matrix(); + switch (orientation) { + default: + case ExifInterface.ORIENTATION_NORMAL: { + return bitmap; + } + case ExifInterface.ORIENTATION_FLIP_HORIZONTAL: { + matrix.setScale(-1, 1); + break; + } + case ExifInterface.ORIENTATION_ROTATE_180: { + matrix.setRotate(180); + break; + } + case ExifInterface.ORIENTATION_FLIP_VERTICAL: { + matrix.setRotate(180); + matrix.postScale(-1, 1); + break; + } + case ExifInterface.ORIENTATION_TRANSPOSE: { + matrix.setRotate(90); + matrix.postScale(-1, 1); + break; + } + case ExifInterface.ORIENTATION_ROTATE_90: { + matrix.setRotate(90); + break; + } + case ExifInterface.ORIENTATION_TRANSVERSE: { + matrix.setRotate(-90); + matrix.postScale(-1, 1); + break; + } + case ExifInterface.ORIENTATION_ROTATE_270: { + matrix.setRotate(-90); + break; + } + } + try { + Bitmap result = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), + bitmap.getHeight(), matrix, true); + if (!bitmap.sameAs(result)) { + bitmap.recycle(); + } + return result; + } catch (OutOfMemoryError e) { + return null; + } + } + + public static int getImageOrientation(Uri uri, ContentResolver contentResolver) { + InputStream inputStream; + try { + inputStream = contentResolver.openInputStream(uri); + } catch (FileNotFoundException e) { + Log.w(TAG, e); + return ExifInterface.ORIENTATION_UNDEFINED; + } + if (inputStream == null) { + return ExifInterface.ORIENTATION_UNDEFINED; + } + ExifInterface exifInterface; + try { + exifInterface = new ExifInterface(inputStream); + } catch (IOException e) { + Log.w(TAG, e); + IOUtils.closeQuietly(inputStream); + return ExifInterface.ORIENTATION_UNDEFINED; + } + int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, + ExifInterface.ORIENTATION_NORMAL); + IOUtils.closeQuietly(inputStream); + return orientation; + } } From f72e08cae46c212c43aba1df85dd2309ee6ca727 Mon Sep 17 00:00:00 2001 From: Conny Duck Date: Mon, 3 Sep 2018 21:23:12 +0200 Subject: [PATCH 17/23] fix crash in notifications fragment, save account only when notificationid changed --- .../tusky/fragment/NotificationsFragment.java | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) 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 bf0749bf5..726a084b6 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java @@ -691,19 +691,23 @@ public class NotificationsFragment extends SFragment implements private void saveNewestNotificationId(List notifications) { AccountEntity account = accountManager.getActiveAccount(); - BigInteger lastNoti = new BigInteger(account.getLastNotificationId()); + if(account != null) { + BigInteger lastNoti = new BigInteger(account.getLastNotificationId()); - for (Notification noti : notifications) { - BigInteger a = new BigInteger(noti.getId()); - if (isBiggerThan(a, lastNoti)) { - lastNoti = a; + for (Notification noti : notifications) { + BigInteger a = new BigInteger(noti.getId()); + if (isBiggerThan(a, lastNoti)) { + lastNoti = a; + } + } + + String lastNotificationId = lastNoti.toString(); + if(!account.getLastNotificationId().equals(lastNotificationId)) { + Log.d(TAG, "saving newest noti id: " + lastNotificationId); + account.setLastNotificationId(lastNotificationId); + accountManager.saveAccount(account); } } - - Log.d(TAG, "saving newest noti id: " + lastNoti); - - account.setLastNotificationId(lastNoti.toString()); - accountManager.saveAccount(account); } private boolean isBiggerThan(BigInteger newId, BigInteger lastShownNotificationId) { From d49502f798535df3b4a3b818fa376bc72c777c66 Mon Sep 17 00:00:00 2001 From: qwazix Date: Tue, 4 Sep 2018 19:35:17 +0300 Subject: [PATCH 18/23] Make page margin to truly black for tablets. (#834) --- app/src/main/res/drawable/tab_page_margin_black.xml | 5 +++++ app/src/main/res/values/colors.xml | 1 + app/src/main/res/values/styles.xml | 1 + 3 files changed, 7 insertions(+) create mode 100644 app/src/main/res/drawable/tab_page_margin_black.xml diff --git a/app/src/main/res/drawable/tab_page_margin_black.xml b/app/src/main/res/drawable/tab_page_margin_black.xml new file mode 100644 index 000000000..a9d6d6cfa --- /dev/null +++ b/app/src/main/res/drawable/tab_page_margin_black.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 543bec8d3..f96ecf92b 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -49,6 +49,7 @@ #FFFFFF #111111 #111111 + #000000 #dfdfdf #8f8f8f diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index b6d4e6885..06b84cb45 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -172,6 +172,7 @@ @color/color_primary_black @color/text_color_primary_black @color/text_color_primary_black + @drawable/tab_page_margin_black