From 0c54623b43d24727e8d0f4dbfdbbaeb8bfcaf52b Mon Sep 17 00:00:00 2001 From: Ezerous Date: Thu, 3 Oct 2019 16:55:39 +0300 Subject: [PATCH] Added option to display relative time --- app/build.gradle | 1 + app/src/main/assets/apache_libraries.html | 6 + .../activities/main/recent/RecentAdapter.java | 34 ++- .../main/recent/RecentFragment.java | 27 +- .../activities/main/unread/UnreadAdapter.java | 26 +- .../main/unread/UnreadFragment.java | 21 +- .../activities/settings/SettingsActivity.java | 1 + .../activities/settings/SettingsFragment.java | 21 +- .../gr/thmmy/mthmmy/base/BaseApplication.java | 14 +- .../gr/thmmy/mthmmy/utils/DateTimeUtils.java | 64 +++++ .../mthmmy/utils/RelativeTimeTextView.java | 249 ++++++++++++++++++ .../utils/parsing/ThmmyDateTimeParser.java | 63 +++++ .../main/res/layout/fragment_recent_row.xml | 2 +- .../main/res/layout/fragment_unread_row.xml | 2 +- app/src/main/res/values/attrs.xml | 3 + app/src/main/res/values/strings.xml | 12 + .../res/xml-v21/app_preferences_guest.xml | 43 +++ .../main/res/xml-v21/app_preferences_user.xml | 66 +++++ .../res/xml-v26/app_preferences_guest.xml | 10 +- .../main/res/xml-v26/app_preferences_user.xml | 18 +- .../main/res/xml/app_preferences_guest.xml | 10 +- app/src/main/res/xml/app_preferences_user.xml | 18 +- 22 files changed, 631 insertions(+), 80 deletions(-) create mode 100644 app/src/main/java/gr/thmmy/mthmmy/utils/DateTimeUtils.java create mode 100644 app/src/main/java/gr/thmmy/mthmmy/utils/RelativeTimeTextView.java create mode 100644 app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ThmmyDateTimeParser.java create mode 100644 app/src/main/res/xml-v21/app_preferences_guest.xml create mode 100644 app/src/main/res/xml-v21/app_preferences_user.xml diff --git a/app/build.gradle b/app/build.gradle index a4f3e951..f059e070 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -90,6 +90,7 @@ dependencies { implementation 'com.squareup.picasso:picasso:2.5.2' implementation 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0' implementation 'org.jsoup:jsoup:1.10.3' //TODO: Warning: upgrading from 1.10.3 will break stuff! + implementation 'joda-time:joda-time:2.10.4' implementation 'com.github.franmontiel:PersistentCookieJar:1.0.1' implementation 'com.github.PhilJay:MPAndroidChart:3.0.3' implementation 'com.mikepenz:materialdrawer:6.1.1' diff --git a/app/src/main/assets/apache_libraries.html b/app/src/main/assets/apache_libraries.html index 911a8e50..d88ed36e 100644 --- a/app/src/main/assets/apache_libraries.html +++ b/app/src/main/assets/apache_libraries.html @@ -71,6 +71,12 @@
  • Grgit v3.0.0 (Copyright ©2018 Andrew Oberstar)
  • +
  • +
    Joda-Time v2.10.4 (Copyright ©2002-2019 Joda.org)
    +
  • +
  • +
    android-storage v2.1.0
    +
  • OkHttpProfiler v1.0.5
  • diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentAdapter.java index 15589d45..0bd447f6 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentAdapter.java @@ -1,6 +1,5 @@ package gr.thmmy.mthmmy.activities.main.recent; -import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -12,8 +11,10 @@ import androidx.recyclerview.widget.RecyclerView; import java.util.List; import gr.thmmy.mthmmy.R; +import gr.thmmy.mthmmy.base.BaseApplication; import gr.thmmy.mthmmy.base.BaseFragment; import gr.thmmy.mthmmy.model.TopicSummary; +import gr.thmmy.mthmmy.utils.RelativeTimeTextView; /** @@ -21,12 +22,10 @@ import gr.thmmy.mthmmy.model.TopicSummary; * specified {@link RecentFragment.RecentFragmentInteractionListener}. */ class RecentAdapter extends RecyclerView.Adapter { - private final Context context; private final List recentList; private final RecentFragment.RecentFragmentInteractionListener mListener; - RecentAdapter(Context context, @NonNull List topicSummaryList, BaseFragment.FragmentInteractionListener listener) { - this.context = context; + RecentAdapter(@NonNull List topicSummaryList, BaseFragment.FragmentInteractionListener listener) { this.recentList = topicSummaryList; mListener = (RecentFragment.RecentFragmentInteractionListener) listener; } @@ -43,22 +42,21 @@ class RecentAdapter extends RecyclerView.Adapter { @Override public void onBindViewHolder(final ViewHolder holder, final int position) { holder.mTitleView.setText(recentList.get(position).getSubject()); - holder.mDateTimeView.setText(recentList.get(position).getDateTimeModified()); - holder.mUserView.setText(recentList.get(position).getLastUser()); - - holder.topic = recentList.get(position); - holder.mView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { + String dateTimeString = recentList.get(position).getDateTimeModified(); + if(BaseApplication.getInstance().isDisplayRelativeTimeEnabled()) + holder.mDateTimeView.setReferenceTime(Long.valueOf(dateTimeString)); + else + holder.mDateTimeView.setText(dateTimeString); - if (null != mListener) { - // Notify the active callbacks interface (the activity, if the - // fragment is attached to one) that an item has been selected. - mListener.onRecentFragmentInteraction(holder.topic); //? - - } + holder.mUserView.setText(recentList.get(position).getLastUser()); + holder.topic = recentList.get(position); + holder.mView.setOnClickListener(v -> { + if (null != mListener) { + // Notify the active callbacks interface (the activity, if the + // fragment is attached to one) that an item has been selected. + mListener.onRecentFragmentInteraction(holder.topic); //? } }); } @@ -72,7 +70,7 @@ class RecentAdapter extends RecyclerView.Adapter { final View mView; final TextView mTitleView; final TextView mUserView; - final TextView mDateTimeView; + final RelativeTimeTextView mDateTimeView; public TopicSummary topic; ViewHolder(View view) { diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java index 1160692d..a1f21281 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java @@ -1,6 +1,7 @@ package gr.thmmy.mthmmy.activities.main.recent; import android.os.AsyncTask; +import android.os.Build; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -34,6 +35,9 @@ import me.zhanghai.android.materialprogressbar.MaterialProgressBar; import okhttp3.Response; import timber.log.Timber; +import static gr.thmmy.mthmmy.utils.DateTimeUtils.convertDateTime; +import static gr.thmmy.mthmmy.utils.parsing.ThmmyDateTimeParser.convertToTimestamp; + /** * A {@link BaseFragment} subclass. @@ -100,7 +104,7 @@ public class RecentFragment extends BaseFragment { // Set the adapter if (rootView instanceof RelativeLayout) { progressBar = rootView.findViewById(R.id.progressBar); - recentAdapter = new RecentAdapter(getActivity(), topicSummaries, fragmentInteractionListener); + recentAdapter = new RecentAdapter(topicSummaries, fragmentInteractionListener); CustomRecyclerView recyclerView = rootView.findViewById(R.id.list); LinearLayoutManager linearLayoutManager = new LinearLayoutManager(recyclerView.getContext()); @@ -186,18 +190,19 @@ public class RecentFragment extends BaseFragment { String dateTime = recent.get(i + 2).text(); pattern = Pattern.compile("\\[(.*)]"); matcher = pattern.matcher(dateTime); - if (matcher.find()) { + if (matcher.find()){ dateTime = matcher.group(1); - if (dateTime.contains(" am") || dateTime.contains(" pm") || - dateTime.contains(" πμ") || dateTime.contains(" μμ")) { - dateTime = dateTime.replaceAll(":[0-5][0-9] ", " "); - } else { - dateTime = dateTime.substring(0, dateTime.lastIndexOf(":")); - } - if (!dateTime.contains(",")) { - dateTime = dateTime.replaceAll(".+? ([0-9])", "$1"); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP + && BaseApplication.getInstance().isDisplayRelativeTimeEnabled()) { + dateTime=convertDateTime(dateTime, false); + String timestamp = convertToTimestamp(dateTime); + if(timestamp!=null) + dateTime=timestamp; } - } else + else + dateTime=convertDateTime(dateTime, true); + } + else throw new ParseException("Parsing failed (dateTime)"); fetchedRecent.add(new TopicSummary(link, title, lastUser, dateTime)); diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadAdapter.java index 3818145a..d213e480 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadAdapter.java @@ -11,8 +11,10 @@ import androidx.recyclerview.widget.RecyclerView; import java.util.List; import gr.thmmy.mthmmy.R; +import gr.thmmy.mthmmy.base.BaseApplication; import gr.thmmy.mthmmy.base.BaseFragment; import gr.thmmy.mthmmy.model.TopicSummary; +import gr.thmmy.mthmmy.utils.RelativeTimeTextView; class UnreadAdapter extends RecyclerView.Adapter { private final List unreadList; @@ -65,19 +67,21 @@ class UnreadAdapter extends RecyclerView.Adapter { final UnreadAdapter.ViewHolder viewHolder = (UnreadAdapter.ViewHolder) holder; viewHolder.mTitleView.setText(unreadList.get(holder.getAdapterPosition()).getSubject()); - viewHolder.mDateTimeView.setText(unreadList.get(holder.getAdapterPosition()).getDateTimeModified()); - viewHolder.mUserView.setText(unreadList.get(position).getLastUser()); + String dateTimeString=unreadList.get(holder.getAdapterPosition()).getDateTimeModified(); + if(BaseApplication.getInstance().isDisplayRelativeTimeEnabled()) + viewHolder.mDateTimeView.setReferenceTime(Long.valueOf(dateTimeString)); + else + viewHolder.mDateTimeView.setText(dateTimeString); + + viewHolder.mUserView.setText(unreadList.get(position).getLastUser()); viewHolder.topic = unreadList.get(holder.getAdapterPosition()); - viewHolder.mView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (null != mListener) { - // Notify the active callbacks interface (the activity, if the - // fragment is attached to one) that an item has been selected. - mListener.onUnreadFragmentInteraction(viewHolder.topic); //? - } + viewHolder.mView.setOnClickListener(v -> { + if (null != mListener) { + // Notify the active callbacks interface (the activity, if the + // fragment is attached to one) that an item has been selected. + mListener.onUnreadFragmentInteraction(viewHolder.topic); //? } }); } else if (holder instanceof UnreadAdapter.MarkReadViewHolder) { @@ -107,7 +111,7 @@ class UnreadAdapter extends RecyclerView.Adapter { final View mView; final TextView mTitleView; final TextView mUserView; - final TextView mDateTimeView; + final RelativeTimeTextView mDateTimeView; public TopicSummary topic; ViewHolder(View view) { diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadFragment.java index 1db4131e..950ca27a 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadFragment.java @@ -2,6 +2,7 @@ package gr.thmmy.mthmmy.activities.main.unread; import android.os.AsyncTask; +import android.os.Build; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -24,6 +25,7 @@ import java.util.ArrayList; import java.util.List; import gr.thmmy.mthmmy.R; +import gr.thmmy.mthmmy.base.BaseApplication; import gr.thmmy.mthmmy.base.BaseFragment; import gr.thmmy.mthmmy.model.TopicSummary; import gr.thmmy.mthmmy.session.SessionManager; @@ -36,6 +38,9 @@ import okhttp3.Request; import okhttp3.Response; import timber.log.Timber; +import static gr.thmmy.mthmmy.utils.DateTimeUtils.convertDateTime; +import static gr.thmmy.mthmmy.utils.parsing.ThmmyDateTimeParser.convertToTimestamp; + /** * A {@link BaseFragment} subclass. * Activities that contain this fragment must implement the @@ -211,17 +216,19 @@ public class UnreadFragment extends BaseFragment { Element lastUserAndDate = information.get(6); String lastUser = lastUserAndDate.select("a").text(); String dateTime = lastUserAndDate.select("span").html(); - //dateTime = dateTime.replace("
    ", ""); dateTime = dateTime.substring(0, dateTime.indexOf("
    ")); dateTime = dateTime.replace("", ""); dateTime = dateTime.replace("", ""); - if (dateTime.contains(" am") || dateTime.contains(" pm") || - dateTime.contains(" πμ") || dateTime.contains(" μμ")) - dateTime = dateTime.replaceAll(":[0-5][0-9] ", " "); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP + && BaseApplication.getInstance().isDisplayRelativeTimeEnabled()) { + dateTime=convertDateTime(dateTime, false); + String timestamp = convertToTimestamp(dateTime); + if(timestamp!=null) + dateTime=timestamp; + } else - dateTime = dateTime.substring(0, dateTime.lastIndexOf(":")); - if (!dateTime.contains(",")) - dateTime = dateTime.replaceAll(".+? ([0-9])", "$1"); + dateTime=convertDateTime(dateTime, true); fetchedTopicSummaries.add(new TopicSummary(link, title, lastUser, dateTime)); } diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsActivity.java index c9637dd0..6414a497 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsActivity.java @@ -9,6 +9,7 @@ import gr.thmmy.mthmmy.base.BaseActivity; public class SettingsActivity extends BaseActivity { public static final String DEFAULT_HOME_TAB = "pref_app_main_default_tab_key"; + public static final String DISPLAY_RELATIVE_TIME = "pref_app_display_relative_time_key"; public static final String NOTIFICATION_LED_KEY = "pref_notification_led_enable_key"; public static final String NOTIFICATION_VIBRATION_KEY = "pref_notification_vibration_enable_key"; public static final String POSTING_APP_SIGNATURE_ENABLE_KEY = "pref_posting_app_signature_enable_key"; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsFragment.java index ec5907b6..edd06356 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsFragment.java @@ -42,6 +42,8 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Shared public static final String SELECTED_RINGTONE = "selectedRingtoneKey"; private static final String SILENT_SELECTED = "STFU"; + private static final String UNREAD = "Unread"; + private SharedPreferences settingsFile; private PREFS_TYPE prefs_type = PREFS_TYPE.NOT_SET; @@ -65,7 +67,7 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Shared defaultHomeTabValues.add("1"); if(isLoggedIn = BaseApplication.getInstance().getSessionManager().isLoggedIn()){ - defaultHomeTabEntries.add("Unread"); + defaultHomeTabEntries.add(UNREAD); defaultHomeTabValues.add("2"); } } @@ -171,16 +173,16 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Shared if(isLoggedIn&& prefs_type==PREFS_TYPE.GUEST) { prefs_type = PREFS_TYPE.USER; setPreferencesFromResource(R.xml.app_preferences_user, getPreferenceScreen().getKey()); - if(!defaultHomeTabEntries.contains("Unread")){ - defaultHomeTabEntries.add("Unread"); + if(!defaultHomeTabEntries.contains(UNREAD)){ + defaultHomeTabEntries.add(UNREAD); defaultHomeTabValues.add("2"); } } else if(!isLoggedIn&&prefs_type==PREFS_TYPE.USER){ prefs_type = PREFS_TYPE.GUEST; setPreferencesFromResource(R.xml.app_preferences_guest,getPreferenceScreen().getKey()); - if(defaultHomeTabEntries.contains("Unread")){ - defaultHomeTabEntries.remove("Unread"); + if(defaultHomeTabEntries.contains(UNREAD)){ + defaultHomeTabEntries.remove(UNREAD); defaultHomeTabValues.remove("2"); } } @@ -201,7 +203,7 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Shared BaseApplication.getInstance().startFirebaseCrashlyticsCollection(); else { Timber.i("Crashlytics collection will be disabled after restarting."); - Toast.makeText(BaseApplication.getInstance().getApplicationContext(), "This change will take effect once you restart the app.", Toast.LENGTH_SHORT).show(); + displayRestartAppToTakeEffectToast(); } } else if (key.equals(getString(R.string.pref_privacy_analytics_enable_key))) { enabled = sharedPreferences.getBoolean(key, false); @@ -210,6 +212,13 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Shared Timber.i("Analytics collection enabled."); else Timber.i("Analytics collection disabled."); + } else if (key.equals(getString(R.string.pref_app_display_relative_time_key)) + && BaseApplication.getInstance().isDisplayRelativeTimeEnabled()!=sharedPreferences.getBoolean(key, false)){ + displayRestartAppToTakeEffectToast(); } } + + private void displayRestartAppToTakeEffectToast(){ + Toast.makeText(BaseApplication.getInstance().getApplicationContext(), "This change will take effect once you restart the app.", Toast.LENGTH_SHORT).show(); + } } \ No newline at end of file diff --git a/app/src/main/java/gr/thmmy/mthmmy/base/BaseApplication.java b/app/src/main/java/gr/thmmy/mthmmy/base/BaseApplication.java index ba0e6a0f..64ad4996 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/base/BaseApplication.java +++ b/app/src/main/java/gr/thmmy/mthmmy/base/BaseApplication.java @@ -49,6 +49,8 @@ import okhttp3.OkHttpClient; import okhttp3.Request; import timber.log.Timber; +import static gr.thmmy.mthmmy.activities.settings.SettingsActivity.DISPLAY_RELATIVE_TIME; + public class BaseApplication extends Application { private static BaseApplication baseApplication; //BaseApplication singleton @@ -60,6 +62,8 @@ public class BaseApplication extends Application { private OkHttpClient client; private SessionManager sessionManager; + private boolean displayRelativeTime; + //TODO: maybe use PreferenceManager.getDefaultSharedPreferences here as well? private static final String SHARED_PREFS = "ThmmySharedPrefs"; @@ -104,12 +108,11 @@ public class BaseApplication extends Application { .addInterceptor(chain -> { Request request = chain.request(); HttpUrl oldUrl = chain.request().url(); - if (Objects.equals(chain.request().url().host(), "www.thmmy.gr")) { - if (!oldUrl.toString().contains("theme=4")) { + if (Objects.equals(chain.request().url().host(), "www.thmmy.gr") + && !oldUrl.toString().contains("theme=4")) { //Probably works but needs more testing: HttpUrl newUrl = oldUrl.newBuilder().addQueryParameter("theme", "4").build(); request = request.newBuilder().url(newUrl).build(); - } } return chain.proceed(request); }) @@ -173,6 +176,8 @@ public class BaseApplication extends Application { DisplayMetrics displayMetrics = getApplicationContext().getResources().getDisplayMetrics(); dpWidth = displayMetrics.widthPixels / displayMetrics.density; + + displayRelativeTime = settingsSharedPrefs.getBoolean(DISPLAY_RELATIVE_TIME, false); } //Getters @@ -192,6 +197,9 @@ public class BaseApplication extends Application { return dpWidth; } + public boolean isDisplayRelativeTimeEnabled() { + return displayRelativeTime; + } //--------------------Firebase-------------------- diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/DateTimeUtils.java b/app/src/main/java/gr/thmmy/mthmmy/utils/DateTimeUtils.java new file mode 100644 index 00000000..7802f931 --- /dev/null +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/DateTimeUtils.java @@ -0,0 +1,64 @@ +package gr.thmmy.mthmmy.utils; + +import static android.text.format.DateUtils.DAY_IN_MILLIS; +import static android.text.format.DateUtils.HOUR_IN_MILLIS; +import static android.text.format.DateUtils.MINUTE_IN_MILLIS; +import static android.text.format.DateUtils.SECOND_IN_MILLIS; +import static android.text.format.DateUtils.YEAR_IN_MILLIS; + +public class DateTimeUtils { + //TODO: move this function to ThmmyDateTimeParser class once KitKat support is dropped + public static String convertDateTime(String dateTime, boolean removeSeconds){ + //Convert e.g. Today at 12:16:48 -> 12:16:48, but October 03, 2019, 16:40:18 remains as is + if (!dateTime.contains(",")) + dateTime = dateTime.replaceAll(".+? ([0-9])", "$1"); + + //Remove seconds + if(removeSeconds) + dateTime = dateTime.replaceAll("(.+?)(:[0-5][0-9])($|\\s)", "$1$3"); + + return dateTime; + } + + private static final long MONTH_IN_MILLIS = DAY_IN_MILLIS*30; + private static final long DECADE_IN_MILLIS = YEAR_IN_MILLIS*10; + + static CharSequence getRelativeTimeSpanString(long time, long now, long minResolution) { + boolean past = (now >= time); + long duration = Math.abs(now - time); + String format; + long count; + if (duration < MINUTE_IN_MILLIS && minResolution < MINUTE_IN_MILLIS) { + count = duration / SECOND_IN_MILLIS; + format = "%d sec"; + } else if (duration < HOUR_IN_MILLIS && minResolution < HOUR_IN_MILLIS) { + count = duration / MINUTE_IN_MILLIS; + format = "%d min"; + } else if (duration < DAY_IN_MILLIS && minResolution < DAY_IN_MILLIS) { + count = duration / HOUR_IN_MILLIS; + format = "%d hour"; + if(count>1) + format = format + 's'; + } else if (duration < MONTH_IN_MILLIS && minResolution < MONTH_IN_MILLIS) { + count = duration / DAY_IN_MILLIS; + format = "%d day"; + if(count>1) + format = format + 's'; + } else if (duration < YEAR_IN_MILLIS && minResolution < YEAR_IN_MILLIS) { + count = duration / MONTH_IN_MILLIS; + format = "%d month"; + if(count>1) + format = format + 's'; + } else if (duration < DECADE_IN_MILLIS && minResolution < DECADE_IN_MILLIS) { + count = duration / YEAR_IN_MILLIS; + format = "%d year"; + if(count>1) + format = format + 's'; + } + else + return past ? "a long time ago": "in the distant future"; + + format = past ? format : "in " + format; + return String.format(format, (int) count); + } +} diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/RelativeTimeTextView.java b/app/src/main/java/gr/thmmy/mthmmy/utils/RelativeTimeTextView.java new file mode 100644 index 00000000..083408c4 --- /dev/null +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/RelativeTimeTextView.java @@ -0,0 +1,249 @@ +package gr.thmmy.mthmmy.utils; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.TypedArray; +import android.os.Handler; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.format.DateUtils; +import android.util.AttributeSet; +import android.view.View; +import android.widget.TextView; + +import java.lang.ref.WeakReference; + +import gr.thmmy.mthmmy.R; + +import static gr.thmmy.mthmmy.utils.DateTimeUtils.getRelativeTimeSpanString; + +/** + * A modified version of https://github.com/curioustechizen/android-ago + */ +@SuppressLint("AppCompatCustomView") +public class RelativeTimeTextView extends TextView { + + private static final long INITIAL_UPDATE_INTERVAL = DateUtils.MINUTE_IN_MILLIS; + + private long mReferenceTime; + private Handler mHandler = new Handler(); + private UpdateTimeRunnable mUpdateTimeTask; + private boolean isUpdateTaskRunning = false; + + public RelativeTimeTextView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs); + } + + public RelativeTimeTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context, attrs); + } + + private void init(Context context, AttributeSet attrs) { + TypedArray a = context.getTheme().obtainStyledAttributes(attrs, + R.styleable.RelativeTimeTextView, 0, 0); + String referenceTimeText; + try { + referenceTimeText = a.getString(R.styleable.RelativeTimeTextView_reference_time); + } finally { + a.recycle(); + } + + try { + mReferenceTime = Long.valueOf(referenceTimeText); + } catch (NumberFormatException nfe) { + /* + * TODO: Better exception handling + */ + mReferenceTime = -1L; + } + + setReferenceTime(mReferenceTime); + + } + + /** + * Sets the reference time for this view. At any moment, the view will render a relative time period relative to the time set here. + *

    + * This value can also be set with the XML attribute {@code reference_time} + * @param referenceTime The timestamp (in milliseconds since epoch) that will be the reference point for this view. + */ + public void setReferenceTime(long referenceTime) { + this.mReferenceTime = referenceTime; + + /* + * Note that this method could be called when a row in a ListView is recycled. + * Hence, we need to first stop any currently running schedules (for example from the recycled view. + */ + stopTaskForPeriodicallyUpdatingRelativeTime(); + + /* + * Instantiate a new runnable with the new reference time + */ + initUpdateTimeTask(); + + /* + * Start a new schedule. + */ + startTaskForPeriodicallyUpdatingRelativeTime(); + + /* + * Finally, update the text display. + */ + updateTextDisplay(); + } + + private void updateTextDisplay() { + /* + * TODO: Validation, Better handling of negative cases + */ + if (this.mReferenceTime == -1L) + return; + setText(getRelativeTimeDisplayString(mReferenceTime, System.currentTimeMillis())); + } + + /** + * Get the text to display for relative time. + *
    + * @param referenceTime The reference time passed in through {@link #setReferenceTime(long)} or through {@code reference_time} attribute + * @param now The current time + * @return The display text for the relative time + */ + protected CharSequence getRelativeTimeDisplayString(long referenceTime, long now) { + long difference = now - referenceTime; + return (difference >= 0 && difference<=DateUtils.MINUTE_IN_MILLIS) ? + "just now" : + getRelativeTimeSpanString( + mReferenceTime, + now, + DateUtils.MINUTE_IN_MILLIS); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + startTaskForPeriodicallyUpdatingRelativeTime(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + stopTaskForPeriodicallyUpdatingRelativeTime(); + } + + @Override + protected void onVisibilityChanged(View changedView, int visibility) { + super.onVisibilityChanged(changedView, visibility); + if (visibility == GONE || visibility == INVISIBLE) { + stopTaskForPeriodicallyUpdatingRelativeTime(); + } else { + startTaskForPeriodicallyUpdatingRelativeTime(); + } + } + + private void startTaskForPeriodicallyUpdatingRelativeTime() { + if(mUpdateTimeTask.isDetached()) initUpdateTimeTask(); + mHandler.post(mUpdateTimeTask); + isUpdateTaskRunning = true; + } + + private void initUpdateTimeTask() { + mUpdateTimeTask = new UpdateTimeRunnable(this, mReferenceTime); + } + + private void stopTaskForPeriodicallyUpdatingRelativeTime() { + if(isUpdateTaskRunning) { + mUpdateTimeTask.detach(); + mHandler.removeCallbacks(mUpdateTimeTask); + isUpdateTaskRunning = false; + } + } + + @Override + public Parcelable onSaveInstanceState() { + Parcelable superState = super.onSaveInstanceState(); + SavedState ss = new SavedState(superState); + ss.referenceTime = mReferenceTime; + return ss; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + if (!(state instanceof SavedState)) { + super.onRestoreInstanceState(state); + return; + } + + SavedState ss = (SavedState)state; + mReferenceTime = ss.referenceTime; + super.onRestoreInstanceState(ss.getSuperState()); + } + + public static class SavedState extends BaseSavedState { + + private long referenceTime; + + public SavedState(Parcelable superState) { + super(superState); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeLong(referenceTime); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + + private SavedState(Parcel in) { + super(in); + referenceTime = in.readLong(); + } + } + + private static class UpdateTimeRunnable implements Runnable { + + private long mRefTime; + private final WeakReference weakRefRttv; + + UpdateTimeRunnable(RelativeTimeTextView rttv, long refTime) { + this.mRefTime = refTime; + weakRefRttv = new WeakReference<>(rttv); + } + + boolean isDetached() { + return weakRefRttv.get() == null; + } + + void detach() { + weakRefRttv.clear(); + } + + @Override + public void run() { + RelativeTimeTextView rttv = weakRefRttv.get(); + if (rttv == null) return; + long difference = Math.abs(System.currentTimeMillis() - mRefTime); + long interval = INITIAL_UPDATE_INTERVAL; + if (difference > DateUtils.WEEK_IN_MILLIS) { + interval = DateUtils.WEEK_IN_MILLIS; + } else if (difference > DateUtils.DAY_IN_MILLIS) { + interval = DateUtils.DAY_IN_MILLIS; + } else if (difference > DateUtils.HOUR_IN_MILLIS) { + interval = DateUtils.HOUR_IN_MILLIS; + } + rttv.updateTextDisplay(); + rttv.mHandler.postDelayed(this, interval); + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ThmmyDateTimeParser.java b/app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ThmmyDateTimeParser.java new file mode 100644 index 00000000..9524e5fe --- /dev/null +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ThmmyDateTimeParser.java @@ -0,0 +1,63 @@ +package gr.thmmy.mthmmy.utils.parsing; + + +import android.os.Build; + +import androidx.annotation.RequiresApi; + +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; +import org.joda.time.format.DateTimeFormatterBuilder; +import org.joda.time.format.DateTimeParser; + +import java.util.Locale; + +import gr.thmmy.mthmmy.base.BaseApplication; +import timber.log.Timber; + +@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) +public class ThmmyDateTimeParser { + private static final DateTimeParser[] parsers = { + DateTimeFormat.forPattern("HH:mm:ss").getParser(), + DateTimeFormat.forPattern("HH:mm:ss a").getParser(), + DateTimeFormat.forPattern("MMMM d, Y, HH:mm:ss").getParser(), + DateTimeFormat.forPattern("MMMM d, Y, HH:mm:ss a").getParser() + }; + + private static final DateTimeFormatter formatter = new DateTimeFormatterBuilder() + .append(null, parsers) + .toFormatter(); + + private static final Locale greekLocale = Locale.forLanguageTag("el-GR"); + private static final Locale englishLocale = Locale.forLanguageTag("en-US"); + + public static String convertToTimestamp(String thmmyDateTime){ + DateTimeZone dtz; + if(!BaseApplication.getInstance().getSessionManager().isLoggedIn()) + dtz = DateTimeZone.forID("Europe/Athens"); + else + dtz = DateTimeZone.getDefault(); + + //Add today's date for the first two cases + if(Character.isDigit(thmmyDateTime.charAt(0))) + thmmyDateTime = (new DateTime()).toString("MMMM d, Y, ") + thmmyDateTime; + + DateTime dateTime; + try{ + dateTime=formatter.withZone(dtz).withLocale(greekLocale).parseDateTime(thmmyDateTime); + } + catch (IllegalArgumentException e1){ + Timber.i("Parsing DateTime using Greek Locale failed."); + try{ + dateTime=formatter.withZone(dtz).withLocale(englishLocale).parseDateTime(thmmyDateTime); + } + catch (IllegalArgumentException e2){ + Timber.e("Couldn't parse DateTime %s", thmmyDateTime); + return null; + } + } + return Long.toString(dateTime.getMillis()); + } +} diff --git a/app/src/main/res/layout/fragment_recent_row.xml b/app/src/main/res/layout/fragment_recent_row.xml index 7d136a4e..07041cc5 100644 --- a/app/src/main/res/layout/fragment_recent_row.xml +++ b/app/src/main/res/layout/fragment_recent_row.xml @@ -32,7 +32,7 @@ android:layout_below="@+id/title" android:layout_toEndOf="@+id/dateTime"/> - - + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fabd3e8a..8446551f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -168,26 +168,38 @@ Settings App + pref_app_main_default_tab_key Default home tab Sets a home screen tab as default Default home tab + pref_app_display_relative_time_key + Display relative time + Considering that you haven\'t set some weird custom time format Notifications + pref_notification_vibration_enable_key Vibration + pref_notification_led_enable_key Notifications led Enables/disables the notifications led (if the device has one) + pref_notifications_select_sound_key Notifications sound Sets your preferred notification sound Posting + pref_category_posting_key + pref_posting_app_signature_enable_key App signature Appends a \"sent from mTHMMY\" message to your posts Uploading + pref_category_uploading_key + pref_uploading_app_signature_enable_key App signature Appends an \"uploaded from mTHMMY\" message to the descriptions of your uploads Privacy + pref_category_privacy_key pref_privacy_crashlytics_enable_key Crash data reports Automatically send us anonymized reports of errors and crashes diff --git a/app/src/main/res/xml-v21/app_preferences_guest.xml b/app/src/main/res/xml-v21/app_preferences_guest.xml new file mode 100644 index 00000000..78c7c8ca --- /dev/null +++ b/app/src/main/res/xml-v21/app_preferences_guest.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/xml-v21/app_preferences_user.xml b/app/src/main/res/xml-v21/app_preferences_user.xml new file mode 100644 index 00000000..c132270f --- /dev/null +++ b/app/src/main/res/xml-v21/app_preferences_user.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/xml-v26/app_preferences_guest.xml b/app/src/main/res/xml-v26/app_preferences_guest.xml index 942c2692..78c7c8ca 100644 --- a/app/src/main/res/xml-v26/app_preferences_guest.xml +++ b/app/src/main/res/xml-v26/app_preferences_guest.xml @@ -10,14 +10,20 @@ android:dialogTitle="@string/pref_app_main_default_tab_dialog_title" android:entries="@array/pref_app_main_default_tab_entries" android:entryValues="@array/pref_app_main_default_tab_values" - android:key="pref_app_main_default_tab_key" + android:key="@string/pref_app_main_default_tab_key" android:title="@string/pref_title_app_main_default_tab" android:summary="@string/pref_summary_app_main_default_tab" app:iconSpaceReserved="false" /> + + @@ -21,24 +21,24 @@ app:iconSpaceReserved="false"> @@ -21,48 +21,48 @@ app:iconSpaceReserved="false">