mirror of https://github.com/ThmmyNoLife/mTHMMY
Ezerous
5 years ago
22 changed files with 631 additions and 80 deletions
@ -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); |
|||
} |
|||
} |
@ -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); |
|||
|
|||
} |
|||
} |
|||
} |
@ -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()); |
|||
} |
|||
} |
@ -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> |
@ -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> |
Loading…
Reference in new issue