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)
+
+
+
+
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">