simplify :app-wear-shared:shared
This commit is contained in:
parent
b7bc33c1d8
commit
81eb4f3a1c
52 changed files with 1145 additions and 834 deletions
|
@ -4,10 +4,18 @@ import android.content.Context
|
|||
import androidx.preference.PreferenceManager
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import info.nightscout.rx.AapsSchedulers
|
||||
import info.nightscout.rx.bus.RxBus
|
||||
import info.nightscout.rx.interfaces.L
|
||||
import info.nightscout.rx.logging.AAPSLogger
|
||||
import info.nightscout.shared.impl.logging.AAPSLoggerProduction
|
||||
import info.nightscout.shared.impl.logging.LImpl
|
||||
import info.nightscout.shared.impl.rx.AapsSchedulersImpl
|
||||
import info.nightscout.shared.impl.rx.bus.RxBusImpl
|
||||
import info.nightscout.shared.impl.sharedPreferences.SPImplementation
|
||||
import info.nightscout.shared.sharedPreferences.SP
|
||||
import info.nightscout.shared.utils.DateUtil
|
||||
import info.nightscout.shared.utils.DateUtilImpl
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module(
|
||||
|
@ -23,4 +31,20 @@ open class SharedImplModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideL(sp: SP): L = LImpl(sp)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideDateUtil(context: Context): DateUtil = DateUtilImpl(context)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAAPSLogger(l: L): AAPSLogger = AAPSLoggerProduction(l)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideRxBus(aapsSchedulers: AapsSchedulers, aapsLogger: AAPSLogger): RxBus = RxBusImpl(aapsSchedulers, aapsLogger)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
internal fun provideSchedulers(): AapsSchedulers = AapsSchedulersImpl()
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
package info.nightscout.rx.logging
|
||||
package info.nightscout.shared.impl.logging
|
||||
|
||||
import info.nightscout.rx.interfaces.L
|
||||
import info.nightscout.rx.logging.AAPSLogger
|
||||
import info.nightscout.rx.logging.LTag
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
/**
|
|
@ -0,0 +1,16 @@
|
|||
package info.nightscout.shared.impl.rx
|
||||
|
||||
import info.nightscout.rx.AapsSchedulers
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Scheduler
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class AapsSchedulersImpl : AapsSchedulers {
|
||||
|
||||
override val main: Scheduler = AndroidSchedulers.mainThread()
|
||||
override val io: Scheduler = Schedulers.io()
|
||||
override val cpu: Scheduler = Schedulers.computation()
|
||||
override val newThread: Scheduler = Schedulers.newThread()
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package info.nightscout.shared.impl.rx.bus
|
||||
|
||||
import info.nightscout.annotations.OpenForTesting
|
||||
import info.nightscout.rx.AapsSchedulers
|
||||
import info.nightscout.rx.bus.RxBus
|
||||
import info.nightscout.rx.events.Event
|
||||
import info.nightscout.rx.logging.AAPSLogger
|
||||
import info.nightscout.rx.logging.LTag
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import io.reactivex.rxjava3.subjects.PublishSubject
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@OpenForTesting
|
||||
@Singleton
|
||||
class RxBusImpl @Inject constructor(
|
||||
val aapsSchedulers: AapsSchedulers,
|
||||
val aapsLogger: AAPSLogger
|
||||
) : RxBus {
|
||||
|
||||
private val publisher = PublishSubject.create<Event>()
|
||||
|
||||
override fun send(event: Event) {
|
||||
aapsLogger.debug(LTag.EVENTS, "Sending $event")
|
||||
publisher.onNext(event)
|
||||
}
|
||||
|
||||
// Listen should return an Observable and not the publisher
|
||||
// Using ofType we filter only events that match that class type
|
||||
override fun <T : Any> toObservable(eventType: Class<T>): Observable<T> =
|
||||
publisher
|
||||
.subscribeOn(aapsSchedulers.io)
|
||||
.ofType(eventType)
|
||||
}
|
|
@ -0,0 +1,470 @@
|
|||
package info.nightscout.shared.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.collection.LongSparseArray
|
||||
import info.nightscout.annotations.OpenForTesting
|
||||
import info.nightscout.shared.R
|
||||
import info.nightscout.shared.SafeParse
|
||||
import info.nightscout.shared.interfaces.ResourceHelper
|
||||
import org.apache.commons.lang3.time.DateUtils.isSameDay
|
||||
import org.joda.time.DateTime
|
||||
import org.joda.time.format.DateTimeFormat
|
||||
import org.joda.time.format.ISODateTimeFormat
|
||||
import java.security.SecureRandom
|
||||
import java.text.DateFormat
|
||||
import java.text.DecimalFormat
|
||||
import java.text.DecimalFormatSymbols
|
||||
import java.text.SimpleDateFormat
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import java.time.ZoneOffset
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import java.util.EnumSet
|
||||
import java.util.GregorianCalendar
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.regex.Pattern
|
||||
import java.util.stream.Collectors
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.floor
|
||||
|
||||
/**
|
||||
* The Class DateUtil. A simple wrapper around SimpleDateFormat to ease the handling of iso date string <-> date obj
|
||||
* with TZ
|
||||
*/
|
||||
@OpenForTesting
|
||||
@Singleton
|
||||
class DateUtilImpl @Inject constructor(private val context: Context) : DateUtil {
|
||||
|
||||
/**
|
||||
* The date format in iso.
|
||||
*/
|
||||
@Suppress("PrivatePropertyName", "SpellCheckingInspection")
|
||||
private val FORMAT_DATE_ISO_OUT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
|
||||
|
||||
/**
|
||||
* Takes in an ISO date string of the following format:
|
||||
* yyyy-mm-ddThh:mm:ss.ms+HoMo
|
||||
*
|
||||
* @param isoDateString the iso date string
|
||||
* @return the date
|
||||
*/
|
||||
override fun fromISODateString(isoDateString: String): Long {
|
||||
val parser = ISODateTimeFormat.dateTimeParser()
|
||||
val dateTime = DateTime.parse(isoDateString, parser)
|
||||
return dateTime.toDate().time
|
||||
}
|
||||
|
||||
/**
|
||||
* Render date
|
||||
*
|
||||
* @param date the date obj
|
||||
* @return the iso-formatted date string
|
||||
*/
|
||||
override fun toISOString(date: Long): String {
|
||||
val f: DateFormat = SimpleDateFormat(FORMAT_DATE_ISO_OUT, Locale.getDefault())
|
||||
f.timeZone = TimeZone.getTimeZone("UTC")
|
||||
return f.format(date)
|
||||
}
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
override fun toISOAsUTC(timestamp: Long): String {
|
||||
val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'0000Z'", Locale.US)
|
||||
format.timeZone = TimeZone.getTimeZone("UTC")
|
||||
return format.format(timestamp)
|
||||
}
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
override fun toISONoZone(timestamp: Long): String {
|
||||
val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US)
|
||||
format.timeZone = TimeZone.getDefault()
|
||||
return format.format(timestamp)
|
||||
}
|
||||
|
||||
override fun secondsOfTheDayToMilliseconds(seconds: Int): Long {
|
||||
val calendar: Calendar = GregorianCalendar()
|
||||
calendar[Calendar.MONTH] = 0 // Set january to be sure we miss DST changing
|
||||
calendar[Calendar.HOUR_OF_DAY] = seconds / 60 / 60
|
||||
calendar[Calendar.MINUTE] = seconds / 60 % 60
|
||||
calendar[Calendar.SECOND] = 0
|
||||
return calendar.timeInMillis
|
||||
}
|
||||
|
||||
override fun toSeconds(hhColonMm: String): Int {
|
||||
val p = Pattern.compile("(\\d+):(\\d+)( a.m.| p.m.| AM| PM|AM|PM|)")
|
||||
val m = p.matcher(hhColonMm)
|
||||
var retVal = 0
|
||||
if (m.find()) {
|
||||
retVal = SafeParse.stringToInt(m.group(1)) * 60 * 60 + SafeParse.stringToInt(m.group(2)) * 60
|
||||
if ((m.group(3) == " a.m." || m.group(3) == " AM" || m.group(3) == "AM") && m.group(1) == "12") retVal -= 12 * 60 * 60
|
||||
if ((m.group(3) == " p.m." || m.group(3) == " PM" || m.group(3) == "PM") && m.group(1) != "12") retVal += 12 * 60 * 60
|
||||
}
|
||||
return retVal
|
||||
}
|
||||
|
||||
override fun dateString(mills: Long): String {
|
||||
val df = DateFormat.getDateInstance(DateFormat.SHORT)
|
||||
return df.format(mills)
|
||||
}
|
||||
|
||||
override fun dateStringRelative(mills: Long, rh: ResourceHelper): String {
|
||||
val df = DateFormat.getDateInstance(DateFormat.SHORT)
|
||||
val day = df.format(mills)
|
||||
val beginOfToday = beginOfDay(now())
|
||||
return if (mills < now()) // Past
|
||||
when {
|
||||
mills > beginOfToday -> rh.gs(R.string.today)
|
||||
mills > beginOfToday - T.days(1).msecs() -> rh.gs(R.string.yesterday)
|
||||
mills > beginOfToday - T.days(7).msecs() -> dayAgo(mills, rh, true)
|
||||
else -> day
|
||||
}
|
||||
else // Future
|
||||
when {
|
||||
mills < beginOfToday + T.days(1).msecs() -> rh.gs(R.string.later_today)
|
||||
mills < beginOfToday + T.days(2).msecs() -> rh.gs(R.string.tomorrow)
|
||||
mills < beginOfToday + T.days(7).msecs() -> dayAgo(mills, rh, true)
|
||||
else -> day
|
||||
}
|
||||
}
|
||||
|
||||
override fun dateStringShort(mills: Long): String {
|
||||
var format = "MM/dd"
|
||||
if (android.text.format.DateFormat.is24HourFormat(context)) {
|
||||
format = "dd/MM"
|
||||
}
|
||||
return DateTime(mills).toString(DateTimeFormat.forPattern(format))
|
||||
}
|
||||
|
||||
override fun timeString(): String = timeString(now())
|
||||
override fun timeString(mills: Long): String {
|
||||
var format = "hh:mma"
|
||||
if (android.text.format.DateFormat.is24HourFormat(context)) {
|
||||
format = "HH:mm"
|
||||
}
|
||||
return DateTime(mills).toString(DateTimeFormat.forPattern(format))
|
||||
}
|
||||
|
||||
override fun secondString(): String = secondString(now())
|
||||
override fun secondString(mills: Long): String =
|
||||
DateTime(mills).toString(DateTimeFormat.forPattern("ss"))
|
||||
|
||||
override fun minuteString(): String = minuteString(now())
|
||||
override fun minuteString(mills: Long): String =
|
||||
DateTime(mills).toString(DateTimeFormat.forPattern("mm"))
|
||||
|
||||
override fun hourString(): String = hourString(now())
|
||||
override fun hourString(mills: Long): String {
|
||||
var format = "hh"
|
||||
if (android.text.format.DateFormat.is24HourFormat(context)) {
|
||||
format = "HH"
|
||||
}
|
||||
return DateTime(mills).toString(DateTimeFormat.forPattern(format))
|
||||
}
|
||||
|
||||
override fun amPm(): String = amPm(now())
|
||||
override fun amPm(mills: Long): String =
|
||||
DateTime(mills).toString(DateTimeFormat.forPattern("a"))
|
||||
|
||||
override fun dayNameString(format: String): String = dayNameString(now(), format)
|
||||
override fun dayNameString(mills: Long, format: String): String =
|
||||
DateTime(mills).toString(DateTimeFormat.forPattern(format))
|
||||
|
||||
override fun dayString(): String = dayString(now())
|
||||
override fun dayString(mills: Long): String =
|
||||
DateTime(mills).toString(DateTimeFormat.forPattern("dd"))
|
||||
|
||||
override fun monthString(format: String): String = monthString(now(), format)
|
||||
override fun monthString(mills: Long, format: String): String =
|
||||
DateTime(mills).toString(DateTimeFormat.forPattern(format))
|
||||
|
||||
override fun weekString(): String = weekString(now())
|
||||
override fun weekString(mills: Long): String =
|
||||
DateTime(mills).toString(DateTimeFormat.forPattern("ww"))
|
||||
|
||||
override fun timeStringWithSeconds(mills: Long): String {
|
||||
var format = "hh:mm:ssa"
|
||||
if (android.text.format.DateFormat.is24HourFormat(context)) {
|
||||
format = "HH:mm:ss"
|
||||
}
|
||||
return DateTime(mills).toString(DateTimeFormat.forPattern(format))
|
||||
}
|
||||
|
||||
override fun dateAndTimeRangeString(start: Long, end: Long): String {
|
||||
return dateAndTimeString(start) + " - " + timeString(end)
|
||||
}
|
||||
|
||||
override fun timeRangeString(start: Long, end: Long): String {
|
||||
return timeString(start) + " - " + timeString(end)
|
||||
}
|
||||
|
||||
override fun dateAndTimeString(mills: Long): String {
|
||||
return if (mills == 0L) "" else dateString(mills) + " " + timeString(mills)
|
||||
}
|
||||
|
||||
override fun dateAndTimeAndSecondsString(mills: Long): String {
|
||||
return if (mills == 0L) "" else dateString(mills) + " " + timeStringWithSeconds(mills)
|
||||
}
|
||||
|
||||
override fun minAgo(rh: ResourceHelper, time: Long?): String {
|
||||
if (time == null) return ""
|
||||
val minutes = ((now() - time) / 1000 / 60).toInt()
|
||||
return rh.gs(R.string.minago, minutes)
|
||||
}
|
||||
|
||||
override fun minAgoShort(time: Long?): String {
|
||||
if (time == null) return ""
|
||||
val minutes = ((time - now()) / 1000 / 60).toInt()
|
||||
return (if (minutes > 0) "+" else "") + minutes
|
||||
}
|
||||
|
||||
override fun minAgoLong(rh: ResourceHelper, time: Long?): String {
|
||||
if (time == null) return ""
|
||||
val minutes = ((now() - time) / 1000 / 60).toInt()
|
||||
return rh.gs(R.string.minago_long, minutes)
|
||||
}
|
||||
|
||||
override fun hourAgo(time: Long, rh: ResourceHelper): String {
|
||||
val hours = (now() - time) / 1000.0 / 60 / 60
|
||||
return rh.gs(R.string.hoursago, hours)
|
||||
}
|
||||
|
||||
override fun dayAgo(time: Long, rh: ResourceHelper, round: Boolean): String {
|
||||
var days = (now() - time) / 1000.0 / 60 / 60 / 24
|
||||
if (round) {
|
||||
return if (now() > time) {
|
||||
days = ceil(days)
|
||||
rh.gs(R.string.days_ago_round, days)
|
||||
} else {
|
||||
days = floor(days)
|
||||
rh.gs(R.string.in_days_round, days)
|
||||
}
|
||||
}
|
||||
return if (now() > time)
|
||||
rh.gs(R.string.days_ago, days)
|
||||
else
|
||||
rh.gs(R.string.in_days, days)
|
||||
}
|
||||
|
||||
override fun beginOfDay(mills: Long): Long {
|
||||
val givenDate = Calendar.getInstance()
|
||||
givenDate.timeInMillis = mills
|
||||
givenDate[Calendar.HOUR_OF_DAY] = 0
|
||||
givenDate[Calendar.MINUTE] = 0
|
||||
givenDate[Calendar.SECOND] = 0
|
||||
givenDate[Calendar.MILLISECOND] = 0
|
||||
return givenDate.timeInMillis
|
||||
}
|
||||
|
||||
override fun timeStringFromSeconds(seconds: Int): String {
|
||||
val cached = timeStrings[seconds.toLong()]
|
||||
if (cached != null) return cached
|
||||
val t = timeString(secondsOfTheDayToMilliseconds(seconds))
|
||||
timeStrings.put(seconds.toLong(), t)
|
||||
return t
|
||||
}
|
||||
|
||||
override fun timeFrameString(timeInMillis: Long, rh: ResourceHelper): String {
|
||||
var remainingTimeMinutes = timeInMillis / (1000 * 60)
|
||||
val remainingTimeHours = remainingTimeMinutes / 60
|
||||
remainingTimeMinutes %= 60
|
||||
return "(" + (if (remainingTimeHours > 0) remainingTimeHours.toString() + rh.gs(R.string.shorthour) + " " else "") + remainingTimeMinutes + "')"
|
||||
}
|
||||
|
||||
override fun sinceString(timestamp: Long, rh: ResourceHelper): String {
|
||||
return timeFrameString(System.currentTimeMillis() - timestamp, rh)
|
||||
}
|
||||
|
||||
override fun untilString(timestamp: Long, rh: ResourceHelper): String {
|
||||
return timeFrameString(timestamp - System.currentTimeMillis(), rh)
|
||||
}
|
||||
|
||||
override fun now(): Long {
|
||||
return System.currentTimeMillis()
|
||||
}
|
||||
|
||||
override fun nowWithoutMilliseconds(): Long {
|
||||
var n = System.currentTimeMillis()
|
||||
n -= n % 1000
|
||||
return n
|
||||
}
|
||||
|
||||
override fun isOlderThan(date: Long, minutes: Long): Boolean {
|
||||
val diff = now() - date
|
||||
return diff > T.mins(minutes).msecs()
|
||||
}
|
||||
|
||||
override fun getTimeZoneOffsetMs(): Long {
|
||||
return GregorianCalendar().timeZone.rawOffset.toLong()
|
||||
}
|
||||
|
||||
override fun getTimeZoneOffsetMinutes(timestamp: Long): Int {
|
||||
return TimeZone.getDefault().getOffset(timestamp) / 60000
|
||||
}
|
||||
|
||||
override fun isSameDay(timestamp1: Long, timestamp2: Long) = isSameDay(Date(timestamp1), Date(timestamp2))
|
||||
|
||||
override fun isSameDayGroup(timestamp1: Long, timestamp2: Long): Boolean {
|
||||
val now = now()
|
||||
if (now in (timestamp1 + 1) until timestamp2 || now in (timestamp2 + 1) until timestamp1)
|
||||
return false
|
||||
return isSameDay(Date(timestamp1), Date(timestamp2))
|
||||
}
|
||||
|
||||
//Map:{DAYS=1, HOURS=3, MINUTES=46, SECONDS=40, MILLISECONDS=0, MICROSECONDS=0, NANOSECONDS=0}
|
||||
override fun computeDiff(date1: Long, date2: Long): Map<TimeUnit, Long> {
|
||||
val units: MutableList<TimeUnit> = ArrayList(EnumSet.allOf(TimeUnit::class.java))
|
||||
units.reverse()
|
||||
val result: MutableMap<TimeUnit, Long> = LinkedHashMap()
|
||||
var millisecondsRest = date2 - date1
|
||||
for (unit in units) {
|
||||
val diff = unit.convert(millisecondsRest, TimeUnit.MILLISECONDS)
|
||||
val diffInMillisecondsForUnit = unit.toMillis(diff)
|
||||
millisecondsRest -= diffInMillisecondsForUnit
|
||||
result[unit] = diff
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
override fun age(milliseconds: Long, useShortText: Boolean, rh: ResourceHelper): String {
|
||||
val diff = computeDiff(0L, milliseconds)
|
||||
var days = " " + rh.gs(R.string.days) + " "
|
||||
var hours = " " + rh.gs(R.string.hours) + " "
|
||||
var minutes = " " + rh.gs(R.string.unit_minutes) + " "
|
||||
if (useShortText) {
|
||||
days = rh.gs(R.string.shortday)
|
||||
hours = rh.gs(R.string.shorthour)
|
||||
minutes = rh.gs(R.string.shortminute)
|
||||
}
|
||||
var result = ""
|
||||
if (diff.getOrDefault(TimeUnit.DAYS, -1) > 0) result += diff[TimeUnit.DAYS].toString() + days
|
||||
if (diff.getOrDefault(TimeUnit.HOURS, -1) > 0) result += diff[TimeUnit.HOURS].toString() + hours
|
||||
if (diff[TimeUnit.DAYS] == 0L) result += diff[TimeUnit.MINUTES].toString() + minutes
|
||||
return result
|
||||
}
|
||||
|
||||
override fun niceTimeScalar(time: Long, rh: ResourceHelper): String {
|
||||
var t = time
|
||||
var unit = rh.gs(R.string.unit_second)
|
||||
t /= 1000
|
||||
if (t != 1L) unit = rh.gs(R.string.unit_seconds)
|
||||
if (t > 59) {
|
||||
unit = rh.gs(R.string.unit_minute)
|
||||
t /= 60
|
||||
if (t != 1L) unit = rh.gs(R.string.unit_minutes)
|
||||
if (t > 59) {
|
||||
unit = rh.gs(R.string.unit_hour)
|
||||
t /= 60
|
||||
if (t != 1L) unit = rh.gs(R.string.unit_hours)
|
||||
if (t > 24) {
|
||||
unit = rh.gs(R.string.unit_day)
|
||||
t /= 24
|
||||
if (t != 1L) unit = rh.gs(R.string.unit_days)
|
||||
if (t > 28) {
|
||||
unit = rh.gs(R.string.unit_week)
|
||||
t /= 7
|
||||
@Suppress("KotlinConstantConditions")
|
||||
if (t != 1L) unit = rh.gs(R.string.unit_weeks)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//if (t != 1) unit = unit + "s"; //implemented plurality in every step, because in other languages plurality of time is not every time adding the same character
|
||||
return qs(t.toDouble(), 0) + " " + unit
|
||||
}
|
||||
|
||||
override fun qs(x: Double, numDigits: Int): String {
|
||||
var digits = numDigits
|
||||
if (digits == -1) {
|
||||
digits = 0
|
||||
if ((x.toInt() % x == 0.0)) {
|
||||
digits++
|
||||
if ((x.toInt() * 10 / 10).toDouble() != x) {
|
||||
digits++
|
||||
if ((x.toInt() * 100 / 100).toDouble() != x) digits++
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dfs == null) {
|
||||
val localDfs = DecimalFormatSymbols()
|
||||
localDfs.decimalSeparator = '.'
|
||||
dfs = localDfs // avoid race condition
|
||||
}
|
||||
val thisDf: DecimalFormat?
|
||||
// use singleton if on ui thread otherwise allocate new as DecimalFormat is not thread safe
|
||||
if (Thread.currentThread().id == 1L) {
|
||||
if (df == null) {
|
||||
val localDf = DecimalFormat("#", dfs)
|
||||
localDf.minimumIntegerDigits = 1
|
||||
df = localDf // avoid race condition
|
||||
}
|
||||
thisDf = df
|
||||
} else {
|
||||
thisDf = DecimalFormat("#", dfs)
|
||||
}
|
||||
thisDf?.maximumFractionDigits = digits
|
||||
return thisDf?.format(x) ?: ""
|
||||
}
|
||||
|
||||
override fun formatHHMM(timeAsSeconds: Int): String {
|
||||
val hour = timeAsSeconds / 60 / 60
|
||||
val minutes = (timeAsSeconds - hour * 60 * 60) / 60
|
||||
val df = DecimalFormat("00")
|
||||
return df.format(hour.toLong()) + ":" + df.format(minutes.toLong())
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
override fun timeZoneByOffset(offsetInMilliseconds: Long): TimeZone =
|
||||
TimeZone.getTimeZone(
|
||||
if (offsetInMilliseconds == 0L) ZoneId.of("UTC")
|
||||
else ZoneId.getAvailableZoneIds()
|
||||
.stream()
|
||||
.map(ZoneId::of)
|
||||
.filter { z -> z.rules.getOffset(Instant.now()).totalSeconds == ZoneOffset.ofHours((offsetInMilliseconds / 1000 / 3600).toInt()).totalSeconds }
|
||||
.collect(Collectors.toList())
|
||||
.firstOrNull() ?: ZoneId.of("UTC")
|
||||
)
|
||||
|
||||
override fun timeStampToUtcDateMillis(timestamp: Long): Long {
|
||||
val current = Calendar.getInstance().apply { timeInMillis = timestamp }
|
||||
return Calendar.getInstance().apply {
|
||||
set(Calendar.YEAR, current[Calendar.YEAR])
|
||||
set(Calendar.MONTH, current[Calendar.MONTH])
|
||||
set(Calendar.DAY_OF_MONTH, current[Calendar.DAY_OF_MONTH])
|
||||
}.timeInMillis
|
||||
}
|
||||
|
||||
override fun mergeUtcDateToTimestamp(timestamp: Long, dateUtcMillis: Long): Long {
|
||||
val selected = Calendar.getInstance().apply { timeInMillis = dateUtcMillis }
|
||||
return Calendar.getInstance().apply {
|
||||
timeInMillis = timestamp
|
||||
set(Calendar.YEAR, selected[Calendar.YEAR])
|
||||
set(Calendar.MONTH, selected[Calendar.MONTH])
|
||||
set(Calendar.DAY_OF_MONTH, selected[Calendar.DAY_OF_MONTH])
|
||||
}.timeInMillis
|
||||
}
|
||||
|
||||
override fun mergeHourMinuteToTimestamp(timestamp: Long, hour: Int, minute: Int, randomSecond: Boolean): Long {
|
||||
return Calendar.getInstance().apply {
|
||||
timeInMillis = timestamp
|
||||
set(Calendar.HOUR_OF_DAY, hour)
|
||||
set(Calendar.MINUTE, minute)
|
||||
if (randomSecond) set(Calendar.SECOND, seconds++)
|
||||
}.timeInMillis
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val timeStrings = LongSparseArray<String>()
|
||||
private var seconds: Int = (SecureRandom().nextDouble() * 59.0).toInt()
|
||||
|
||||
// singletons to avoid repeated allocation
|
||||
private var dfs: DecimalFormatSymbols? = null
|
||||
private var df: DecimalFormat? = null
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M0.103,0h24v24h-24z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M0.103,0h24v24h-24z" />
|
||||
</vector>
|
|
@ -0,0 +1,306 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="400dp"
|
||||
android:height="400dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M10.744,0.074L10.885,1.416"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M13.113,22.599L13.254,23.942"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9.504,0.27L9.784,1.591"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M14.214,22.425L14.495,23.745"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M8.29,0.595L8.708,1.88"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M15.291,22.136L15.708,23.42"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M7.118,1.046L7.667,2.279"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M16.332,21.737L16.881,22.97"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M4.945,2.3L5.739,3.392"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M18.26,20.623L19.053,21.716"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M3.969,3.091L4.873,4.094"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19.126,19.922L20.029,20.925"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M3.081,3.979L4.084,4.882"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19.914,19.133L20.917,20.037"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M2.291,4.955L3.383,5.749"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M20.616,18.267L21.708,19.061"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M1.036,7.128L2.27,7.677"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M21.729,16.339L22.962,16.888"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0.586,8.3L1.871,8.718"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M22.128,15.298L23.412,15.715"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0.261,9.514L1.582,9.794"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M22.416,14.221L23.737,14.502"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0.065,10.754L1.408,10.895"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M22.591,13.12L23.934,13.261"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0.065,13.263L1.408,13.122"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M22.591,10.894L23.933,10.753"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0.262,14.503L1.582,14.223"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M22.416,9.793L23.737,9.512"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0.587,15.717L1.871,15.299"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M22.128,8.716L23.412,8.299"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M1.037,16.889L2.271,16.34"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M21.728,7.676L22.962,7.126"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M2.291,19.062L3.384,18.268"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M20.615,5.747L21.707,4.954"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M3.082,20.038L4.085,19.134"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19.913,4.881L20.917,3.978"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M3.97,20.926L4.874,19.923"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19.125,4.093L20.028,3.09"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M4.946,21.716L5.74,20.624"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M18.258,3.392L19.052,2.299"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M7.119,22.971L7.668,21.737"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M16.33,2.278L16.879,1.045"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M8.292,23.421L8.709,22.137"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M15.29,1.879L15.707,0.595"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9.505,23.746L9.786,22.425"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M14.213,1.591L14.494,0.27"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M10.746,23.942L10.887,22.599"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M13.112,1.416L13.253,0.073"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M11.999,0.008L11.999,2.708"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M11.999,21.307L11.999,24.008"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M23.999,12L21.298,12"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M2.699,12L-0.001,12"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M5.999,1.616L7.349,3.954"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M16.648,20.061L17.999,22.4"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M22.387,6.001L20.048,7.351"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M3.941,16.651L1.602,18.001"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M1.606,6.008L3.945,7.358"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M20.052,16.658L22.391,18.008"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M17.992,1.612L16.642,3.95"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M7.342,20.057L5.992,22.396"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="400dp"
|
||||
android:height="400dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M11.999,4.906c-0.104,0 -0.188,0.084 -0.188,0.188V12c0,0.104 0.084,0.188 0.188,0.188c0.104,0 0.188,-0.084 0.188,-0.188V5.094C12.186,4.99 12.102,4.906 11.999,4.906zM11.999,12.094c-0.047,0 -0.086,-0.038 -0.086,-0.086s0.039,-0.086 0.086,-0.086c0.047,0 0.086,0.038 0.086,0.086S12.046,12.094 11.999,12.094z" />
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="400dp"
|
||||
android:height="400dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M11.999,1.406c-0.104,0 -0.188,0.084 -0.188,0.188V12c0,0.104 0.084,0.188 0.188,0.188c0.104,0 0.188,-0.084 0.188,-0.188V1.594C12.186,1.49 12.102,1.406 11.999,1.406zM11.999,12.094c-0.047,0 -0.086,-0.038 -0.086,-0.086s0.039,-0.086 0.086,-0.086c0.047,0 0.086,0.038 0.086,0.086S12.046,12.094 11.999,12.094z" />
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="400dp"
|
||||
android:height="400dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF1313"
|
||||
android:pathData="M12.198,11.462v-0.237c0,-0.077 -0.05,-0.131 -0.114,-0.164V0.508c0,-0.047 -0.038,-0.086 -0.086,-0.086c-0.047,0 -0.086,0.038 -0.086,0.086v10.553c-0.063,0.033 -0.114,0.087 -0.114,0.164v0.238c-0.219,0.082 -0.376,0.29 -0.376,0.537s0.157,0.455 0.376,0.537v0.92c0,0.11 0.089,0.2 0.2,0.2s0.2,-0.089 0.2,-0.2v-0.919c0.221,-0.081 0.381,-0.289 0.381,-0.538S12.419,11.543 12.198,11.462zM11.999,12.094c-0.047,0 -0.086,-0.038 -0.086,-0.086s0.039,-0.086 0.086,-0.086c0.047,0 0.086,0.038 0.086,0.086S12.046,12.094 11.999,12.094z" />
|
||||
</vector>
|
|
@ -0,0 +1,66 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="400dp"
|
||||
android:height="400dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M11.999,0.008L11.999,2.708"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M11.999,21.307L11.999,24.008"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M23.999,12L21.298,12"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M2.699,12L-0.001,12"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M5.999,1.616L7.349,3.954"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M16.648,20.061L17.999,22.4"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M22.387,6.001L20.048,7.351"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M3.941,16.651L1.602,18.001"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M1.606,6.008L3.945,7.358"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M20.052,16.658L22.391,18.008"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M17.992,1.612L16.642,3.95"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M7.342,20.057L5.992,22.396"
|
||||
android:strokeWidth="0.1417"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
</vector>
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
|
@ -14,16 +14,18 @@ apply from: "${project.rootDir}/core/main/jacoco_global.gradle"
|
|||
|
||||
android {
|
||||
namespace 'info.nightscout.sharedtests'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':database:entities')
|
||||
implementation project(':app-wear-shared:shared')
|
||||
implementation project(':app-wear-shared:shared-impl')
|
||||
implementation project(':core:interfaces')
|
||||
implementation project(':core:main')
|
||||
implementation project(':core:utils')
|
||||
implementation project(':implementation')
|
||||
|
||||
|
||||
api "org.mockito:mockito-junit-jupiter:$mockito_version"
|
||||
api "org.mockito.kotlin:mockito-kotlin:5.1.0"
|
||||
api "org.junit.jupiter:junit-jupiter-api:$junit_jupiter_version"
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
package info.nightscout.rx.logging
|
||||
package info.nightscout.sharedtests
|
||||
|
||||
import info.nightscout.rx.logging.AAPSLogger
|
||||
import info.nightscout.rx.logging.LTag
|
||||
|
||||
/**
|
||||
* Created by adrian on 2019-12-27.
|
|
@ -2,8 +2,7 @@ package info.nightscout.sharedtests
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import info.nightscout.rx.AapsSchedulers
|
||||
import info.nightscout.rx.TestAapsSchedulers
|
||||
import info.nightscout.rx.logging.AAPSLoggerTest
|
||||
import info.nightscout.sharedtests.rx.TestAapsSchedulers
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.mockito.ArgumentMatcher
|
||||
|
|
|
@ -19,11 +19,11 @@ import info.nightscout.interfaces.profile.ProfileFunction
|
|||
import info.nightscout.interfaces.profile.ProfileStore
|
||||
import info.nightscout.interfaces.utils.DecimalFormatter
|
||||
import info.nightscout.interfaces.utils.HardLimits
|
||||
import info.nightscout.rx.bus.RxBus
|
||||
import info.nightscout.shared.impl.rx.bus.RxBusImpl
|
||||
import info.nightscout.shared.interfaces.ProfileUtil
|
||||
import info.nightscout.shared.interfaces.ResourceHelper
|
||||
import info.nightscout.shared.sharedPreferences.SP
|
||||
import info.nightscout.shared.utils.DateUtil
|
||||
import info.nightscout.shared.utils.DateUtilImpl
|
||||
import org.json.JSONObject
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.mockito.ArgumentMatchers.anyDouble
|
||||
|
@ -45,11 +45,11 @@ open class TestBaseWithProfile : TestBase() {
|
|||
@Mock lateinit var context: Context
|
||||
@Mock lateinit var sp: SP
|
||||
|
||||
lateinit var dateUtil: DateUtil
|
||||
lateinit var dateUtil: DateUtilImpl
|
||||
lateinit var profileUtil: ProfileUtil
|
||||
lateinit var decimalFormatter: DecimalFormatter
|
||||
lateinit var hardLimits: HardLimits
|
||||
val rxBus = RxBus(aapsSchedulers, aapsLogger)
|
||||
val rxBus = RxBusImpl(aapsSchedulers, aapsLogger)
|
||||
|
||||
val profileInjector = HasAndroidInjector {
|
||||
AndroidInjector {
|
||||
|
@ -82,7 +82,7 @@ open class TestBaseWithProfile : TestBase() {
|
|||
invalidProfileJSON = "{\"dia\":\"1\",\"carbratio\":[{\"time\":\"00:00\",\"value\":\"30\"}],\"carbs_hr\":\"20\",\"delay\":\"20\",\"sens\":[{\"time\":\"00:00\",\"value\":\"3\"}," +
|
||||
"{\"time\":\"2:00\",\"value\":\"3.4\"}],\"timezone\":\"UTC\",\"basal\":[{\"time\":\"00:00\",\"value\":\"1\"}],\"target_low\":[{\"time\":\"00:00\",\"value\":\"4.5\"}]," +
|
||||
"\"target_high\":[{\"time\":\"00:00\",\"value\":\"7\"}],\"startDate\":\"1970-01-01T00:00:00.000Z\",\"units\":\"mmol\"}"
|
||||
dateUtil = Mockito.spy(DateUtil(context))
|
||||
dateUtil = Mockito.spy(DateUtilImpl(context))
|
||||
decimalFormatter = DecimalFormatterImpl(rh)
|
||||
profileUtil = ProfileUtilImpl(sp, decimalFormatter)
|
||||
testPumpPlugin = TestPumpPlugin(profileInjector)
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package info.nightscout.sharedtests.rx
|
||||
|
||||
import info.nightscout.rx.AapsSchedulers
|
||||
import io.reactivex.rxjava3.core.Scheduler
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
|
||||
/**
|
||||
* Created by adrian on 12.04.20.
|
||||
*/
|
||||
|
||||
class TestAapsSchedulers : AapsSchedulers {
|
||||
|
||||
override val main: Scheduler = Schedulers.trampoline()
|
||||
override val io: Scheduler = Schedulers.trampoline()
|
||||
override val cpu: Scheduler = Schedulers.trampoline()
|
||||
override val newThread: Scheduler = Schedulers.trampoline()
|
||||
}
|
|
@ -1,30 +1,15 @@
|
|||
package info.nightscout.rx
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Scheduler
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
|
||||
/**
|
||||
* Created by adrian on 12.04.20.
|
||||
*/
|
||||
|
||||
interface AapsSchedulers {
|
||||
|
||||
val main: Scheduler
|
||||
val io: Scheduler
|
||||
val cpu: Scheduler
|
||||
val newThread: Scheduler
|
||||
}
|
||||
|
||||
class DefaultAapsSchedulers : AapsSchedulers {
|
||||
override val main: Scheduler = AndroidSchedulers.mainThread()
|
||||
override val io: Scheduler = Schedulers.io()
|
||||
override val cpu: Scheduler = Schedulers.computation()
|
||||
override val newThread: Scheduler = Schedulers.newThread()
|
||||
}
|
||||
|
||||
class TestAapsSchedulers : AapsSchedulers {
|
||||
override val main: Scheduler = Schedulers.trampoline()
|
||||
override val io: Scheduler = Schedulers.trampoline()
|
||||
override val cpu: Scheduler = Schedulers.trampoline()
|
||||
override val newThread: Scheduler = Schedulers.trampoline()
|
||||
}
|
|
@ -1,33 +1,13 @@
|
|||
package info.nightscout.rx.bus
|
||||
|
||||
import info.nightscout.annotations.OpenForTesting
|
||||
import info.nightscout.rx.AapsSchedulers
|
||||
import info.nightscout.rx.events.Event
|
||||
import info.nightscout.rx.logging.AAPSLogger
|
||||
import info.nightscout.rx.logging.LTag
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import io.reactivex.rxjava3.subjects.PublishSubject
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@OpenForTesting
|
||||
@Singleton
|
||||
class RxBus @Inject constructor(
|
||||
val aapsSchedulers: AapsSchedulers,
|
||||
val aapsLogger: AAPSLogger
|
||||
) {
|
||||
interface RxBus {
|
||||
|
||||
private val publisher = PublishSubject.create<Event>()
|
||||
|
||||
fun send(event: Event) {
|
||||
aapsLogger.debug(LTag.EVENTS, "Sending $event")
|
||||
publisher.onNext(event)
|
||||
}
|
||||
fun send(event: Event)
|
||||
|
||||
// Listen should return an Observable and not the publisher
|
||||
// Using ofType we filter only events that match that class type
|
||||
fun <T : Any> toObservable(eventType: Class<T>): Observable<T> =
|
||||
publisher
|
||||
.subscribeOn(aapsSchedulers.io)
|
||||
.ofType(eventType)
|
||||
fun <T : Any> toObservable(eventType: Class<T>): Observable<T>
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
package info.nightscout.rx.di
|
||||
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import info.nightscout.rx.AapsSchedulers
|
||||
import info.nightscout.rx.DefaultAapsSchedulers
|
||||
import info.nightscout.rx.interfaces.L
|
||||
import info.nightscout.rx.logging.AAPSLogger
|
||||
import info.nightscout.rx.logging.AAPSLoggerProduction
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module(
|
||||
includes = [
|
||||
]
|
||||
)
|
||||
open class RxModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
internal fun provideSchedulers(): AapsSchedulers = DefaultAapsSchedulers()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAAPSLogger(l: L): AAPSLogger = AAPSLoggerProduction(l)
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
package info.nightscout.shared.di
|
||||
|
||||
import dagger.Module
|
||||
|
||||
@Module(
|
||||
includes = [
|
||||
]
|
||||
)
|
||||
open class SharedModule
|
|
@ -1,52 +1,16 @@
|
|||
package info.nightscout.shared.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.collection.LongSparseArray
|
||||
import info.nightscout.annotations.OpenForTesting
|
||||
import info.nightscout.shared.R
|
||||
import info.nightscout.shared.SafeParse
|
||||
import info.nightscout.shared.interfaces.ResourceHelper
|
||||
import org.apache.commons.lang3.time.DateUtils.isSameDay
|
||||
import org.joda.time.DateTime
|
||||
import org.joda.time.format.DateTimeFormat
|
||||
import org.joda.time.format.ISODateTimeFormat
|
||||
import java.security.SecureRandom
|
||||
import java.text.DateFormat
|
||||
import java.text.DecimalFormat
|
||||
import java.text.DecimalFormatSymbols
|
||||
import java.text.SimpleDateFormat
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import java.time.ZoneOffset
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import java.util.EnumSet
|
||||
import java.util.GregorianCalendar
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.regex.Pattern
|
||||
import java.util.stream.Collectors
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.floor
|
||||
|
||||
/**
|
||||
* The Class DateUtil. A simple wrapper around SimpleDateFormat to ease the handling of iso date string <-> date obj
|
||||
* with TZ
|
||||
*/
|
||||
@OpenForTesting
|
||||
@Singleton
|
||||
class DateUtil @Inject constructor(private val context: Context) {
|
||||
|
||||
/**
|
||||
* The date format in iso.
|
||||
*/
|
||||
@Suppress("PrivatePropertyName", "SpellCheckingInspection")
|
||||
private val FORMAT_DATE_ISO_OUT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
|
||||
interface DateUtil {
|
||||
|
||||
/**
|
||||
* Takes in an ISO date string of the following format:
|
||||
|
@ -55,11 +19,7 @@ class DateUtil @Inject constructor(private val context: Context) {
|
|||
* @param isoDateString the iso date string
|
||||
* @return the date
|
||||
*/
|
||||
fun fromISODateString(isoDateString: String): Long {
|
||||
val parser = ISODateTimeFormat.dateTimeParser()
|
||||
val dateTime = DateTime.parse(isoDateString, parser)
|
||||
return dateTime.toDate().time
|
||||
}
|
||||
fun fromISODateString(isoDateString: String): Long
|
||||
|
||||
/**
|
||||
* Render date
|
||||
|
@ -67,404 +27,66 @@ class DateUtil @Inject constructor(private val context: Context) {
|
|||
* @param date the date obj
|
||||
* @return the iso-formatted date string
|
||||
*/
|
||||
fun toISOString(date: Long): String {
|
||||
val f: DateFormat = SimpleDateFormat(FORMAT_DATE_ISO_OUT, Locale.getDefault())
|
||||
f.timeZone = TimeZone.getTimeZone("UTC")
|
||||
return f.format(date)
|
||||
}
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
fun toISOAsUTC(timestamp: Long): String {
|
||||
val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'0000Z'", Locale.US)
|
||||
format.timeZone = TimeZone.getTimeZone("UTC")
|
||||
return format.format(timestamp)
|
||||
}
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
fun toISONoZone(timestamp: Long): String {
|
||||
val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US)
|
||||
format.timeZone = TimeZone.getDefault()
|
||||
return format.format(timestamp)
|
||||
}
|
||||
|
||||
fun secondsOfTheDayToMilliseconds(seconds: Int): Long {
|
||||
val calendar: Calendar = GregorianCalendar()
|
||||
calendar[Calendar.MONTH] = 0 // Set january to be sure we miss DST changing
|
||||
calendar[Calendar.HOUR_OF_DAY] = seconds / 60 / 60
|
||||
calendar[Calendar.MINUTE] = seconds / 60 % 60
|
||||
calendar[Calendar.SECOND] = 0
|
||||
return calendar.timeInMillis
|
||||
}
|
||||
|
||||
fun toSeconds(hhColonMm: String): Int {
|
||||
val p = Pattern.compile("(\\d+):(\\d+)( a.m.| p.m.| AM| PM|AM|PM|)")
|
||||
val m = p.matcher(hhColonMm)
|
||||
var retVal = 0
|
||||
if (m.find()) {
|
||||
retVal = SafeParse.stringToInt(m.group(1)) * 60 * 60 + SafeParse.stringToInt(m.group(2)) * 60
|
||||
if ((m.group(3) == " a.m." || m.group(3) == " AM" || m.group(3) == "AM") && m.group(1) == "12") retVal -= 12 * 60 * 60
|
||||
if ((m.group(3) == " p.m." || m.group(3) == " PM" || m.group(3) == "PM") && m.group(1) != "12") retVal += 12 * 60 * 60
|
||||
}
|
||||
return retVal
|
||||
}
|
||||
|
||||
fun dateString(mills: Long): String {
|
||||
val df = DateFormat.getDateInstance(DateFormat.SHORT)
|
||||
return df.format(mills)
|
||||
}
|
||||
|
||||
fun dateStringRelative(mills: Long, rh: ResourceHelper): String {
|
||||
val df = DateFormat.getDateInstance(DateFormat.SHORT)
|
||||
val day = df.format(mills)
|
||||
val beginOfToday = beginOfDay(now())
|
||||
return if (mills < now()) // Past
|
||||
when {
|
||||
mills > beginOfToday -> rh.gs(R.string.today)
|
||||
mills > beginOfToday - T.days(1).msecs() -> rh.gs(R.string.yesterday)
|
||||
mills > beginOfToday - T.days(7).msecs() -> dayAgo(mills, rh, true)
|
||||
else -> day
|
||||
}
|
||||
else // Future
|
||||
when {
|
||||
mills < beginOfToday + T.days(1).msecs() -> rh.gs(R.string.later_today)
|
||||
mills < beginOfToday + T.days(2).msecs() -> rh.gs(R.string.tomorrow)
|
||||
mills < beginOfToday + T.days(7).msecs() -> dayAgo(mills, rh, true)
|
||||
else -> day
|
||||
}
|
||||
}
|
||||
|
||||
fun dateStringShort(mills: Long): String {
|
||||
var format = "MM/dd"
|
||||
if (android.text.format.DateFormat.is24HourFormat(context)) {
|
||||
format = "dd/MM"
|
||||
}
|
||||
return DateTime(mills).toString(DateTimeFormat.forPattern(format))
|
||||
}
|
||||
|
||||
fun timeString(): String = timeString(now())
|
||||
fun timeString(mills: Long): String {
|
||||
var format = "hh:mma"
|
||||
if (android.text.format.DateFormat.is24HourFormat(context)) {
|
||||
format = "HH:mm"
|
||||
}
|
||||
return DateTime(mills).toString(DateTimeFormat.forPattern(format))
|
||||
}
|
||||
|
||||
fun secondString(): String = secondString(now())
|
||||
fun secondString(mills: Long): String =
|
||||
DateTime(mills).toString(DateTimeFormat.forPattern("ss"))
|
||||
|
||||
fun minuteString(): String = minuteString(now())
|
||||
fun minuteString(mills: Long): String =
|
||||
DateTime(mills).toString(DateTimeFormat.forPattern("mm"))
|
||||
|
||||
fun hourString(): String = hourString(now())
|
||||
fun hourString(mills: Long): String {
|
||||
var format = "hh"
|
||||
if (android.text.format.DateFormat.is24HourFormat(context)) {
|
||||
format = "HH"
|
||||
}
|
||||
return DateTime(mills).toString(DateTimeFormat.forPattern(format))
|
||||
}
|
||||
|
||||
fun amPm(): String = amPm(now())
|
||||
fun amPm(mills: Long): String =
|
||||
DateTime(mills).toString(DateTimeFormat.forPattern("a"))
|
||||
|
||||
fun dayNameString(format: String = "E"): String = dayNameString(now(), format)
|
||||
fun dayNameString(mills: Long, format: String = "E"): String =
|
||||
DateTime(mills).toString(DateTimeFormat.forPattern(format))
|
||||
|
||||
fun toISOString(date: Long): String
|
||||
fun toISOAsUTC(timestamp: Long): String
|
||||
fun toISONoZone(timestamp: Long): String
|
||||
fun secondsOfTheDayToMilliseconds(seconds: Int): Long
|
||||
fun toSeconds(hhColonMm: String): Int
|
||||
fun dateString(mills: Long): String
|
||||
fun dateStringRelative(mills: Long, rh: ResourceHelper): String
|
||||
fun dateStringShort(mills: Long): String
|
||||
fun timeString(): String
|
||||
fun timeString(mills: Long): String
|
||||
fun secondString(): String
|
||||
fun secondString(mills: Long): String
|
||||
fun minuteString(): String
|
||||
fun minuteString(mills: Long): String
|
||||
fun hourString(): String
|
||||
fun hourString(mills: Long): String
|
||||
fun amPm(): String
|
||||
fun amPm(mills: Long): String
|
||||
fun dayNameString(format: String = "E"): String
|
||||
fun dayNameString(mills: Long, format: String = "E"): String
|
||||
fun dayString(): String = dayString(now())
|
||||
fun dayString(mills: Long): String =
|
||||
DateTime(mills).toString(DateTimeFormat.forPattern("dd"))
|
||||
fun dayString(mills: Long): String
|
||||
fun monthString(format: String = "MMM"): String
|
||||
fun monthString(mills: Long, format: String = "MMM"): String
|
||||
fun weekString(): String
|
||||
fun weekString(mills: Long): String
|
||||
fun timeStringWithSeconds(mills: Long): String
|
||||
fun dateAndTimeRangeString(start: Long, end: Long): String
|
||||
fun timeRangeString(start: Long, end: Long): String
|
||||
fun dateAndTimeString(mills: Long): String
|
||||
fun dateAndTimeAndSecondsString(mills: Long): String
|
||||
fun minAgo(rh: ResourceHelper, time: Long?): String
|
||||
fun minAgoShort(time: Long?): String
|
||||
fun minAgoLong(rh: ResourceHelper, time: Long?): String
|
||||
fun hourAgo(time: Long, rh: ResourceHelper): String
|
||||
fun dayAgo(time: Long, rh: ResourceHelper, round: Boolean = false): String
|
||||
fun beginOfDay(mills: Long): Long
|
||||
fun timeStringFromSeconds(seconds: Int): String
|
||||
fun timeFrameString(timeInMillis: Long, rh: ResourceHelper): String
|
||||
fun sinceString(timestamp: Long, rh: ResourceHelper): String
|
||||
fun untilString(timestamp: Long, rh: ResourceHelper): String
|
||||
fun now(): Long
|
||||
fun nowWithoutMilliseconds(): Long
|
||||
fun isOlderThan(date: Long, minutes: Long): Boolean
|
||||
fun getTimeZoneOffsetMs(): Long
|
||||
fun getTimeZoneOffsetMinutes(timestamp: Long): Int
|
||||
fun isSameDay(timestamp1: Long, timestamp2: Long): Boolean
|
||||
|
||||
fun monthString(format: String = "MMM"): String = monthString(now(), format)
|
||||
fun monthString(mills: Long, format: String = "MMM"): String =
|
||||
DateTime(mills).toString(DateTimeFormat.forPattern(format))
|
||||
|
||||
fun weekString(): String = weekString(now())
|
||||
fun weekString(mills: Long): String =
|
||||
DateTime(mills).toString(DateTimeFormat.forPattern("ww"))
|
||||
|
||||
fun timeStringWithSeconds(mills: Long): String {
|
||||
var format = "hh:mm:ssa"
|
||||
if (android.text.format.DateFormat.is24HourFormat(context)) {
|
||||
format = "HH:mm:ss"
|
||||
}
|
||||
return DateTime(mills).toString(DateTimeFormat.forPattern(format))
|
||||
}
|
||||
|
||||
fun dateAndTimeRangeString(start: Long, end: Long): String {
|
||||
return dateAndTimeString(start) + " - " + timeString(end)
|
||||
}
|
||||
|
||||
fun timeRangeString(start: Long, end: Long): String {
|
||||
return timeString(start) + " - " + timeString(end)
|
||||
}
|
||||
|
||||
fun dateAndTimeString(mills: Long): String {
|
||||
return if (mills == 0L) "" else dateString(mills) + " " + timeString(mills)
|
||||
}
|
||||
|
||||
fun dateAndTimeAndSecondsString(mills: Long): String {
|
||||
return if (mills == 0L) "" else dateString(mills) + " " + timeStringWithSeconds(mills)
|
||||
}
|
||||
|
||||
fun minAgo(rh: ResourceHelper, time: Long?): String {
|
||||
if (time == null) return ""
|
||||
val minutes = ((now() - time) / 1000 / 60).toInt()
|
||||
return rh.gs(R.string.minago, minutes)
|
||||
}
|
||||
|
||||
fun minAgoShort(time: Long?): String {
|
||||
if (time == null) return ""
|
||||
val minutes = ((time - now()) / 1000 / 60).toInt()
|
||||
return (if (minutes > 0) "+" else "") + minutes
|
||||
}
|
||||
|
||||
fun minAgoLong(rh: ResourceHelper, time: Long?): String {
|
||||
if (time == null) return ""
|
||||
val minutes = ((now() - time) / 1000 / 60).toInt()
|
||||
return rh.gs(R.string.minago_long, minutes)
|
||||
}
|
||||
|
||||
fun hourAgo(time: Long, rh: ResourceHelper): String {
|
||||
val hours = (now() - time) / 1000.0 / 60 / 60
|
||||
return rh.gs(R.string.hoursago, hours)
|
||||
}
|
||||
|
||||
fun dayAgo(time: Long, rh: ResourceHelper, round: Boolean = false): String {
|
||||
var days = (now() - time) / 1000.0 / 60 / 60 / 24
|
||||
if (round) {
|
||||
return if (now() > time) {
|
||||
days = ceil(days)
|
||||
rh.gs(R.string.days_ago_round, days)
|
||||
} else {
|
||||
days = floor(days)
|
||||
rh.gs(R.string.in_days_round, days)
|
||||
}
|
||||
}
|
||||
return if (now() > time)
|
||||
rh.gs(R.string.days_ago, days)
|
||||
else
|
||||
rh.gs(R.string.in_days, days)
|
||||
}
|
||||
|
||||
fun beginOfDay(mills: Long): Long {
|
||||
val givenDate = Calendar.getInstance()
|
||||
givenDate.timeInMillis = mills
|
||||
givenDate[Calendar.HOUR_OF_DAY] = 0
|
||||
givenDate[Calendar.MINUTE] = 0
|
||||
givenDate[Calendar.SECOND] = 0
|
||||
givenDate[Calendar.MILLISECOND] = 0
|
||||
return givenDate.timeInMillis
|
||||
}
|
||||
|
||||
fun timeStringFromSeconds(seconds: Int): String {
|
||||
val cached = timeStrings[seconds.toLong()]
|
||||
if (cached != null) return cached
|
||||
val t = timeString(secondsOfTheDayToMilliseconds(seconds))
|
||||
timeStrings.put(seconds.toLong(), t)
|
||||
return t
|
||||
}
|
||||
|
||||
fun timeFrameString(timeInMillis: Long, rh: ResourceHelper): String {
|
||||
var remainingTimeMinutes = timeInMillis / (1000 * 60)
|
||||
val remainingTimeHours = remainingTimeMinutes / 60
|
||||
remainingTimeMinutes %= 60
|
||||
return "(" + (if (remainingTimeHours > 0) remainingTimeHours.toString() + rh.gs(R.string.shorthour) + " " else "") + remainingTimeMinutes + "')"
|
||||
}
|
||||
|
||||
fun sinceString(timestamp: Long, rh: ResourceHelper): String {
|
||||
return timeFrameString(System.currentTimeMillis() - timestamp, rh)
|
||||
}
|
||||
|
||||
fun untilString(timestamp: Long, rh: ResourceHelper): String {
|
||||
return timeFrameString(timestamp - System.currentTimeMillis(), rh)
|
||||
}
|
||||
|
||||
fun now(): Long {
|
||||
return System.currentTimeMillis()
|
||||
}
|
||||
|
||||
fun nowWithoutMilliseconds(): Long {
|
||||
var n = System.currentTimeMillis()
|
||||
n -= n % 1000
|
||||
return n
|
||||
}
|
||||
|
||||
fun isOlderThan(date: Long, minutes: Long): Boolean {
|
||||
val diff = now() - date
|
||||
return diff > T.mins(minutes).msecs()
|
||||
}
|
||||
|
||||
fun getTimeZoneOffsetMs(): Long {
|
||||
return GregorianCalendar().timeZone.rawOffset.toLong()
|
||||
}
|
||||
|
||||
fun getTimeZoneOffsetMinutes(timestamp: Long): Int {
|
||||
return TimeZone.getDefault().getOffset(timestamp) / 60000
|
||||
}
|
||||
|
||||
fun isSameDay(timestamp1: Long, timestamp2: Long) = isSameDay(Date(timestamp1), Date(timestamp2))
|
||||
|
||||
fun isSameDayGroup(timestamp1: Long, timestamp2: Long): Boolean {
|
||||
val now = now()
|
||||
if (now in (timestamp1 + 1) until timestamp2 || now in (timestamp2 + 1) until timestamp1)
|
||||
return false
|
||||
return isSameDay(Date(timestamp1), Date(timestamp2))
|
||||
}
|
||||
fun isSameDayGroup(timestamp1: Long, timestamp2: Long): Boolean
|
||||
|
||||
//Map:{DAYS=1, HOURS=3, MINUTES=46, SECONDS=40, MILLISECONDS=0, MICROSECONDS=0, NANOSECONDS=0}
|
||||
fun computeDiff(date1: Long, date2: Long): Map<TimeUnit, Long> {
|
||||
val units: MutableList<TimeUnit> = ArrayList(EnumSet.allOf(TimeUnit::class.java))
|
||||
units.reverse()
|
||||
val result: MutableMap<TimeUnit, Long> = LinkedHashMap()
|
||||
var millisecondsRest = date2 - date1
|
||||
for (unit in units) {
|
||||
val diff = unit.convert(millisecondsRest, TimeUnit.MILLISECONDS)
|
||||
val diffInMillisecondsForUnit = unit.toMillis(diff)
|
||||
millisecondsRest -= diffInMillisecondsForUnit
|
||||
result[unit] = diff
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun age(milliseconds: Long, useShortText: Boolean, rh: ResourceHelper): String {
|
||||
val diff = computeDiff(0L, milliseconds)
|
||||
var days = " " + rh.gs(R.string.days) + " "
|
||||
var hours = " " + rh.gs(R.string.hours) + " "
|
||||
var minutes = " " + rh.gs(R.string.unit_minutes) + " "
|
||||
if (useShortText) {
|
||||
days = rh.gs(R.string.shortday)
|
||||
hours = rh.gs(R.string.shorthour)
|
||||
minutes = rh.gs(R.string.shortminute)
|
||||
}
|
||||
var result = ""
|
||||
if (diff.getOrDefault(TimeUnit.DAYS, -1) > 0) result += diff[TimeUnit.DAYS].toString() + days
|
||||
if (diff.getOrDefault(TimeUnit.HOURS, -1) > 0) result += diff[TimeUnit.HOURS].toString() + hours
|
||||
if (diff[TimeUnit.DAYS] == 0L) result += diff[TimeUnit.MINUTES].toString() + minutes
|
||||
return result
|
||||
}
|
||||
|
||||
fun niceTimeScalar(time: Long, rh: ResourceHelper): String {
|
||||
var t = time
|
||||
var unit = rh.gs(R.string.unit_second)
|
||||
t /= 1000
|
||||
if (t != 1L) unit = rh.gs(R.string.unit_seconds)
|
||||
if (t > 59) {
|
||||
unit = rh.gs(R.string.unit_minute)
|
||||
t /= 60
|
||||
if (t != 1L) unit = rh.gs(R.string.unit_minutes)
|
||||
if (t > 59) {
|
||||
unit = rh.gs(R.string.unit_hour)
|
||||
t /= 60
|
||||
if (t != 1L) unit = rh.gs(R.string.unit_hours)
|
||||
if (t > 24) {
|
||||
unit = rh.gs(R.string.unit_day)
|
||||
t /= 24
|
||||
if (t != 1L) unit = rh.gs(R.string.unit_days)
|
||||
if (t > 28) {
|
||||
unit = rh.gs(R.string.unit_week)
|
||||
t /= 7
|
||||
@Suppress("KotlinConstantConditions")
|
||||
if (t != 1L) unit = rh.gs(R.string.unit_weeks)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//if (t != 1) unit = unit + "s"; //implemented plurality in every step, because in other languages plurality of time is not every time adding the same character
|
||||
return qs(t.toDouble(), 0) + " " + unit
|
||||
}
|
||||
|
||||
fun qs(x: Double, numDigits: Int): String {
|
||||
var digits = numDigits
|
||||
if (digits == -1) {
|
||||
digits = 0
|
||||
if ((x.toInt() % x == 0.0)) {
|
||||
digits++
|
||||
if ((x.toInt() * 10 / 10).toDouble() != x) {
|
||||
digits++
|
||||
if ((x.toInt() * 100 / 100).toDouble() != x) digits++
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dfs == null) {
|
||||
val localDfs = DecimalFormatSymbols()
|
||||
localDfs.decimalSeparator = '.'
|
||||
dfs = localDfs // avoid race condition
|
||||
}
|
||||
val thisDf: DecimalFormat?
|
||||
// use singleton if on ui thread otherwise allocate new as DecimalFormat is not thread safe
|
||||
if (Thread.currentThread().id == 1L) {
|
||||
if (df == null) {
|
||||
val localDf = DecimalFormat("#", dfs)
|
||||
localDf.minimumIntegerDigits = 1
|
||||
df = localDf // avoid race condition
|
||||
}
|
||||
thisDf = df
|
||||
} else {
|
||||
thisDf = DecimalFormat("#", dfs)
|
||||
}
|
||||
thisDf?.maximumFractionDigits = digits
|
||||
return thisDf?.format(x) ?: ""
|
||||
}
|
||||
|
||||
fun formatHHMM(timeAsSeconds: Int): String {
|
||||
val hour = timeAsSeconds / 60 / 60
|
||||
val minutes = (timeAsSeconds - hour * 60 * 60) / 60
|
||||
val df = DecimalFormat("00")
|
||||
return df.format(hour.toLong()) + ":" + df.format(minutes.toLong())
|
||||
}
|
||||
fun computeDiff(date1: Long, date2: Long): Map<TimeUnit, Long>
|
||||
fun age(milliseconds: Long, useShortText: Boolean, rh: ResourceHelper): String
|
||||
fun niceTimeScalar(time: Long, rh: ResourceHelper): String
|
||||
fun qs(x: Double, numDigits: Int): String
|
||||
fun formatHHMM(timeAsSeconds: Int): String
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
fun timeZoneByOffset(offsetInMilliseconds: Long): TimeZone =
|
||||
TimeZone.getTimeZone(
|
||||
if (offsetInMilliseconds == 0L) ZoneId.of("UTC")
|
||||
else ZoneId.getAvailableZoneIds()
|
||||
.stream()
|
||||
.map(ZoneId::of)
|
||||
.filter { z -> z.rules.getOffset(Instant.now()).totalSeconds == ZoneOffset.ofHours((offsetInMilliseconds / 1000 / 3600).toInt()).totalSeconds }
|
||||
.collect(Collectors.toList())
|
||||
.firstOrNull() ?: ZoneId.of("UTC")
|
||||
)
|
||||
|
||||
fun timeStampToUtcDateMillis(timestamp: Long): Long {
|
||||
val current = Calendar.getInstance().apply { timeInMillis = timestamp }
|
||||
return Calendar.getInstance().apply {
|
||||
set(Calendar.YEAR, current[Calendar.YEAR])
|
||||
set(Calendar.MONTH, current[Calendar.MONTH])
|
||||
set(Calendar.DAY_OF_MONTH, current[Calendar.DAY_OF_MONTH])
|
||||
}.timeInMillis
|
||||
}
|
||||
|
||||
fun mergeUtcDateToTimestamp(timestamp: Long, dateUtcMillis: Long): Long {
|
||||
val selected = Calendar.getInstance().apply { timeInMillis = dateUtcMillis }
|
||||
return Calendar.getInstance().apply {
|
||||
timeInMillis = timestamp
|
||||
set(Calendar.YEAR, selected[Calendar.YEAR])
|
||||
set(Calendar.MONTH, selected[Calendar.MONTH])
|
||||
set(Calendar.DAY_OF_MONTH, selected[Calendar.DAY_OF_MONTH])
|
||||
}.timeInMillis
|
||||
}
|
||||
|
||||
fun mergeHourMinuteToTimestamp(timestamp: Long, hour: Int, minute: Int, randomSecond: Boolean = false): Long {
|
||||
return Calendar.getInstance().apply {
|
||||
timeInMillis = timestamp
|
||||
set(Calendar.HOUR_OF_DAY, hour)
|
||||
set(Calendar.MINUTE, minute)
|
||||
if (randomSecond) set(Calendar.SECOND, seconds++)
|
||||
}.timeInMillis
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val timeStrings = LongSparseArray<String>()
|
||||
private var seconds: Int = (SecureRandom().nextDouble() * 59.0).toInt()
|
||||
|
||||
// singletons to avoid repeated allocation
|
||||
private var dfs: DecimalFormatSymbols? = null
|
||||
private var df: DecimalFormat? = null
|
||||
}
|
||||
fun timeZoneByOffset(offsetInMilliseconds: Long): TimeZone
|
||||
fun timeStampToUtcDateMillis(timestamp: Long): Long
|
||||
fun mergeUtcDateToTimestamp(timestamp: Long, dateUtcMillis: Long): Long
|
||||
fun mergeHourMinuteToTimestamp(timestamp: Long, hour: Int, minute: Int, randomSecond: Boolean = false): Long
|
||||
}
|
||||
|
|
|
@ -1,183 +0,0 @@
|
|||
<vector android:height="400dp" android:viewportHeight="24"
|
||||
android:viewportWidth="24" android:width="400dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M10.744,0.074L10.885,1.416"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M13.113,22.599L13.254,23.942"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M9.504,0.27L9.784,1.591"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M14.214,22.425L14.495,23.745"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M8.29,0.595L8.708,1.88"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M15.291,22.136L15.708,23.42"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M7.118,1.046L7.667,2.279"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M16.332,21.737L16.881,22.97"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M4.945,2.3L5.739,3.392"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M18.26,20.623L19.053,21.716"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M3.969,3.091L4.873,4.094"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M19.126,19.922L20.029,20.925"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M3.081,3.979L4.084,4.882"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M19.914,19.133L20.917,20.037"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M2.291,4.955L3.383,5.749"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M20.616,18.267L21.708,19.061"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M1.036,7.128L2.27,7.677"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M21.729,16.339L22.962,16.888"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M0.586,8.3L1.871,8.718"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M22.128,15.298L23.412,15.715"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M0.261,9.514L1.582,9.794"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M22.416,14.221L23.737,14.502"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M0.065,10.754L1.408,10.895"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M22.591,13.12L23.934,13.261"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M0.065,13.263L1.408,13.122"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M22.591,10.894L23.933,10.753"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M0.262,14.503L1.582,14.223"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M22.416,9.793L23.737,9.512"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M0.587,15.717L1.871,15.299"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M22.128,8.716L23.412,8.299"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M1.037,16.889L2.271,16.34"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M21.728,7.676L22.962,7.126"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M2.291,19.062L3.384,18.268"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M20.615,5.747L21.707,4.954"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M3.082,20.038L4.085,19.134"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M19.913,4.881L20.917,3.978"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M3.97,20.926L4.874,19.923"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M19.125,4.093L20.028,3.09"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M4.946,21.716L5.74,20.624"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M18.258,3.392L19.052,2.299"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M7.119,22.971L7.668,21.737"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M16.33,2.278L16.879,1.045"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M8.292,23.421L8.709,22.137"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M15.29,1.879L15.707,0.595"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M9.505,23.746L9.786,22.425"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M14.213,1.591L14.494,0.27"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M10.746,23.942L10.887,22.599"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M13.112,1.416L13.253,0.073"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M11.999,0.008L11.999,2.708"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M11.999,21.307L11.999,24.008"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M23.999,12L21.298,12"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M2.699,12L-0.001,12"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M5.999,1.616L7.349,3.954"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M16.648,20.061L17.999,22.4"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M22.387,6.001L20.048,7.351"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M3.941,16.651L1.602,18.001"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M1.606,6.008L3.945,7.358"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M20.052,16.658L22.391,18.008"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M17.992,1.612L16.642,3.95"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M7.342,20.057L5.992,22.396"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
</vector>
|
|
@ -1,4 +0,0 @@
|
|||
<vector android:height="400dp" android:viewportHeight="24"
|
||||
android:viewportWidth="24" android:width="400dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FFFFFF" android:pathData="M11.999,4.906c-0.104,0 -0.188,0.084 -0.188,0.188V12c0,0.104 0.084,0.188 0.188,0.188c0.104,0 0.188,-0.084 0.188,-0.188V5.094C12.186,4.99 12.102,4.906 11.999,4.906zM11.999,12.094c-0.047,0 -0.086,-0.038 -0.086,-0.086s0.039,-0.086 0.086,-0.086c0.047,0 0.086,0.038 0.086,0.086S12.046,12.094 11.999,12.094z"/>
|
||||
</vector>
|
|
@ -1,4 +0,0 @@
|
|||
<vector android:height="400dp" android:viewportHeight="24"
|
||||
android:viewportWidth="24" android:width="400dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FFFFFF" android:pathData="M11.999,1.406c-0.104,0 -0.188,0.084 -0.188,0.188V12c0,0.104 0.084,0.188 0.188,0.188c0.104,0 0.188,-0.084 0.188,-0.188V1.594C12.186,1.49 12.102,1.406 11.999,1.406zM11.999,12.094c-0.047,0 -0.086,-0.038 -0.086,-0.086s0.039,-0.086 0.086,-0.086c0.047,0 0.086,0.038 0.086,0.086S12.046,12.094 11.999,12.094z"/>
|
||||
</vector>
|
|
@ -1,4 +0,0 @@
|
|||
<vector android:height="400dp" android:viewportHeight="24"
|
||||
android:viewportWidth="24" android:width="400dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF1313" android:pathData="M12.198,11.462v-0.237c0,-0.077 -0.05,-0.131 -0.114,-0.164V0.508c0,-0.047 -0.038,-0.086 -0.086,-0.086c-0.047,0 -0.086,0.038 -0.086,0.086v10.553c-0.063,0.033 -0.114,0.087 -0.114,0.164v0.238c-0.219,0.082 -0.376,0.29 -0.376,0.537s0.157,0.455 0.376,0.537v0.92c0,0.11 0.089,0.2 0.2,0.2s0.2,-0.089 0.2,-0.2v-0.919c0.221,-0.081 0.381,-0.289 0.381,-0.538S12.419,11.543 12.198,11.462zM11.999,12.094c-0.047,0 -0.086,-0.038 -0.086,-0.086s0.039,-0.086 0.086,-0.086c0.047,0 0.086,0.038 0.086,0.086S12.046,12.094 11.999,12.094z"/>
|
||||
</vector>
|
|
@ -1,39 +0,0 @@
|
|||
<vector android:height="400dp" android:viewportHeight="24"
|
||||
android:viewportWidth="24" android:width="400dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M11.999,0.008L11.999,2.708"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M11.999,21.307L11.999,24.008"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M23.999,12L21.298,12"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M2.699,12L-0.001,12"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M5.999,1.616L7.349,3.954"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M16.648,20.061L17.999,22.4"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M22.387,6.001L20.048,7.351"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M3.941,16.651L1.602,18.001"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M1.606,6.008L3.945,7.358"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M20.052,16.658L22.391,18.008"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M17.992,1.612L16.642,3.95"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M7.342,20.057L5.992,22.396"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
|
||||
</vector>
|
|
@ -8,7 +8,6 @@ import info.nightscout.androidaps.MainApp
|
|||
import info.nightscout.androidaps.danar.di.DanaRModule
|
||||
import info.nightscout.androidaps.insight.di.InsightDatabaseModule
|
||||
import info.nightscout.androidaps.insight.di.InsightModule
|
||||
import info.nightscout.plugins.sync.di.OpenHumansModule
|
||||
import info.nightscout.androidaps.plugins.pump.common.di.RileyLinkModule
|
||||
import info.nightscout.androidaps.plugins.pump.eopatch.dagger.EopatchModule
|
||||
import info.nightscout.androidaps.plugins.pump.medtronic.di.MedtronicModule
|
||||
|
@ -24,6 +23,7 @@ import info.nightscout.insulin.di.InsulinModule
|
|||
import info.nightscout.plugins.aps.di.ApsModule
|
||||
import info.nightscout.plugins.constraints.di.PluginsConstraintsModule
|
||||
import info.nightscout.plugins.di.PluginsModule
|
||||
import info.nightscout.plugins.sync.di.OpenHumansModule
|
||||
import info.nightscout.plugins.sync.di.SyncModule
|
||||
import info.nightscout.pump.combo.di.ComboModule
|
||||
import info.nightscout.pump.combov2.di.ComboV2Module
|
||||
|
@ -31,11 +31,9 @@ import info.nightscout.pump.common.di.PumpCommonModule
|
|||
import info.nightscout.pump.dana.di.DanaHistoryModule
|
||||
import info.nightscout.pump.dana.di.DanaModule
|
||||
import info.nightscout.pump.danars.di.DanaRSModule
|
||||
import info.nightscout.pump.medtrum.di.MedtrumModule
|
||||
import info.nightscout.pump.diaconn.di.DiaconnG8Module
|
||||
import info.nightscout.pump.medtrum.di.MedtrumModule
|
||||
import info.nightscout.pump.virtual.di.VirtualPumpModule
|
||||
import info.nightscout.rx.di.RxModule
|
||||
import info.nightscout.shared.di.SharedModule
|
||||
import info.nightscout.shared.impl.di.SharedImplModule
|
||||
import info.nightscout.source.di.SourceModule
|
||||
import info.nightscout.ui.di.UiModule
|
||||
|
@ -62,8 +60,6 @@ import javax.inject.Singleton
|
|||
InsulinModule::class,
|
||||
OpenHumansModule::class,
|
||||
PluginsModule::class,
|
||||
RxModule::class,
|
||||
SharedModule::class,
|
||||
SharedImplModule::class,
|
||||
UiModule::class,
|
||||
ValidatorsModule::class,
|
||||
|
|
3
core/ui/src/main/res/values-sw600dp/layout.xml
Normal file
3
core/ui/src/main/res/values-sw600dp/layout.xml
Normal file
|
@ -0,0 +1,3 @@
|
|||
<resources>
|
||||
<bool name="isTablet">true</bool>
|
||||
</resources>
|
3
core/ui/src/main/res/values/layout.xml
Normal file
3
core/ui/src/main/res/values/layout.xml
Normal file
|
@ -0,0 +1,3 @@
|
|||
<resources>
|
||||
<bool name="isTablet">false</bool>
|
||||
</resources>
|
|
@ -17,5 +17,6 @@ android {
|
|||
|
||||
dependencies {
|
||||
implementation project(':app-wear-shared:shared')
|
||||
implementation project(':app-wear-shared:shared-impl')
|
||||
api "com.google.android.material:material:$material_version"
|
||||
}
|
|
@ -32,7 +32,6 @@ import info.nightscout.rx.bus.RxBus
|
|||
import info.nightscout.rx.events.EventPreferenceChange
|
||||
import info.nightscout.rx.logging.AAPSLogger
|
||||
import info.nightscout.rx.logging.LTag
|
||||
import info.nightscout.shared.extensions.runOnUiThread
|
||||
import info.nightscout.shared.extensions.toVisibility
|
||||
import info.nightscout.shared.interfaces.ResourceHelper
|
||||
import io.reactivex.rxjava3.core.Completable
|
||||
|
@ -106,7 +105,7 @@ class MaintenanceFragment : DaggerFragment() {
|
|||
onError = { aapsLogger.error("Error clearing databases", it) },
|
||||
onComplete = {
|
||||
rxBus.send(EventPreferenceChange(rh.gs(info.nightscout.core.utils.R.string.key_units)))
|
||||
runOnUiThread { activity.recreate() }
|
||||
info.nightscout.shared.extensions.runOnUiThread { activity.recreate() }
|
||||
}
|
||||
)
|
||||
uel.log(Action.RESET_DATABASES, Sources.Maintenance)
|
||||
|
|
|
@ -16,6 +16,7 @@ android {
|
|||
|
||||
dependencies {
|
||||
implementation project(':app-wear-shared:shared')
|
||||
implementation project(':app-wear-shared:shared-impl')
|
||||
implementation project(':database:entities')
|
||||
implementation project(':database:impl')
|
||||
implementation project(':core:graphview')
|
||||
|
|
|
@ -34,6 +34,7 @@ import info.nightscout.rx.events.EventNewHistoryData
|
|||
import info.nightscout.rx.events.EventPumpStatusChanged
|
||||
import info.nightscout.rx.events.EventUpdateOverviewCalcProgress
|
||||
import info.nightscout.rx.logging.AAPSLogger
|
||||
import info.nightscout.shared.impl.rx.bus.RxBusImpl
|
||||
import info.nightscout.shared.interfaces.ResourceHelper
|
||||
import info.nightscout.shared.sharedPreferences.SP
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
|
@ -72,7 +73,7 @@ class OverviewPlugin @Inject constructor(
|
|||
|
||||
private var disposable: CompositeDisposable = CompositeDisposable()
|
||||
|
||||
override val overviewBus = RxBus(aapsSchedulers, aapsLogger)
|
||||
override val overviewBus = RxBusImpl(aapsSchedulers, aapsLogger)
|
||||
|
||||
override fun addNotificationWithDialogResponse(id: Int, text: String, level: Int, @StringRes actionButtonId: Int, title: String, message: String) {
|
||||
rxBus.send(
|
||||
|
|
|
@ -17,6 +17,7 @@ android {
|
|||
|
||||
dependencies {
|
||||
implementation project(':app-wear-shared:shared')
|
||||
implementation project(':app-wear-shared:shared-impl')
|
||||
implementation project(':database:entities')
|
||||
implementation project(':database:impl')
|
||||
implementation project(':core:interfaces')
|
||||
|
|
|
@ -16,6 +16,7 @@ android {
|
|||
|
||||
dependencies {
|
||||
implementation project(':app-wear-shared:shared')
|
||||
implementation project(':app-wear-shared:shared-impl')
|
||||
implementation project(':database:entities')
|
||||
implementation project(':database:impl')
|
||||
implementation project(':core:main')
|
||||
|
|
|
@ -19,7 +19,6 @@ import info.nightscout.pump.combo.ruffyscripter.PumpState
|
|||
import info.nightscout.rx.AapsSchedulers
|
||||
import info.nightscout.rx.bus.RxBus
|
||||
import info.nightscout.rx.events.EventQueueChanged
|
||||
import info.nightscout.shared.extensions.runOnUiThread
|
||||
import info.nightscout.shared.interfaces.ResourceHelper
|
||||
import info.nightscout.shared.utils.DateUtil
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
|
@ -62,7 +61,7 @@ class ComboFragment : DaggerFragment() {
|
|||
binding.comboRefreshButton.isEnabled = false
|
||||
commandQueue.readStatus(rh.gs(info.nightscout.core.ui.R.string.user_request), object : Callback() {
|
||||
override fun run() {
|
||||
runOnUiThread { binding.comboRefreshButton.isEnabled = true }
|
||||
activity?.runOnUiThread { binding.comboRefreshButton.isEnabled = true }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -153,14 +152,17 @@ class ComboFragment : DaggerFragment() {
|
|||
binding.comboInsulinstate.setTextColor(rh.gac(context, info.nightscout.core.ui.R.attr.defaultTextColor))
|
||||
binding.comboInsulinstate.setTypeface(null, Typeface.NORMAL)
|
||||
}
|
||||
|
||||
PumpState.LOW -> {
|
||||
binding.comboInsulinstate.setTextColor(rh.gac(context, info.nightscout.core.ui.R.attr.omniYellowColor))
|
||||
binding.comboInsulinstate.setTypeface(null, Typeface.BOLD)
|
||||
}
|
||||
|
||||
PumpState.EMPTY -> {
|
||||
binding.comboInsulinstate.setTextColor(rh.gac(context, info.nightscout.core.ui.R.attr.warningColor))
|
||||
binding.comboInsulinstate.setTypeface(null, Typeface.BOLD)
|
||||
}
|
||||
|
||||
else -> {
|
||||
binding.comboInsulinstate.setTextColor(rh.gac(context, info.nightscout.core.ui.R.attr.defaultTextColor))
|
||||
binding.comboInsulinstate.setTypeface(null, Typeface.NORMAL)
|
||||
|
@ -175,10 +177,12 @@ class ComboFragment : DaggerFragment() {
|
|||
binding.comboLastconnection.setText(R.string.combo_pump_connected_now)
|
||||
binding.comboLastconnection.setTextColor(rh.gac(context, info.nightscout.core.ui.R.attr.defaultTextColor))
|
||||
}
|
||||
|
||||
comboPlugin.pump.lastSuccessfulCmdTime + 30 * 60 * 1000 < System.currentTimeMillis() -> {
|
||||
binding.comboLastconnection.text = rh.gs(R.string.combo_no_pump_connection, min)
|
||||
binding.comboLastconnection.setTextColor(rh.gac(context, info.nightscout.core.ui.R.attr.warningColor))
|
||||
}
|
||||
|
||||
else -> {
|
||||
binding.comboLastconnection.text = minAgo
|
||||
binding.comboLastconnection.setTextColor(rh.gac(context, info.nightscout.core.ui.R.attr.defaultTextColor))
|
||||
|
|
|
@ -26,6 +26,7 @@ android {
|
|||
|
||||
dependencies {
|
||||
implementation project(':app-wear-shared:shared')
|
||||
implementation project(':app-wear-shared:shared-impl')
|
||||
implementation project(':core:libraries')
|
||||
implementation project(':core:interfaces')
|
||||
implementation project(':core:utils')
|
||||
|
|
|
@ -27,6 +27,7 @@ import javax.inject.Inject
|
|||
import javax.inject.Singleton
|
||||
|
||||
interface IAlarmRegistry {
|
||||
|
||||
fun add(alarmCode: AlarmCode, triggerAfter: Long, isFirst: Boolean = false): Maybe<AlarmCode>
|
||||
fun add(patchAeCodes: Set<PatchAeCode>)
|
||||
fun remove(alarmCode: AlarmCode): Maybe<AlarmCode>
|
||||
|
@ -34,11 +35,13 @@ interface IAlarmRegistry {
|
|||
|
||||
@Singleton
|
||||
class AlarmRegistry @Inject constructor() : IAlarmRegistry {
|
||||
|
||||
@Inject lateinit var mContext: Context
|
||||
@Inject lateinit var pm: IPreferenceManager
|
||||
@Inject lateinit var rxBus: RxBus
|
||||
@Inject lateinit var aapsLogger: AAPSLogger
|
||||
@Inject lateinit var aapsSchedulers: AapsSchedulers
|
||||
@Inject lateinit var dateUtil: DateUtil
|
||||
|
||||
private lateinit var mOsAlarmManager: AlarmManager
|
||||
private var mDisposable: Disposable? = null
|
||||
|
@ -49,19 +52,21 @@ class AlarmRegistry @Inject constructor() : IAlarmRegistry {
|
|||
mDisposable = pm.observePatchLifeCycle()
|
||||
.observeOn(aapsSchedulers.main)
|
||||
.subscribe {
|
||||
when(it){
|
||||
when (it) {
|
||||
PatchLifecycle.REMOVE_NEEDLE_CAP -> {
|
||||
val triggerAfter = pm.getPatchConfig().patchWakeupTimestamp + TimeUnit.HOURS.toMillis(1) - System.currentTimeMillis()
|
||||
compositeDisposable.add(add(AlarmCode.A020, triggerAfter).subscribe())
|
||||
}
|
||||
PatchLifecycle.ACTIVATED -> {
|
||||
|
||||
PatchLifecycle.ACTIVATED -> {
|
||||
|
||||
}
|
||||
PatchLifecycle.SHUTDOWN -> {
|
||||
|
||||
PatchLifecycle.SHUTDOWN -> {
|
||||
val sources = ArrayList<Maybe<*>>()
|
||||
sources.add(Maybe.just(true))
|
||||
pm.getAlarms().occurred.let{ occurredAlarms ->
|
||||
if(occurredAlarms.isNotEmpty()){
|
||||
pm.getAlarms().occurred.let { occurredAlarms ->
|
||||
if (occurredAlarms.isNotEmpty()) {
|
||||
occurredAlarms.keys.forEach { alarmCode ->
|
||||
sources.add(
|
||||
Maybe.just(alarmCode)
|
||||
|
@ -71,30 +76,30 @@ class AlarmRegistry @Inject constructor() : IAlarmRegistry {
|
|||
}
|
||||
}
|
||||
}
|
||||
pm.getAlarms().registered.let{ registeredAlarms ->
|
||||
if(registeredAlarms.isNotEmpty()){
|
||||
pm.getAlarms().registered.let { registeredAlarms ->
|
||||
if (registeredAlarms.isNotEmpty()) {
|
||||
registeredAlarms.keys.forEach { alarmCode ->
|
||||
sources.add(remove(alarmCode))
|
||||
}
|
||||
}
|
||||
}
|
||||
compositeDisposable.add(Maybe.concat(sources)
|
||||
.subscribe {
|
||||
pm.getAlarms().clear()
|
||||
pm.flushAlarms()
|
||||
}
|
||||
.subscribe {
|
||||
pm.getAlarms().clear()
|
||||
pm.flushAlarms()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun add(alarmCode: AlarmCode, triggerAfter: Long, isFirst: Boolean): Maybe<AlarmCode> {
|
||||
if(pm.getAlarms().occurred.containsKey(alarmCode)){
|
||||
if (pm.getAlarms().occurred.containsKey(alarmCode)) {
|
||||
return Maybe.just(alarmCode)
|
||||
}else {
|
||||
} else {
|
||||
val triggerTimeMilli = System.currentTimeMillis() + triggerAfter
|
||||
pm.getAlarms().register(alarmCode, triggerAfter)
|
||||
pm.flushAlarms()
|
||||
|
@ -109,11 +114,11 @@ class AlarmRegistry @Inject constructor() : IAlarmRegistry {
|
|||
override fun add(patchAeCodes: Set<PatchAeCode>) {
|
||||
compositeDisposable.add(
|
||||
Observable.fromIterable(patchAeCodes)
|
||||
.filter{patchAeCodeItem -> AlarmCode.findByPatchAeCode(patchAeCodeItem.aeValue) != null}
|
||||
.observeOn(aapsSchedulers.main)
|
||||
.filter { aeCodes -> AlarmCode.findByPatchAeCode(aeCodes.aeValue) != null }
|
||||
.flatMapMaybe{aeCodeResponse -> add(AlarmCode.findByPatchAeCode(aeCodeResponse.aeValue)!!, 0L, true)}
|
||||
.subscribe()
|
||||
.filter { patchAeCodeItem -> AlarmCode.findByPatchAeCode(patchAeCodeItem.aeValue) != null }
|
||||
.observeOn(aapsSchedulers.main)
|
||||
.filter { aeCodes -> AlarmCode.findByPatchAeCode(aeCodes.aeValue) != null }
|
||||
.flatMapMaybe { aeCodeResponse -> add(AlarmCode.findByPatchAeCode(aeCodeResponse.aeValue)!!, 0L, true) }
|
||||
.subscribe()
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -121,21 +126,21 @@ class AlarmRegistry @Inject constructor() : IAlarmRegistry {
|
|||
return Maybe.fromCallable {
|
||||
cancelOsAlarmInternal(alarmCode)
|
||||
val pendingIntent = createPendingIntent(alarmCode, 0)
|
||||
aapsLogger.debug("[${alarmCode}] OS Alarm added. ${DateUtil(mContext).toISOString(triggerTime)}")
|
||||
aapsLogger.debug("[${alarmCode}] OS Alarm added. ${dateUtil.toISOString(triggerTime)}")
|
||||
mOsAlarmManager.setAlarmClock(AlarmClockInfo(triggerTime, pendingIntent), pendingIntent)
|
||||
alarmCode
|
||||
}
|
||||
}
|
||||
|
||||
override fun remove(alarmCode: AlarmCode): Maybe<AlarmCode> {
|
||||
return if(pm.getAlarms().registered.containsKey(alarmCode)) {
|
||||
return if (pm.getAlarms().registered.containsKey(alarmCode)) {
|
||||
cancelOsAlarms(alarmCode)
|
||||
.doOnSuccess {
|
||||
pm.getAlarms().unregister(alarmCode)
|
||||
pm.flushAlarms()
|
||||
}
|
||||
.map { alarmCode }
|
||||
}else{
|
||||
} else {
|
||||
Maybe.just(alarmCode)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import info.nightscout.androidaps.plugins.pump.omnipod.common.definition.Omnipod
|
|||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.database.DashHistoryDatabase
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.database.HistoryRecordDao
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.mapper.HistoryMapper
|
||||
import info.nightscout.rx.logging.AAPSLoggerTest
|
||||
import info.nightscout.sharedtests.AAPSLoggerTest
|
||||
import org.junit.Before
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Test
|
||||
|
|
|
@ -2,7 +2,7 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.endecry
|
|||
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessagePacket
|
||||
import info.nightscout.core.utils.toHex
|
||||
import info.nightscout.rx.logging.AAPSLoggerTest
|
||||
import info.nightscout.sharedtests.AAPSLoggerTest
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.spongycastle.util.encoders.Hex
|
||||
|
|
|
@ -4,7 +4,7 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.util.Rand
|
|||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.util.X25519KeyGenerator
|
||||
import info.nightscout.core.utils.toHex
|
||||
import info.nightscout.interfaces.Config
|
||||
import info.nightscout.rx.logging.AAPSLoggerTest
|
||||
import info.nightscout.sharedtests.AAPSLoggerTest
|
||||
import info.nightscout.sharedtests.TestBase
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session
|
||||
|
||||
import info.nightscout.core.utils.toHex
|
||||
import info.nightscout.rx.logging.AAPSLoggerTest
|
||||
import info.nightscout.sharedtests.AAPSLoggerTest
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.spongycastle.util.encoders.Hex
|
||||
|
|
|
@ -2,7 +2,7 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session
|
|||
|
||||
import info.nightscout.core.utils.toHex
|
||||
import info.nightscout.interfaces.Config
|
||||
import info.nightscout.rx.logging.AAPSLoggerTest
|
||||
import info.nightscout.sharedtests.AAPSLoggerTest
|
||||
import info.nightscout.sharedtests.TestBase
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
|
|
|
@ -37,6 +37,8 @@ dependencies {
|
|||
implementation project(':pump:rileylink')
|
||||
implementation project(':pump:omnipod-common')
|
||||
|
||||
testImplementation project(':app-wear-shared:shared-impl')
|
||||
|
||||
api "androidx.room:room-ktx:$room_version"
|
||||
api "androidx.room:room-runtime:$room_version"
|
||||
api "androidx.room:room-rxjava3:$room_version"
|
||||
|
|
|
@ -2,10 +2,10 @@ package info.nightscout.androidaps.plugins.pump.omnipod.eros.manager
|
|||
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.eros.driver.definition.FirmwareVersion
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.eros.driver.definition.PodProgressStatus
|
||||
import info.nightscout.rx.TestAapsSchedulers
|
||||
import info.nightscout.rx.bus.RxBus
|
||||
import info.nightscout.shared.impl.rx.bus.RxBusImpl
|
||||
import info.nightscout.shared.sharedPreferences.SP
|
||||
import info.nightscout.sharedtests.TestBase
|
||||
import info.nightscout.sharedtests.rx.TestAapsSchedulers
|
||||
import org.joda.time.DateTime
|
||||
import org.joda.time.DateTimeUtils
|
||||
import org.joda.time.DateTimeZone
|
||||
|
@ -19,7 +19,7 @@ class AapsErosPodStateManagerTest : TestBase() {
|
|||
|
||||
@Mock lateinit var sp: SP
|
||||
|
||||
private val rxBus = RxBus(TestAapsSchedulers(), aapsLogger)
|
||||
private val rxBus = RxBusImpl(TestAapsSchedulers(), aapsLogger)
|
||||
|
||||
@Test fun times() {
|
||||
val timeZone = DateTimeZone.UTC
|
||||
|
|
|
@ -6,8 +6,6 @@ import dagger.Module
|
|||
import dagger.Provides
|
||||
import dagger.android.HasAndroidInjector
|
||||
import info.nightscout.androidaps.WearApp
|
||||
import info.nightscout.rx.di.RxModule
|
||||
import info.nightscout.shared.di.SharedModule
|
||||
import info.nightscout.shared.impl.di.SharedImplModule
|
||||
import kotlinx.datetime.Clock
|
||||
|
||||
|
@ -16,8 +14,6 @@ import kotlinx.datetime.Clock
|
|||
includes = [
|
||||
WearModule.AppBindings::class,
|
||||
WearActivitiesModule::class,
|
||||
RxModule::class,
|
||||
SharedModule::class,
|
||||
SharedImplModule::class
|
||||
]
|
||||
)
|
||||
|
|
|
@ -35,15 +35,15 @@ import info.nightscout.androidaps.watchfaces.utils.BaseWatchFace
|
|||
import info.nightscout.rx.logging.LTag
|
||||
import info.nightscout.rx.weardata.CUSTOM_VERSION
|
||||
import info.nightscout.rx.weardata.CwfData
|
||||
import info.nightscout.rx.weardata.ResFileMap
|
||||
import info.nightscout.rx.weardata.CwfResDataMap
|
||||
import info.nightscout.rx.weardata.CwfMetadataKey
|
||||
import info.nightscout.rx.weardata.CwfMetadataMap
|
||||
import info.nightscout.rx.weardata.ResData
|
||||
import info.nightscout.rx.weardata.ResFormat
|
||||
import info.nightscout.rx.weardata.CwfResDataMap
|
||||
import info.nightscout.rx.weardata.EventData
|
||||
import info.nightscout.rx.weardata.JsonKeyValues
|
||||
import info.nightscout.rx.weardata.JsonKeys.*
|
||||
import info.nightscout.rx.weardata.ResData
|
||||
import info.nightscout.rx.weardata.ResFileMap
|
||||
import info.nightscout.rx.weardata.ResFormat
|
||||
import info.nightscout.rx.weardata.ViewKeys
|
||||
import info.nightscout.rx.weardata.ZipWatchfaceFormat
|
||||
import info.nightscout.shared.extensions.toVisibility
|
||||
|
@ -208,7 +208,7 @@ class CustomWatchface : BaseWatchFace() {
|
|||
it.clearColorFilter()
|
||||
view.setImageDrawable(it)
|
||||
} ?: apply {
|
||||
view.setImageDrawable(id.defaultDrawable?.let {resources.getDrawable(it)})
|
||||
view.setImageDrawable(id.defaultDrawable?.let { resources.getDrawable(it) })
|
||||
if (viewJson.has(COLOR.key))
|
||||
view.setColorFilter(getColor(viewJson.optString(COLOR.key)))
|
||||
else
|
||||
|
@ -217,7 +217,7 @@ class CustomWatchface : BaseWatchFace() {
|
|||
}
|
||||
|
||||
}
|
||||
} ?:apply {
|
||||
} ?: apply {
|
||||
view.visibility = View.GONE
|
||||
if (view is TextView) {
|
||||
view.text = ""
|
||||
|
@ -301,7 +301,7 @@ class CustomWatchface : BaseWatchFace() {
|
|||
}
|
||||
val metadataMap = ZipWatchfaceFormat.loadMetadata(json)
|
||||
val drawableDataMap: CwfResDataMap = mutableMapOf()
|
||||
getResourceByteArray(info.nightscout.shared.R.drawable.watchface_custom)?.let {
|
||||
getResourceByteArray(info.nightscout.shared.impl.R.drawable.watchface_custom)?.let {
|
||||
drawableDataMap[ResFileMap.CUSTOM_WATCHFACE.fileName] = ResData(it, ResFormat.PNG)
|
||||
}
|
||||
return EventData.ActionSetCustomWatchface(CwfData(json.toString(4), metadataMap, drawableDataMap))
|
||||
|
@ -394,7 +394,7 @@ class CustomWatchface : BaseWatchFace() {
|
|||
@StringRes val pref: Int?,
|
||||
@IdRes val defaultDrawable: Int?,
|
||||
val customDrawable: ResFileMap?,
|
||||
val customHigh:ResFileMap?,
|
||||
val customHigh: ResFileMap?,
|
||||
val customLow: ResFileMap?
|
||||
) {
|
||||
|
||||
|
@ -402,7 +402,7 @@ class CustomWatchface : BaseWatchFace() {
|
|||
ViewKeys.BACKGROUND.key,
|
||||
R.id.background,
|
||||
null,
|
||||
info.nightscout.shared.R.drawable.background,
|
||||
info.nightscout.shared.impl.R.drawable.background,
|
||||
ResFileMap.BACKGROUND,
|
||||
ResFileMap.BACKGROUND_HIGH,
|
||||
ResFileMap.BACKGROUND_LOW
|
||||
|
@ -448,7 +448,7 @@ class CustomWatchface : BaseWatchFace() {
|
|||
ViewKeys.COVER_PLATE.key,
|
||||
R.id.cover_plate,
|
||||
null,
|
||||
info.nightscout.shared.R.drawable.simplified_dial,
|
||||
info.nightscout.shared.impl.R.drawable.simplified_dial,
|
||||
ResFileMap.COVER_PLATE,
|
||||
ResFileMap.COVER_PLATE_HIGH,
|
||||
ResFileMap.COVER_PLATE_LOW
|
||||
|
@ -457,7 +457,7 @@ class CustomWatchface : BaseWatchFace() {
|
|||
ViewKeys.HOUR_HAND.key,
|
||||
R.id.hour_hand,
|
||||
null,
|
||||
info.nightscout.shared.R.drawable.hour_hand,
|
||||
info.nightscout.shared.impl.R.drawable.hour_hand,
|
||||
ResFileMap.HOUR_HAND,
|
||||
ResFileMap.HOUR_HAND_HIGH,
|
||||
ResFileMap.HOUR_HAND_LOW
|
||||
|
@ -466,7 +466,7 @@ class CustomWatchface : BaseWatchFace() {
|
|||
ViewKeys.MINUTE_HAND.key,
|
||||
R.id.minute_hand,
|
||||
null,
|
||||
info.nightscout.shared.R.drawable.minute_hand,
|
||||
info.nightscout.shared.impl.R.drawable.minute_hand,
|
||||
ResFileMap.MINUTE_HAND,
|
||||
ResFileMap.MINUTE_HAND_HIGH,
|
||||
ResFileMap.MINUTE_HAND_LOW
|
||||
|
@ -475,7 +475,7 @@ class CustomWatchface : BaseWatchFace() {
|
|||
ViewKeys.SECOND_HAND.key,
|
||||
R.id.second_hand,
|
||||
R.string.key_show_seconds,
|
||||
info.nightscout.shared.R.drawable.second_hand,
|
||||
info.nightscout.shared.impl.R.drawable.second_hand,
|
||||
ResFileMap.SECOND_HAND,
|
||||
ResFileMap.SECOND_HAND_HIGH,
|
||||
ResFileMap.SECOND_HAND_LOW
|
||||
|
@ -491,16 +491,25 @@ class CustomWatchface : BaseWatchFace() {
|
|||
|
||||
fun drawable(resources: Resources, drawableDataMap: CwfResDataMap, sgvLevel: Long): Drawable? = customDrawable?.let { cd ->
|
||||
when (sgvLevel) {
|
||||
1L -> { customHigh?.let {drawableDataMap[customHigh.fileName]}?.toDrawable(resources) ?: drawableDataMap[cd.fileName]?.toDrawable(resources) }
|
||||
0L -> { drawableDataMap[cd.fileName]?.toDrawable(resources) }
|
||||
-1L -> { customLow?.let {drawableDataMap[customLow.fileName]}?.toDrawable(resources) ?: drawableDataMap[cd.fileName]?.toDrawable(resources) }
|
||||
1L -> {
|
||||
customHigh?.let { drawableDataMap[customHigh.fileName] }?.toDrawable(resources) ?: drawableDataMap[cd.fileName]?.toDrawable(resources)
|
||||
}
|
||||
|
||||
0L -> {
|
||||
drawableDataMap[cd.fileName]?.toDrawable(resources)
|
||||
}
|
||||
|
||||
-1L -> {
|
||||
customLow?.let { drawableDataMap[customLow.fileName] }?.toDrawable(resources) ?: drawableDataMap[cd.fileName]?.toDrawable(resources)
|
||||
}
|
||||
|
||||
else -> drawableDataMap[cd.fileName]?.toDrawable(resources)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum class TrendArrowMap(val symbol: String, @DrawableRes val icon: Int,val customDrawable: ResFileMap?) {
|
||||
private enum class TrendArrowMap(val symbol: String, @DrawableRes val icon: Int, val customDrawable: ResFileMap?) {
|
||||
NONE("??", R.drawable.ic_invalid, ResFileMap.ARROW_NONE),
|
||||
TRIPLE_UP("X", R.drawable.ic_doubleup, ResFileMap.ARROW_DOUBLE_UP),
|
||||
DOUBLE_UP("\u21c8", R.drawable.ic_doubleup, ResFileMap.ARROW_DOUBLE_UP),
|
||||
|
@ -515,8 +524,8 @@ private enum class TrendArrowMap(val symbol: String, @DrawableRes val icon: Int,
|
|||
companion object {
|
||||
|
||||
fun drawable(direction: String?, resources: Resources, drawableDataMap: CwfResDataMap): Drawable {
|
||||
val arrow = values().firstOrNull { it.symbol == direction } ?:NONE
|
||||
return arrow.customDrawable?. let {drawableDataMap[arrow.customDrawable.fileName]}?.toDrawable(resources) ?:resources.getDrawable(arrow.icon)
|
||||
val arrow = values().firstOrNull { it.symbol == direction } ?: NONE
|
||||
return arrow.customDrawable?.let { drawableDataMap[arrow.customDrawable.fileName] }?.toDrawable(resources) ?: resources.getDrawable(arrow.icon)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -558,7 +567,7 @@ private enum class FontMap(val key: String, var font: Typeface, @FontRes val fon
|
|||
resDataMap.filter { (_, resData) ->
|
||||
resData.format == ResFormat.TTF || resData.format == ResFormat.OTF
|
||||
}.forEach { (key, resData) ->
|
||||
customFonts[key.lowercase()] = resData.toTypeface() ?:Typeface.DEFAULT
|
||||
customFonts[key.lowercase()] = resData.toTypeface() ?: Typeface.DEFAULT
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,16 +7,13 @@ import android.support.wearable.watchface.WatchFaceStyle
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import info.nightscout.androidaps.R
|
||||
import info.nightscout.androidaps.databinding.ActivityDigitalstyleBinding
|
||||
import info.nightscout.shared.extensions.toVisibility
|
||||
import info.nightscout.androidaps.watchfaces.utils.BaseWatchFace
|
||||
import info.nightscout.rx.logging.LTag
|
||||
|
||||
|
||||
class DigitalStyleWatchface : BaseWatchFace() {
|
||||
|
||||
private lateinit var binding: ActivityDigitalstyleBinding
|
||||
|
|
|
@ -4,8 +4,8 @@ import android.content.Context
|
|||
import android.hardware.Sensor
|
||||
import android.hardware.SensorManager
|
||||
import info.nightscout.rx.AapsSchedulers
|
||||
import info.nightscout.rx.logging.AAPSLoggerTest
|
||||
import info.nightscout.rx.weardata.EventData.ActionHeartRate
|
||||
import info.nightscout.sharedtests.AAPSLoggerTest
|
||||
import io.reactivex.rxjava3.core.Scheduler
|
||||
import io.reactivex.rxjava3.disposables.Disposable
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
|
@ -22,8 +22,9 @@ import org.mockito.Mockito.`when`
|
|||
import java.util.concurrent.TimeUnit
|
||||
|
||||
internal class HeartRateListenerTest {
|
||||
|
||||
private val aapsLogger = AAPSLoggerTest()
|
||||
private val aapsSchedulers = object: AapsSchedulers {
|
||||
private val aapsSchedulers = object : AapsSchedulers {
|
||||
override val main: Scheduler = mock(Scheduler::class.java)
|
||||
override val io: Scheduler = mock(Scheduler::class.java)
|
||||
override val cpu: Scheduler = mock(Scheduler::class.java)
|
||||
|
@ -35,11 +36,15 @@ internal class HeartRateListenerTest {
|
|||
|
||||
private fun create(timestampMillis: Long): HeartRateListener {
|
||||
val ctx = mock(Context::class.java)
|
||||
`when`(aapsSchedulers.io.schedulePeriodicallyDirect(
|
||||
any(), eq(60_000L), eq(60_000L), eq(TimeUnit.MILLISECONDS))).thenReturn(schedule)
|
||||
`when`(
|
||||
aapsSchedulers.io.schedulePeriodicallyDirect(
|
||||
any(), eq(60_000L), eq(60_000L), eq(TimeUnit.MILLISECONDS)
|
||||
)
|
||||
).thenReturn(schedule)
|
||||
val listener = HeartRateListener(ctx, aapsLogger, aapsSchedulers, timestampMillis)
|
||||
verify(aapsSchedulers.io).schedulePeriodicallyDirect(
|
||||
any(), eq(60_000L), eq(60_000L), eq(TimeUnit.MILLISECONDS))
|
||||
any(), eq(60_000L), eq(60_000L), eq(TimeUnit.MILLISECONDS)
|
||||
)
|
||||
listener.sendHeartRate = { hr -> heartRates.add(hr) }
|
||||
return listener
|
||||
}
|
||||
|
@ -49,10 +54,11 @@ internal class HeartRateListenerTest {
|
|||
timestamp: Long,
|
||||
heartRate: Int,
|
||||
sensorType: Int? = Sensor.TYPE_HEART_RATE,
|
||||
accuracy: Int = SensorManager.SENSOR_STATUS_ACCURACY_HIGH) {
|
||||
accuracy: Int = SensorManager.SENSOR_STATUS_ACCURACY_HIGH
|
||||
) {
|
||||
listener.onSensorChanged(sensorType, accuracy, timestamp, floatArrayOf(heartRate.toFloat()))
|
||||
}
|
||||
|
||||
|
||||
@BeforeEach
|
||||
fun before() {
|
||||
heartRates.clear()
|
||||
|
@ -66,7 +72,7 @@ internal class HeartRateListenerTest {
|
|||
Mockito.verifyNoInteractions(aapsSchedulers.newThread)
|
||||
verify(schedule).dispose()
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun onSensorChanged() {
|
||||
val start = System.currentTimeMillis()
|
||||
|
@ -95,7 +101,7 @@ internal class HeartRateListenerTest {
|
|||
sendSensorEvent(listener, start, 80)
|
||||
assertEquals(0, heartRates.size)
|
||||
assertEquals(80, listener.currentHeartRateBpm)
|
||||
sendSensorEvent(listener, start + d1,100)
|
||||
sendSensorEvent(listener, start + d1, 100)
|
||||
assertEquals(0, heartRates.size)
|
||||
assertEquals(100, listener.currentHeartRateBpm)
|
||||
|
||||
|
@ -117,7 +123,7 @@ internal class HeartRateListenerTest {
|
|||
listener.send(start + d1)
|
||||
assertEquals(1, heartRates.size)
|
||||
|
||||
sendSensorEvent(listener, start + d1,100)
|
||||
sendSensorEvent(listener, start + d1, 100)
|
||||
assertEquals(1, heartRates.size)
|
||||
listener.send(start + d2)
|
||||
assertEquals(2, heartRates.size)
|
||||
|
|
Loading…
Reference in a new issue