diff --git a/app/build.gradle b/app/build.gradle index 19fc0508..f38ea2c6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -202,6 +202,7 @@ dependencies { // Android Utility compile 'com.deploygate:sdk:3.1.1' compile 'com.jakewharton.timber:timber:4.5.1' + compile 'com.jakewharton.threetenabp:threetenabp:1.0.5' debugCompile 'com.facebook.stetho:stetho:1.4.2' debugCompile 'com.facebook.stetho:stetho-okhttp3:1.4.2' debugCompile 'com.facebook.stetho:stetho-timber:1.4.2@aar' diff --git a/app/src/main/java/io/github/droidkaigi/confsched2017/MainApplication.java b/app/src/main/java/io/github/droidkaigi/confsched2017/MainApplication.java index 0876932a..dab93209 100644 --- a/app/src/main/java/io/github/droidkaigi/confsched2017/MainApplication.java +++ b/app/src/main/java/io/github/droidkaigi/confsched2017/MainApplication.java @@ -1,6 +1,7 @@ package io.github.droidkaigi.confsched2017; import com.deploygate.sdk.DeployGate; +import com.jakewharton.threetenabp.AndroidThreeTen; import com.squareup.leakcanary.LeakCanary; import com.tomoima.debot.DebotConfigurator; import com.tomoima.debot.DebotStrategyBuilder; @@ -56,6 +57,7 @@ public AppComponent getComponent() { @Override public void onCreate() { super.onCreate(); + AndroidThreeTen.init(this); appComponent = DaggerAppComponent.builder() .appModule(new AppModule(this)) diff --git a/app/src/main/java/io/github/droidkaigi/confsched2017/di/AppModule.java b/app/src/main/java/io/github/droidkaigi/confsched2017/di/AppModule.java index 8b3b8c49..fa9f9b22 100644 --- a/app/src/main/java/io/github/droidkaigi/confsched2017/di/AppModule.java +++ b/app/src/main/java/io/github/droidkaigi/confsched2017/di/AppModule.java @@ -5,6 +5,8 @@ import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; +import org.threeten.bp.ZonedDateTime; + import android.app.Application; import android.content.Context; import android.content.SharedPreferences; @@ -19,6 +21,8 @@ import io.github.droidkaigi.confsched2017.api.service.GoogleFormService; import io.github.droidkaigi.confsched2017.model.OrmaDatabase; import io.github.droidkaigi.confsched2017.pref.DefaultPrefs; +import io.github.droidkaigi.confsched2017.util.ZonedDateTimeDeserializer; +import io.github.droidkaigi.confsched2017.util.ZonedDateTimeSerializer; import io.reactivex.disposables.CompositeDisposable; import okhttp3.Interceptor; import okhttp3.OkHttpClient; @@ -105,6 +109,10 @@ public GoogleFormService provideGoogleFormService(OkHttpClient client) { } private static Gson createGson() { - return new GsonBuilder().setDateFormat("yyyy/MM/dd HH:mm:ss").create(); + return new GsonBuilder() + .setDateFormat("yyyy/MM/dd HH:mm:ss") + .registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeSerializer()) + .registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeDeserializer()) + .create(); } } diff --git a/app/src/main/java/io/github/droidkaigi/confsched2017/model/Session.java b/app/src/main/java/io/github/droidkaigi/confsched2017/model/Session.java index 1a6fa4a7..56a2c424 100644 --- a/app/src/main/java/io/github/droidkaigi/confsched2017/model/Session.java +++ b/app/src/main/java/io/github/droidkaigi/confsched2017/model/Session.java @@ -6,9 +6,9 @@ import com.github.gfx.android.orma.annotation.PrimaryKey; import com.github.gfx.android.orma.annotation.Table; -import android.support.annotation.Nullable; +import org.threeten.bp.ZonedDateTime; -import java.util.Date; +import android.support.annotation.Nullable; @Table public class Session { @@ -34,11 +34,11 @@ public class Session { @Column @SerializedName("stime") - public Date stime; + public ZonedDateTime stime; @Column @SerializedName("etime") - public Date etime; + public ZonedDateTime etime; @Column @SerializedName("duration_min") @@ -107,8 +107,8 @@ public boolean isDinner() { return Type.DINNER.matches(type); } - public boolean isLiveAt(Date when) { - return stime.before(when) && etime.after(when); + public boolean isLiveAt(ZonedDateTime when) { + return stime.isBefore(when)&& etime.isAfter(when); } @Override diff --git a/app/src/main/java/io/github/droidkaigi/confsched2017/util/AlarmUtil.java b/app/src/main/java/io/github/droidkaigi/confsched2017/util/AlarmUtil.java index 527e1893..e495087f 100644 --- a/app/src/main/java/io/github/droidkaigi/confsched2017/util/AlarmUtil.java +++ b/app/src/main/java/io/github/droidkaigi/confsched2017/util/AlarmUtil.java @@ -20,7 +20,7 @@ public class AlarmUtil { private static final long REMIND_DURATION_MINUTES_FOR_START = TimeUnit.MINUTES.toMillis(10); public static void registerAlarm(@NonNull Context context, @NonNull Session session) { - long time = session.stime.getTime() - REMIND_DURATION_MINUTES_FOR_START; + long time = session.stime.toInstant().toEpochMilli() - REMIND_DURATION_MINUTES_FOR_START; DefaultPrefs prefs = DefaultPrefs.get(context); if (prefs.getNotificationTestFlag()) { @@ -51,8 +51,8 @@ private static PendingIntent createAlarmIntent(@NonNull Context context, @NonNul room = session.room.name; } String text = context.getString(R.string.notification_message, - DateUtil.getHourMinute(displaySTime), - DateUtil.getHourMinute(displayETime), + DateUtil.getHourMinute(displaySTime, context), + DateUtil.getHourMinute(displayETime, context), room); Intent intent = NotificationReceiver.createIntent(context, session.id, title, text); return PendingIntent.getBroadcast(context, session.id, diff --git a/app/src/main/java/io/github/droidkaigi/confsched2017/util/DateUtil.java b/app/src/main/java/io/github/droidkaigi/confsched2017/util/DateUtil.java index e5c86e6d..526007ea 100644 --- a/app/src/main/java/io/github/droidkaigi/confsched2017/util/DateUtil.java +++ b/app/src/main/java/io/github/droidkaigi/confsched2017/util/DateUtil.java @@ -39,7 +39,7 @@ public static String getMonthDate(Date date, Context context) { } @NonNull - public static String getHourMinute(Date date) { + public static String getHourMinute(Date date, Context context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), FORMAT_KKMM); return new SimpleDateFormat(pattern, Locale.getDefault()).format(date); @@ -49,11 +49,10 @@ public static String getHourMinute(Date date) { } @NonNull - public static String getLongFormatDate(@Nullable Date date) { + public static String getLongFormatDate(@Nullable Date date, Context context) { if (date == null) { return ""; } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), FORMAT_YYYYMMDDKKMM); return new SimpleDateFormat(pattern, Locale.getDefault()).format(date); diff --git a/app/src/main/java/io/github/droidkaigi/confsched2017/util/DroidKaigiTypeAdapters.java b/app/src/main/java/io/github/droidkaigi/confsched2017/util/DroidKaigiTypeAdapters.java new file mode 100644 index 00000000..8d9d9055 --- /dev/null +++ b/app/src/main/java/io/github/droidkaigi/confsched2017/util/DroidKaigiTypeAdapters.java @@ -0,0 +1,33 @@ +package io.github.droidkaigi.confsched2017.util; + +import com.github.gfx.android.orma.annotation.StaticTypeAdapter; +import com.github.gfx.android.orma.annotation.StaticTypeAdapters; + +import org.threeten.bp.LocalDateTime; +import org.threeten.bp.ZoneId; +import org.threeten.bp.ZonedDateTime; +import org.threeten.bp.format.DateTimeFormatter; + +import io.github.droidkaigi.confsched2017.BuildConfig; + +@StaticTypeAdapters({ + @StaticTypeAdapter( + targetType = ZonedDateTime.class, + serializedType = String.class, + serializer = "serializeZonedDateTime", + deserializer = "deserializeZonedDateTime" + ) +}) +public class DroidKaigiTypeAdapters { + + private static final ZoneId CONFERENCE_TIMEZONE = ZoneId.of(BuildConfig.CONFERENCE_TIMEZONE); + public static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"); + + public static String serializeZonedDateTime(ZonedDateTime time) { + return time.format(formatter); + } + + public static ZonedDateTime deserializeZonedDateTime(String serialized) { + return LocalDateTime.parse(serialized, formatter).atZone(CONFERENCE_TIMEZONE); + } +} diff --git a/app/src/main/java/io/github/droidkaigi/confsched2017/util/LocaleUtil.java b/app/src/main/java/io/github/droidkaigi/confsched2017/util/LocaleUtil.java index 7e650b5c..0b1ac03f 100644 --- a/app/src/main/java/io/github/droidkaigi/confsched2017/util/LocaleUtil.java +++ b/app/src/main/java/io/github/droidkaigi/confsched2017/util/LocaleUtil.java @@ -1,5 +1,12 @@ package io.github.droidkaigi.confsched2017.util; +import org.threeten.bp.DateTimeUtils; +import org.threeten.bp.Instant; +import org.threeten.bp.LocalDateTime; +import org.threeten.bp.ZoneId; +import org.threeten.bp.ZoneOffset; +import org.threeten.bp.ZonedDateTime; + import android.content.Context; import android.content.res.Configuration; import android.os.Build; @@ -7,9 +14,6 @@ import android.support.annotation.StringRes; import android.text.TextUtils; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; import java.util.List; @@ -30,6 +34,10 @@ public class LocaleUtil { private static final TimeZone CONFERENCE_TIMEZONE = TimeZone.getTimeZone(BuildConfig.CONFERENCE_TIMEZONE); + private static final ZoneOffset CONFERENCE_ZONE_OFFSET = LocalDateTime.now() + .atZone(ZoneId.of(BuildConfig.CONFERENCE_TIMEZONE)) + .getOffset(); + public static void initLocale(Context context) { setLocale(context, getCurrentLanguageId(context)); } @@ -105,17 +113,16 @@ public static int getLanguage(@NonNull String languageId) { } } - public static Date getDisplayDate(@NonNull Date date, Context context) { - DateFormat formatTokyo = SimpleDateFormat.getDateTimeInstance(); - formatTokyo.setTimeZone(CONFERENCE_TIMEZONE); - DateFormat formatLocal = SimpleDateFormat.getDateTimeInstance(); - formatLocal.setTimeZone(getDisplayTimeZone(context)); - try { - return formatLocal.parse(formatTokyo.format(date)); - } catch (ParseException e) { - Timber.tag(TAG).e(e, "date: %s can not parse.", date.toString()); - return date; + public static Date getDisplayDate(@NonNull ZonedDateTime date, Context context) { + Instant instant; + if (DefaultPrefs.get(context).getShowLocalTimeFlag()) { + instant = date.toInstant(); + } else { + instant = date.withZoneSameInstant(ZoneId.systemDefault()) + .minusSeconds(CONFERENCE_ZONE_OFFSET.compareTo(ZonedDateTime.now().getOffset())) + .toInstant(); } + return DateTimeUtils.toDate(instant); } public static TimeZone getDisplayTimeZone(Context context) { @@ -123,4 +130,6 @@ public static TimeZone getDisplayTimeZone(Context context) { boolean shouldShowLocalTime = DefaultPrefs.get(context).getShowLocalTimeFlag(); return (shouldShowLocalTime && defaultTimeZone != null) ? defaultTimeZone : CONFERENCE_TIMEZONE; } + + } diff --git a/app/src/main/java/io/github/droidkaigi/confsched2017/util/ZonedDateTimeDeserializer.java b/app/src/main/java/io/github/droidkaigi/confsched2017/util/ZonedDateTimeDeserializer.java new file mode 100644 index 00000000..2307fd63 --- /dev/null +++ b/app/src/main/java/io/github/droidkaigi/confsched2017/util/ZonedDateTimeDeserializer.java @@ -0,0 +1,18 @@ +package io.github.droidkaigi.confsched2017.util; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; + +import org.threeten.bp.ZonedDateTime; + +import java.lang.reflect.Type; + +public class ZonedDateTimeDeserializer implements JsonDeserializer { + @Override + public ZonedDateTime deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws + JsonParseException { + return DroidKaigiTypeAdapters.deserializeZonedDateTime(jsonElement.getAsString()); + } +} diff --git a/app/src/main/java/io/github/droidkaigi/confsched2017/util/ZonedDateTimeSerializer.java b/app/src/main/java/io/github/droidkaigi/confsched2017/util/ZonedDateTimeSerializer.java new file mode 100644 index 00000000..9ef55d52 --- /dev/null +++ b/app/src/main/java/io/github/droidkaigi/confsched2017/util/ZonedDateTimeSerializer.java @@ -0,0 +1,17 @@ +package io.github.droidkaigi.confsched2017.util; + +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import org.threeten.bp.ZonedDateTime; + +import java.lang.reflect.Type; + +public class ZonedDateTimeSerializer implements JsonSerializer { + @Override + public JsonElement serialize(ZonedDateTime zonedDateTime, Type type, JsonSerializationContext jsonSerializationContext) { + return new JsonPrimitive(zonedDateTime.format(DroidKaigiTypeAdapters.formatter)); + } +} diff --git a/app/src/main/java/io/github/droidkaigi/confsched2017/view/fragment/SessionsFragment.java b/app/src/main/java/io/github/droidkaigi/confsched2017/view/fragment/SessionsFragment.java index 22e4b353..aa4b1daf 100644 --- a/app/src/main/java/io/github/droidkaigi/confsched2017/view/fragment/SessionsFragment.java +++ b/app/src/main/java/io/github/droidkaigi/confsched2017/view/fragment/SessionsFragment.java @@ -3,6 +3,7 @@ import org.lucasr.twowayview.TwoWayLayoutManager; import org.lucasr.twowayview.widget.DividerItemDecoration; import org.lucasr.twowayview.widget.SpannableGridLayoutManager; +import org.threeten.bp.ZonedDateTime; import android.content.Context; import android.graphics.Point; @@ -26,7 +27,6 @@ import android.widget.LinearLayout; import android.widget.TextView; -import java.util.Date; import java.util.List; import java.util.Locale; @@ -200,7 +200,7 @@ public void onScrolled(RecyclerView recyclerView, int dx, int dy) { } private void renderSessions(List adjustedSessionViewModels) { - List stimes = viewModel.getStimes(); + List stimes = viewModel.getStimes(); List rooms = viewModel.getRooms(); if (binding.recyclerView.getLayoutManager() == null) { diff --git a/app/src/main/java/io/github/droidkaigi/confsched2017/viewmodel/MySessionViewModel.java b/app/src/main/java/io/github/droidkaigi/confsched2017/viewmodel/MySessionViewModel.java index 0bc87608..224d6327 100644 --- a/app/src/main/java/io/github/droidkaigi/confsched2017/viewmodel/MySessionViewModel.java +++ b/app/src/main/java/io/github/droidkaigi/confsched2017/viewmodel/MySessionViewModel.java @@ -65,8 +65,8 @@ private String decideSessionTimeRange(Context context, Session session) { Date displayETime = LocaleUtil.getDisplayDate(session.etime, context); return context.getString(R.string.session_time_range, - DateUtil.getLongFormatDate(displaySTime), - DateUtil.getHourMinute(displayETime), + DateUtil.getLongFormatDate(displaySTime, context), + DateUtil.getHourMinute(displayETime, context), DateUtil.getMinutes(displaySTime, displayETime)); } diff --git a/app/src/main/java/io/github/droidkaigi/confsched2017/viewmodel/SessionDetailViewModel.java b/app/src/main/java/io/github/droidkaigi/confsched2017/viewmodel/SessionDetailViewModel.java index 49dd6127..1f984953 100644 --- a/app/src/main/java/io/github/droidkaigi/confsched2017/viewmodel/SessionDetailViewModel.java +++ b/app/src/main/java/io/github/droidkaigi/confsched2017/viewmodel/SessionDetailViewModel.java @@ -162,9 +162,9 @@ private String decideSessionTimeRange(Context context, Session session) { Date displayETime = LocaleUtil.getDisplayDate(session.etime, context); return context.getString(R.string.session_time_range, - DateUtil.getLongFormatDate(displaySTime), - DateUtil.getHourMinute(displayETime), - DateUtil.getMinutes(displaySTime, displayETime)); + DateUtil.getLongFormatDate(displaySTime, context), + DateUtil.getHourMinute(displayETime, context), + session.durationMin); } public String getSessionTitle() { diff --git a/app/src/main/java/io/github/droidkaigi/confsched2017/viewmodel/SessionViewModel.java b/app/src/main/java/io/github/droidkaigi/confsched2017/viewmodel/SessionViewModel.java index e8defa68..00497a69 100644 --- a/app/src/main/java/io/github/droidkaigi/confsched2017/viewmodel/SessionViewModel.java +++ b/app/src/main/java/io/github/droidkaigi/confsched2017/viewmodel/SessionViewModel.java @@ -1,5 +1,7 @@ package io.github.droidkaigi.confsched2017.viewmodel; +import org.threeten.bp.ZonedDateTime; + import android.content.Context; import android.databinding.BaseObservable; import android.databinding.Bindable; @@ -17,6 +19,7 @@ import io.github.droidkaigi.confsched2017.util.AlarmUtil; import io.github.droidkaigi.confsched2017.util.DateUtil; import io.github.droidkaigi.confsched2017.view.helper.Navigator; +import io.github.droidkaigi.confsched2017.util.LocaleUtil; import timber.log.Timber; public class SessionViewModel extends BaseObservable implements ViewModel { @@ -69,8 +72,10 @@ public class SessionViewModel extends BaseObservable implements ViewModel { MySessionsRepository mySessionsRepository) { this.session = session; this.navigator = navigator; - this.shortStime = DateUtil.getHourMinute(session.stime); - this.formattedDate = DateUtil.getMonthDate(session.stime, context); + + Date displayDate = LocaleUtil.getDisplayDate(session.stime, context); + this.shortStime = DateUtil.getHourMinute(displayDate, context); + this.formattedDate = DateUtil.getMonthDate(displayDate, context); this.title = session.title; if (session.speaker != null) { @@ -98,7 +103,7 @@ public class SessionViewModel extends BaseObservable implements ViewModel { this.normalSessionItemVisibility = View.GONE; } else { this.isClickable = true; - this.backgroundResId = session.isLiveAt(new Date()) ? R.drawable.clickable_purple : R.drawable.clickable_white; + this.backgroundResId = session.isLiveAt(ZonedDateTime.now()) ? R.drawable.clickable_purple : R.drawable.clickable_white; this.topicColorResId = TopicColor.from(session.topic).middleColorResId; this.normalSessionItemVisibility = View.VISIBLE; } @@ -143,7 +148,7 @@ private void decideRowSpan(@NonNull Session session) { } } - Date getStime() { + ZonedDateTime getStime() { return session.stime; } diff --git a/app/src/main/java/io/github/droidkaigi/confsched2017/viewmodel/SessionsViewModel.java b/app/src/main/java/io/github/droidkaigi/confsched2017/viewmodel/SessionsViewModel.java index 864c9fd8..f1f0b789 100644 --- a/app/src/main/java/io/github/droidkaigi/confsched2017/viewmodel/SessionsViewModel.java +++ b/app/src/main/java/io/github/droidkaigi/confsched2017/viewmodel/SessionsViewModel.java @@ -2,6 +2,8 @@ import com.annimon.stream.Stream; +import org.threeten.bp.ZonedDateTime; + import android.content.Context; import android.databinding.BaseObservable; import android.databinding.Bindable; @@ -26,6 +28,7 @@ import io.github.droidkaigi.confsched2017.repository.sessions.SessionsRepository; import io.github.droidkaigi.confsched2017.util.DateUtil; import io.github.droidkaigi.confsched2017.view.helper.Navigator; +import io.github.droidkaigi.confsched2017.util.LocaleUtil; import io.reactivex.Single; public class SessionsViewModel extends BaseObservable implements ViewModel { @@ -38,7 +41,7 @@ public class SessionsViewModel extends BaseObservable implements ViewModel { private List rooms; - private List stimes; + private List stimes; @Inject SessionsViewModel(Navigator navigator, SessionsRepository sessionsRepository, MySessionsRepository mySessionsRepository) { @@ -86,23 +89,25 @@ private List adjustViewModels(List sessionVi // In the case of Welcome talk and lunch time, set dummy room roomName = rooms.get(0).name; } - sessionMap.put(generateStimeRoomKey(viewModel.getStime(), roomName), viewModel); + sessionMap.put(generateStimeRoomKey(LocaleUtil.getDisplayDate(viewModel.getStime(), context), roomName, context), viewModel); } final List adjustedViewModels = new ArrayList<>(); // Format date that user can see. Ex) 9, March String lastFormattedDate = null; - for (Date stime : stimes) { + Date displayDate; + for (ZonedDateTime stime : stimes) { + displayDate = LocaleUtil.getDisplayDate(stime, context); if (lastFormattedDate == null) { - lastFormattedDate = DateUtil.getMonthDate(stime, context); + lastFormattedDate = DateUtil.getMonthDate(displayDate, context); } final List sameTimeViewModels = new ArrayList<>(); int maxRowSpan = 1; for (int i = 0, size = rooms.size(); i < size; i++) { Room room = rooms.get(i); - SessionViewModel viewModel = sessionMap.get(generateStimeRoomKey(stime, room.name)); + SessionViewModel viewModel = sessionMap.get(generateStimeRoomKey(displayDate, room.name, context)); if (viewModel != null) { if (!lastFormattedDate.equals(viewModel.getFormattedDate())) { // Change the date @@ -141,11 +146,11 @@ private List adjustViewModels(List sessionVi return adjustedViewModels; } - private String generateStimeRoomKey(@NonNull Date stime, @NonNull String roomName) { - return DateUtil.getLongFormatDate(stime) + "_" + roomName; + private String generateStimeRoomKey(@NonNull Date stime, @NonNull String roomName, Context context) { + return DateUtil.getLongFormatDate(stime, context) + "_" + roomName; } - private List extractStimes(List sessions) { + private List extractStimes(List sessions) { return Stream.of(sessions) .map(session -> session.stime) .sorted() @@ -166,7 +171,7 @@ public List getRooms() { return rooms; } - public List getStimes() { + public List getStimes() { return stimes; }