Browse Source

Added option to display relative time

pull/63/head
Ezerous 5 years ago
parent
commit
0c54623b43
  1. 1
      app/build.gradle
  2. 6
      app/src/main/assets/apache_libraries.html
  3. 34
      app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentAdapter.java
  4. 27
      app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java
  5. 26
      app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadAdapter.java
  6. 21
      app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadFragment.java
  7. 1
      app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsActivity.java
  8. 21
      app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsFragment.java
  9. 14
      app/src/main/java/gr/thmmy/mthmmy/base/BaseApplication.java
  10. 64
      app/src/main/java/gr/thmmy/mthmmy/utils/DateTimeUtils.java
  11. 249
      app/src/main/java/gr/thmmy/mthmmy/utils/RelativeTimeTextView.java
  12. 63
      app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ThmmyDateTimeParser.java
  13. 2
      app/src/main/res/layout/fragment_recent_row.xml
  14. 2
      app/src/main/res/layout/fragment_unread_row.xml
  15. 3
      app/src/main/res/values/attrs.xml
  16. 12
      app/src/main/res/values/strings.xml
  17. 43
      app/src/main/res/xml-v21/app_preferences_guest.xml
  18. 66
      app/src/main/res/xml-v21/app_preferences_user.xml
  19. 10
      app/src/main/res/xml-v26/app_preferences_guest.xml
  20. 18
      app/src/main/res/xml-v26/app_preferences_user.xml
  21. 10
      app/src/main/res/xml/app_preferences_guest.xml
  22. 18
      app/src/main/res/xml/app_preferences_user.xml

1
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'

6
app/src/main/assets/apache_libraries.html

@ -71,6 +71,12 @@
<li>
<h5><a href="https://github.com/ajoberstar/grgit">Grgit</a>&nbsp;v3.0.0 (Copyright ©2018 Andrew Oberstar)</h5>
</li>
<li>
<h5><a href="https://github.com/JodaOrg/joda-time">Joda-Time</a>&nbsp;v2.10.4 (Copyright ©2002-2019 Joda.org)</h5>
</li>
<li>
<h5><a href="https://github.com/sromku/android-storage">android-storage</a>&nbsp;v2.1.0</h5>
</li>
<li>
<h5><a href=https://github.com/itkacher/OkHttpProfiler">OkHttpProfiler</a>&nbsp;v1.0.5</h5>
</li>

34
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<RecentAdapter.ViewHolder> {
private final Context context;
private final List<TopicSummary> recentList;
private final RecentFragment.RecentFragmentInteractionListener mListener;
RecentAdapter(Context context, @NonNull List<TopicSummary> topicSummaryList, BaseFragment.FragmentInteractionListener listener) {
this.context = context;
RecentAdapter(@NonNull List<TopicSummary> topicSummaryList, BaseFragment.FragmentInteractionListener listener) {
this.recentList = topicSummaryList;
mListener = (RecentFragment.RecentFragmentInteractionListener) listener;
}
@ -43,22 +42,21 @@ class RecentAdapter extends RecyclerView.Adapter<RecentAdapter.ViewHolder> {
@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<RecentAdapter.ViewHolder> {
final View mView;
final TextView mTitleView;
final TextView mUserView;
final TextView mDateTimeView;
final RelativeTimeTextView mDateTimeView;
public TopicSummary topic;
ViewHolder(View view) {

27
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));

26
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<RecyclerView.ViewHolder> {
private final List<TopicSummary> unreadList;
@ -65,19 +67,21 @@ class UnreadAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
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<RecyclerView.ViewHolder> {
final View mView;
final TextView mTitleView;
final TextView mUserView;
final TextView mDateTimeView;
final RelativeTimeTextView mDateTimeView;
public TopicSummary topic;
ViewHolder(View view) {

21
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("<br>", "");
dateTime = dateTime.substring(0, dateTime.indexOf("<br>"));
dateTime = dateTime.replace("<b>", "");
dateTime = dateTime.replace("</b>", "");
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));
}

1
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";

21
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();
}
}

14
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--------------------

64
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);
}
}

249
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.
* <p/>
* 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.
* <br/>
* @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<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
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<RelativeTimeTextView> 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);
}
}
}

63
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());
}
}

2
app/src/main/res/layout/fragment_recent_row.xml

@ -32,7 +32,7 @@
android:layout_below="@+id/title"
android:layout_toEndOf="@+id/dateTime"/>
<TextView
<gr.thmmy.mthmmy.utils.RelativeTimeTextView
android:id="@+id/dateTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

2
app/src/main/res/layout/fragment_unread_row.xml

@ -32,7 +32,7 @@
android:layout_below="@+id/title"
android:layout_toEndOf="@+id/dateTime"/>
<TextView
<gr.thmmy.mthmmy.utils.RelativeTimeTextView
android:id="@+id/dateTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

3
app/src/main/res/values/attrs.xml

@ -3,4 +3,7 @@
<declare-styleable name="EditorView" >
<attr name="hint" format="string" />
</declare-styleable>
<declare-styleable name="RelativeTimeTextView">
<attr name="reference_time" format="string" />
</declare-styleable>
</resources>

12
app/src/main/res/values/strings.xml

@ -168,26 +168,38 @@
<string name="title_activity_settings">Settings</string>
<string name="pref_category_app">App</string>
<string name="pref_app_main_default_tab_key">pref_app_main_default_tab_key</string>
<string name="pref_title_app_main_default_tab">Default home tab</string>
<string name="pref_summary_app_main_default_tab">Sets a home screen tab as default</string>
<string name="pref_app_main_default_tab_dialog_title">Default home tab</string>
<string name="pref_app_display_relative_time_key">pref_app_display_relative_time_key</string>
<string name="pref_title_display_relative_time">Display relative time</string>
<string name="pref_summary_display_relative_time">Considering that you haven\'t set some weird custom time format</string>
<string name="pref_category_notifications">Notifications</string>
<string name="pref_notification_vibration_enable_key">pref_notification_vibration_enable_key</string>
<string name="pref_title_notification_vibration_enable">Vibration</string>
<string name="pref_notification_led_enable_key">pref_notification_led_enable_key</string>
<string name="pref_title_notification_led_enable">Notifications led</string>
<string name="pref_summary_notification_led_enable">Enables/disables the notifications led (if the device has one)</string>
<string name="pref_notifications_select_sound_key">pref_notifications_select_sound_key</string>
<string name="pref_title_notifications_sound">Notifications sound</string>
<string name="pref_summary_notifications_sound">Sets your preferred notification sound</string>
<string name="pref_category_posting">Posting</string>
<string name="pref_category_posting_key">pref_category_posting_key</string>
<string name="pref_posting_app_signature_enable_key">pref_posting_app_signature_enable_key</string>
<string name="pref_title_posting_app_signature_enable">App signature</string>
<string name="pref_summary_posting_app_signature_enable">Appends a \"sent from mTHMMY\" message to your posts</string>
<string name="pref_category_uploading">Uploading</string>
<string name="pref_category_uploading_key">pref_category_uploading_key</string>
<string name="pref_uploading_app_signature_enable_key">pref_uploading_app_signature_enable_key</string>
<string name="pref_title_uploading_app_signature_enable">App signature</string>
<string name="pref_summary_uploading_app_signature_enable">Appends an \"uploaded from mTHMMY\" message to the descriptions of your uploads</string>
<string name="pref_category_privacy">Privacy</string>
<string name="pref_category_privacy_key">pref_category_privacy_key</string>
<string name="pref_privacy_crashlytics_enable_key">pref_privacy_crashlytics_enable_key</string>
<string name="pref_title_privacy_crashlytics_enable">Crash data reports</string>
<string name="pref_summary_privacy_crashlytics_enable">Automatically send us anonymized reports of errors and crashes</string>

43
app/src/main/res/xml-v21/app_preferences_guest.xml

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.preference.PreferenceCategory
android:title="@string/pref_category_app"
app:iconSpaceReserved="false">
<androidx.preference.ListPreference
android:defaultValue="0"
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="@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" />
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="@string/pref_app_display_relative_time_key"
android:title="@string/pref_title_display_relative_time"
android:summary="@string/pref_summary_display_relative_time"
app:iconSpaceReserved="false" />
</androidx.preference.PreferenceCategory>
<androidx.preference.PreferenceCategory
android:key="@string/pref_category_privacy_key"
android:title="@string/pref_category_privacy"
app:iconSpaceReserved="false">
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="@string/pref_privacy_crashlytics_enable_key"
android:title="@string/pref_title_privacy_crashlytics_enable"
android:summary="@string/pref_summary_privacy_crashlytics_enable"
app:iconSpaceReserved="false" />
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="@string/pref_privacy_analytics_enable_key"
android:title="@string/pref_title_privacy_analytics_enable"
android:summary="@string/pref_summary_privacy_analytics_enable"
app:iconSpaceReserved="false" />
</androidx.preference.PreferenceCategory>
</androidx.preference.PreferenceScreen>

66
app/src/main/res/xml-v21/app_preferences_user.xml

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.preference.PreferenceCategory
android:title="@string/pref_category_app"
app:iconSpaceReserved="false">
<androidx.preference.ListPreference
android:defaultValue="0"
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="@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" />
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="@string/pref_app_display_relative_time_key"
android:title="@string/pref_title_display_relative_time"
android:summary="@string/pref_summary_display_relative_time"
app:iconSpaceReserved="false" />
</androidx.preference.PreferenceCategory>
<androidx.preference.PreferenceCategory
android:key="@string/pref_category_posting_key"
android:title="@string/pref_category_posting"
app:iconSpaceReserved="false">
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="@string/pref_posting_app_signature_enable_key"
android:title="@string/pref_title_posting_app_signature_enable"
android:summary="@string/pref_summary_posting_app_signature_enable"
app:iconSpaceReserved="false" />
</androidx.preference.PreferenceCategory>
<androidx.preference.PreferenceCategory
android:key="@string/pref_category_uploading_key"
android:title="@string/pref_category_uploading"
app:iconSpaceReserved="false">
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="@string/pref_uploading_app_signature_enable_key"
android:title="@string/pref_title_uploading_app_signature_enable"
android:summary="@string/pref_summary_uploading_app_signature_enable"
app:iconSpaceReserved="false" />
</androidx.preference.PreferenceCategory>
<androidx.preference.PreferenceCategory
android:key="@string/pref_category_privacy_key"
android:title="@string/pref_category_privacy"
app:iconSpaceReserved="false">
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="@string/pref_privacy_crashlytics_enable_key"
android:title="@string/pref_title_privacy_crashlytics_enable"
android:summary="@string/pref_summary_privacy_crashlytics_enable"
app:iconSpaceReserved="false" />
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="@string/pref_privacy_analytics_enable_key"
android:title="@string/pref_title_privacy_analytics_enable"
android:summary="@string/pref_summary_privacy_analytics_enable"
app:iconSpaceReserved="false" />
</androidx.preference.PreferenceCategory>
</androidx.preference.PreferenceScreen>

10
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" />
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="@string/pref_app_display_relative_time_key"
android:title="@string/pref_title_display_relative_time"
android:summary="@string/pref_summary_display_relative_time"
app:iconSpaceReserved="false" />
</androidx.preference.PreferenceCategory>
<androidx.preference.PreferenceCategory
android:key="pref_category_privacy_key"
android:key="@string/pref_category_privacy_key"
android:title="@string/pref_category_privacy"
app:iconSpaceReserved="false">
<androidx.preference.SwitchPreferenceCompat

18
app/src/main/res/xml-v26/app_preferences_user.xml

@ -10,38 +10,44 @@
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" />
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="@string/pref_app_display_relative_time_key"
android:title="@string/pref_title_display_relative_time"
android:summary="@string/pref_summary_display_relative_time"
app:iconSpaceReserved="false" />
</androidx.preference.PreferenceCategory>
<androidx.preference.PreferenceCategory
android:key="pref_category_posting_key"
android:key="@string/pref_category_posting_key"
android:title="@string/pref_category_posting"
app:iconSpaceReserved="false">
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="pref_posting_app_signature_enable_key"
android:key="@string/pref_posting_app_signature_enable_key"
android:title="@string/pref_title_posting_app_signature_enable"
android:summary="@string/pref_summary_posting_app_signature_enable"
app:iconSpaceReserved="false" />
</androidx.preference.PreferenceCategory>
<androidx.preference.PreferenceCategory
android:key="pref_category_uploading_key"
android:key="@string/pref_category_uploading_key"
android:title="@string/pref_category_uploading"
app:iconSpaceReserved="false">
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="pref_uploading_app_signature_enable_key"
android:key="@string/pref_uploading_app_signature_enable_key"
android:title="@string/pref_title_uploading_app_signature_enable"
android:summary="@string/pref_summary_uploading_app_signature_enable"
app:iconSpaceReserved="false" />
</androidx.preference.PreferenceCategory>
<androidx.preference.PreferenceCategory
android:key="pref_category_privacy_key"
android:key="@string/pref_category_privacy_key"
android:title="@string/pref_category_privacy"
app:iconSpaceReserved="false">
<androidx.preference.SwitchPreferenceCompat

10
app/src/main/res/xml/app_preferences_guest.xml

@ -10,7 +10,7 @@
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">
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="pref_notification_vibration_enable_key"
android:key="@string/pref_notification_vibration_enable_key"
android:title="@string/pref_title_notification_vibration_enable"
app:iconSpaceReserved="false" />
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="pref_notification_led_enable_key"
android:key="@string/pref_notification_led_enable_key"
android:title="@string/pref_title_notification_led_enable"
android:summary="@string/pref_summary_notification_led_enable"
app:iconSpaceReserved="false" />
<Preference
android:key="pref_notifications_select_sound_key"
android:key="@string/pref_notifications_select_sound_key"
android:title="@string/pref_title_notifications_sound"
android:summary="@string/pref_summary_notifications_sound"
app:iconSpaceReserved="false" />
</androidx.preference.PreferenceCategory>
<androidx.preference.PreferenceCategory
android:key="pref_category_privacy_key"
android:key="@string/pref_category_privacy_key"
android:title="@string/pref_category_privacy"
app:iconSpaceReserved="false">
<androidx.preference.SwitchPreferenceCompat

18
app/src/main/res/xml/app_preferences_user.xml

@ -10,7 +10,7 @@
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,48 +21,48 @@
app:iconSpaceReserved="false">
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="pref_notification_vibration_enable_key"
android:key="@string/pref_notification_vibration_enable_key"
android:title="@string/pref_title_notification_vibration_enable"
app:iconSpaceReserved="false" />
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="pref_notification_led_enable_key"
android:key="@string/pref_notification_led_enable_key"
android:title="@string/pref_title_notification_led_enable"
android:summary="@string/pref_summary_notification_led_enable"
app:iconSpaceReserved="false" />
<Preference
android:key="pref_notifications_select_sound_key"
android:key="@string/pref_notifications_select_sound_key"
android:title="@string/pref_title_notifications_sound"
android:summary="@string/pref_summary_notifications_sound"
app:iconSpaceReserved="false" />
</androidx.preference.PreferenceCategory>
<androidx.preference.PreferenceCategory
android:key="pref_category_posting_key"
android:key="@string/pref_category_posting_key"
android:title="@string/pref_category_posting"
app:iconSpaceReserved="false">
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="pref_posting_app_signature_enable_key"
android:key="@string/pref_posting_app_signature_enable_key"
android:title="@string/pref_title_posting_app_signature_enable"
android:summary="@string/pref_summary_posting_app_signature_enable"
app:iconSpaceReserved="false" />
</androidx.preference.PreferenceCategory>
<androidx.preference.PreferenceCategory
android:key="pref_category_uploading_key"
android:key="@string/pref_category_uploading_key"
android:title="@string/pref_category_uploading"
app:iconSpaceReserved="false">
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="pref_uploading_app_signature_enable_key"
android:key="@string/pref_uploading_app_signature_enable_key"
android:title="@string/pref_title_uploading_app_signature_enable"
android:summary="@string/pref_summary_uploading_app_signature_enable"
app:iconSpaceReserved="false" />
</androidx.preference.PreferenceCategory>
<androidx.preference.PreferenceCategory
android:key="pref_category_privacy_key"
android:key="@string/pref_category_privacy_key"
android:title="@string/pref_category_privacy"
app:iconSpaceReserved="false">
<androidx.preference.SwitchPreferenceCompat

Loading…
Cancel
Save