mirror of
https://github.com/szkolny-eu/szkolny-android.git
synced 2024-11-24 19:04:38 -06:00
[APIv2/Szkolny] Add Szkolny API and add getting shared events.
This commit is contained in:
parent
d6f9b81de6
commit
40ba9e8434
@ -170,6 +170,9 @@ dependencies {
|
||||
implementation 'com.github.kuba2k2:RecyclerTabLayout:700f980584'
|
||||
|
||||
implementation 'com.github.kuba2k2:Tachyon:551943a6b5'
|
||||
|
||||
implementation "com.squareup.retrofit2:retrofit:${versions.retrofit}"
|
||||
implementation "com.squareup.retrofit2:converter-gson:${versions.retrofit}"
|
||||
}
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
@ -222,7 +222,7 @@ public class App extends androidx.multidex.MultiDexApplication implements Config
|
||||
byte[] signatureBytes = signature.toByteArray();
|
||||
MessageDigest md = MessageDigest.getInstance("SHA");
|
||||
md.update(signatureBytes);
|
||||
this.signature = Base64.encodeToString(md.digest(), Base64.DEFAULT);
|
||||
this.signature = Base64.encodeToString(md.digest(), Base64.NO_WRAP);
|
||||
//Log.d(TAG, "Signature is "+this.signature);
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,8 @@ import android.text.*
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import android.text.style.StrikethroughSpan
|
||||
import android.text.style.StyleSpan
|
||||
import android.util.Base64.NO_WRAP
|
||||
import android.util.Base64.encodeToString
|
||||
import android.util.LongSparseArray
|
||||
import android.util.SparseArray
|
||||
import android.util.TypedValue
|
||||
@ -34,15 +36,20 @@ import im.wangchao.mhttp.Response
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.RequestBody
|
||||
import okio.Buffer
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.teams.Team
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||
import pl.szczodrzynski.navlib.getColorFromAttr
|
||||
import pl.szczodrzynski.navlib.getColorFromRes
|
||||
import java.math.BigInteger
|
||||
import java.security.MessageDigest
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.zip.CRC32
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
|
||||
fun List<Teacher>.byId(id: Long) = firstOrNull { it.id == id }
|
||||
@ -358,6 +365,28 @@ fun String.crc32(): Long {
|
||||
return crc.value
|
||||
}
|
||||
|
||||
fun String.hmacSHA1(password: String): String {
|
||||
val key = SecretKeySpec(password.toByteArray(), "HmacSHA1")
|
||||
|
||||
val mac = Mac.getInstance("HmacSHA1").apply {
|
||||
init(key)
|
||||
update(this@hmacSHA1.toByteArray())
|
||||
}
|
||||
|
||||
return encodeToString(mac.doFinal(), NO_WRAP)
|
||||
}
|
||||
|
||||
fun String.md5(): String {
|
||||
val md = MessageDigest.getInstance("MD5")
|
||||
return BigInteger(1, md.digest(toByteArray())).toString(16).padStart(32, '0')
|
||||
}
|
||||
|
||||
fun RequestBody.bodyToString(): String {
|
||||
val buffer = Buffer()
|
||||
writeTo(buffer)
|
||||
return buffer.readUtf8()
|
||||
}
|
||||
|
||||
fun Long.formatDate(format: String = "yyyy-MM-dd HH:mm:ss"): String = SimpleDateFormat(format).format(this)
|
||||
|
||||
fun CharSequence?.asColoredSpannable(colorInt: Int): Spannable {
|
||||
|
@ -15,12 +15,10 @@ import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.api.v2.events.*
|
||||
import pl.szczodrzynski.edziennik.api.v2.events.requests.ServiceCloseRequest
|
||||
import pl.szczodrzynski.edziennik.api.v2.events.requests.TaskCancelRequest
|
||||
import pl.szczodrzynski.edziennik.api.v2.events.task.EdziennikTask
|
||||
import pl.szczodrzynski.edziennik.api.v2.events.task.ErrorReportTask
|
||||
import pl.szczodrzynski.edziennik.api.v2.events.task.IApiTask
|
||||
import pl.szczodrzynski.edziennik.api.v2.events.task.NotifyTask
|
||||
import pl.szczodrzynski.edziennik.api.v2.events.task.*
|
||||
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback
|
||||
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.profiles.ProfileFull
|
||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||
import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
@ -41,8 +39,8 @@ class ApiService : Service() {
|
||||
private val app by lazy { applicationContext as App }
|
||||
|
||||
private val finishingTaskQueue = mutableListOf(
|
||||
NotifyTask(),
|
||||
ErrorReportTask()
|
||||
ServerSyncTask(),
|
||||
NotifyTask()
|
||||
)
|
||||
private val taskQueue = mutableListOf<IApiTask>()
|
||||
private val errorList = mutableListOf<ApiError>()
|
||||
@ -63,6 +61,8 @@ class ApiService : Service() {
|
||||
private var lastEventTime = System.currentTimeMillis()
|
||||
private var taskCancelTries = 0
|
||||
|
||||
private val syncingProfiles = mutableListOf<ProfileFull>()
|
||||
|
||||
/* ______ _ _ _ _ _____ _ _ _ _
|
||||
| ____| | | (_) (_) | / ____| | | | | | |
|
||||
| |__ __| |_____ ___ _ __ _ __ _| | __ | | __ _| | | |__ __ _ ___| | __
|
||||
@ -156,11 +156,14 @@ class ApiService : Service() {
|
||||
// post an event
|
||||
EventBus.getDefault().post(ApiTaskStartedEvent(taskProfileId, task.profile))
|
||||
|
||||
task.profile?.let { syncingProfiles.add(it) }
|
||||
|
||||
try {
|
||||
when (task) {
|
||||
is EdziennikTask -> task.run(app, taskCallback)
|
||||
is NotifyTask -> task.run(app, taskCallback)
|
||||
is ErrorReportTask -> task.run(app, taskCallback, notification, errorList)
|
||||
is ServerSyncTask -> task.run(app, syncingProfiles, taskCallback)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
taskCallback.onError(ApiError(TAG, EXCEPTION_API_TASK).withThrowable(e))
|
||||
|
@ -39,7 +39,7 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
|
||||
taskName = app.getString(R.string.edziennik_notification_api_first_login_title)
|
||||
} else {
|
||||
// get the requested profile and login store
|
||||
val profile = app.db.profileDao().getByIdNow(profileId)
|
||||
val profile = app.db.profileDao().getFullByIdNow(profileId)
|
||||
this.profile = profile
|
||||
if (profile == null) {
|
||||
return
|
||||
|
@ -11,11 +11,11 @@ import android.os.Build.VERSION_CODES.O
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.api.v2.ApiService
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.profiles.ProfileFull
|
||||
|
||||
abstract class IApiTask(open val profileId: Int) {
|
||||
var taskId: Int = 0
|
||||
var profile: Profile? = null
|
||||
var profile: ProfileFull? = null
|
||||
var taskName: String? = null
|
||||
|
||||
/**
|
||||
@ -39,4 +39,4 @@ abstract class IApiTask(open val profileId: Int) {
|
||||
override fun toString(): String {
|
||||
return "IApiTask(profileId=$profileId, taskId=$taskId, profile=$profile, taskName=$taskName)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,9 @@ package pl.szczodrzynski.edziennik.api.v2.events.task
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback
|
||||
import pl.szczodrzynski.edziennik.api.v2.szkolny.SzkolnyApi
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.profiles.ProfileFull
|
||||
|
||||
class ServerSyncTask : IApiTask(-1) {
|
||||
override fun prepare(app: App) {
|
||||
@ -17,9 +20,25 @@ class ServerSyncTask : IApiTask(-1) {
|
||||
|
||||
}
|
||||
|
||||
fun run(app: App, taskCallback: EdziennikCallback) {
|
||||
fun run(app: App, profiles: List<ProfileFull>, taskCallback: EdziennikCallback) {
|
||||
val api = SzkolnyApi(app, profiles)
|
||||
|
||||
val events = api.getEvents()
|
||||
|
||||
if (events.isNotEmpty()) {
|
||||
app.db.eventDao().addAll(events)
|
||||
app.db.metadataDao().addAllIgnore(events.map { event ->
|
||||
Metadata(
|
||||
event.profileId,
|
||||
Metadata.TYPE_EVENT,
|
||||
event.id,
|
||||
event.seen,
|
||||
event.notified,
|
||||
event.addedDate
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
taskCallback.onCompleted()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (c) Kacper Ziubryniewicz 2019-12-8
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.api.v2.szkolny
|
||||
|
||||
import android.os.Build
|
||||
import com.google.gson.GsonBuilder
|
||||
import okhttp3.OkHttpClient
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.BuildConfig
|
||||
import pl.szczodrzynski.edziennik.api.v2.szkolny.adapter.DateAdapter
|
||||
import pl.szczodrzynski.edziennik.api.v2.szkolny.adapter.TimeAdapter
|
||||
import pl.szczodrzynski.edziennik.api.v2.szkolny.interceptor.SignatureInterceptor
|
||||
import pl.szczodrzynski.edziennik.api.v2.szkolny.request.ServerSyncRequest
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.events.EventFull
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.profiles.ProfileFull
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import retrofit2.create
|
||||
import java.util.concurrent.TimeUnit.SECONDS
|
||||
|
||||
class SzkolnyApi(val app: App, val profiles: List<ProfileFull>) {
|
||||
|
||||
private var api: SzkolnyService
|
||||
|
||||
init {
|
||||
val okHttpClient: OkHttpClient = app.http.newBuilder()
|
||||
.followRedirects(true)
|
||||
.callTimeout(30, SECONDS)
|
||||
.addInterceptor(SignatureInterceptor(app))
|
||||
.build()
|
||||
|
||||
val gsonConverterFactory = GsonConverterFactory.create(
|
||||
GsonBuilder()
|
||||
.setLenient()
|
||||
.registerTypeAdapter(Date::class.java, DateAdapter())
|
||||
.registerTypeAdapter(Time::class.java, TimeAdapter())
|
||||
.create())
|
||||
|
||||
val retrofit: Retrofit = Retrofit.Builder()
|
||||
.baseUrl("https://api.szkolny.eu/")
|
||||
.addConverterFactory(gsonConverterFactory)
|
||||
.client(okHttpClient)
|
||||
.build()
|
||||
|
||||
api = retrofit.create()
|
||||
}
|
||||
|
||||
fun getEvents(): List<EventFull> {
|
||||
val teams = app.db.teamDao().allNow
|
||||
|
||||
val response = api.serverSync(ServerSyncRequest(
|
||||
deviceId = app.deviceId,
|
||||
device = ServerSyncRequest.Device(
|
||||
osType = "Android",
|
||||
osVersion = Build.VERSION.RELEASE,
|
||||
hardware = "${Build.MANUFACTURER} ${Build.MODEL}",
|
||||
pushToken = app.config.sync.tokenApp,
|
||||
appVersion = BuildConfig.VERSION_NAME,
|
||||
appType = BuildConfig.BUILD_TYPE,
|
||||
appVersionCode = BuildConfig.VERSION_CODE,
|
||||
syncInterval = app.config.sync.interval
|
||||
),
|
||||
userCodes = profiles.map { it.usernameId },
|
||||
users = profiles.map { profile ->
|
||||
ServerSyncRequest.User(
|
||||
profile.usernameId,
|
||||
profile.studentNameLong ?: "",
|
||||
profile.studentNameShort ?: "",
|
||||
profile.loginStoreType,
|
||||
teams.filter { it.profileId == profile.id }.map { it.code }
|
||||
)
|
||||
}
|
||||
)).execute().body()
|
||||
|
||||
val events = mutableListOf<EventFull>()
|
||||
|
||||
response?.data?.events?.forEach { event ->
|
||||
teams.filter { it.code == event.teamCode }.forEach { team ->
|
||||
val profile = profiles.firstOrNull { it.id == team.profileId }
|
||||
|
||||
events.add(event.apply {
|
||||
profileId = team.profileId
|
||||
teamId = team.id
|
||||
addedManually = true
|
||||
seen = profile?.empty ?: false
|
||||
notified = profile?.empty ?: false
|
||||
|
||||
if (profile?.usernameId == event.sharedBy) sharedBy = "self"
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return events
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright (c) Kacper Ziubryniewicz 2019-12-8
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.api.v2.szkolny
|
||||
|
||||
import pl.szczodrzynski.edziennik.api.v2.szkolny.request.ServerSyncRequest
|
||||
import pl.szczodrzynski.edziennik.api.v2.szkolny.response.ApiResponse
|
||||
import pl.szczodrzynski.edziennik.api.v2.szkolny.response.ServerSyncResponse
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
|
||||
interface SzkolnyService {
|
||||
|
||||
@POST("appSync")
|
||||
fun serverSync(@Body request: ServerSyncRequest): Call<ApiResponse<ServerSyncResponse>>
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) Kacper Ziubryniewicz 2019-12-8
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.api.v2.szkolny.adapter
|
||||
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonToken
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
|
||||
class DateAdapter : TypeAdapter<Date>() {
|
||||
override fun write(writer: JsonWriter?, value: Date?) {}
|
||||
|
||||
override fun read(reader: JsonReader?): Date? {
|
||||
if (reader?.peek() == JsonToken.NULL) {
|
||||
reader.nextNull()
|
||||
return null
|
||||
}
|
||||
return reader?.nextInt()?.let { Date.fromValue(it) }
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) Kacper Ziubryniewicz 2019-12-8
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.api.v2.szkolny.adapter
|
||||
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonToken
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||
|
||||
class TimeAdapter : TypeAdapter<Time>() {
|
||||
override fun write(writer: JsonWriter?, value: Time?) {}
|
||||
|
||||
override fun read(reader: JsonReader?): Time? {
|
||||
if (reader?.peek() == JsonToken.NULL) {
|
||||
reader.nextNull()
|
||||
return null
|
||||
}
|
||||
return reader?.nextInt()?.let { Time.fromValue(it) }
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) Kacper Ziubryniewicz 2019-12-8
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.api.v2.szkolny.interceptor
|
||||
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
|
||||
class SignatureInterceptor(val app: App) : Interceptor {
|
||||
companion object {
|
||||
private const val API_KEY = "szkolny_android_42a66f0842fc7da4e37c66732acf705a"
|
||||
private const val API_PASSWORD = "HodrJ+6OAl9zqlK1IlYBUg=="
|
||||
}
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val request = chain.request()
|
||||
|
||||
val timestamp = currentTimeUnix()
|
||||
val body = request.body()?.bodyToString() ?: ""
|
||||
val url = request.url().toString()
|
||||
|
||||
return chain.proceed(
|
||||
request.newBuilder()
|
||||
.header("X-ApiKey", API_KEY)
|
||||
.header("X-AppVersion", BuildConfig.VERSION_CODE.toString())
|
||||
.header("X-Timestamp", timestamp.toString())
|
||||
.header("X-Signature", sign(timestamp, body, url))
|
||||
.build())
|
||||
}
|
||||
|
||||
private fun sign(timestamp: Long, body: String, url: String): String {
|
||||
val content = timestamp.toString().md5() + body.md5() + url.md5()
|
||||
val password = API_PASSWORD + BuildConfig.VERSION_CODE.toString() + app.signature
|
||||
|
||||
return content.hmacSHA1(password)
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) Kacper Ziubryniewicz 2019-12-8
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.api.v2.szkolny.request
|
||||
|
||||
data class ServerSyncRequest(
|
||||
|
||||
val deviceId: String,
|
||||
val device: Device? = null,
|
||||
|
||||
val userCodes: List<String>,
|
||||
val users: List<User>? = null
|
||||
) {
|
||||
data class Device(
|
||||
val osType: String,
|
||||
val osVersion: String,
|
||||
val hardware: String,
|
||||
val pushToken: String?,
|
||||
val appVersion: String,
|
||||
val appType: String,
|
||||
val appVersionCode: Int,
|
||||
val syncInterval: Int
|
||||
)
|
||||
|
||||
data class User(
|
||||
val userCode: String,
|
||||
val studentName: String,
|
||||
val studentNameShort: String,
|
||||
val loginType: Int,
|
||||
val teamCodes: List<String>
|
||||
)
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) Kacper Ziubryniewicz 2019-12-8
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.api.v2.szkolny.response
|
||||
|
||||
data class ApiResponse<T> (
|
||||
|
||||
val success: Boolean,
|
||||
|
||||
val errors: List<Error>? = null,
|
||||
|
||||
val data: T? = null
|
||||
) {
|
||||
data class Error (val code: String, val reason: String)
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
/*
|
||||
* Copyright (c) Kacper Ziubryniewicz 2019-12-8
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.api.v2.szkolny.response
|
||||
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.events.EventFull
|
||||
|
||||
data class ServerSyncResponse(val events: List<EventFull>)
|
@ -10,6 +10,7 @@ public class EventFull extends Event {
|
||||
public String subjectShortName = "";
|
||||
|
||||
public String teamName = "";
|
||||
public String teamCode = null;
|
||||
|
||||
// metadata
|
||||
public boolean seen;
|
||||
|
@ -223,13 +223,13 @@ class ProfileFull : Profile {
|
||||
}
|
||||
|
||||
fun loginStoreType(): String {
|
||||
when (loginStoreType) {
|
||||
LOGIN_TYPE_MOBIDZIENNIK -> return "LOGIN_TYPE_MOBIDZIENNIK"
|
||||
LOGIN_TYPE_LIBRUS -> return "LOGIN_TYPE_LIBRUS"
|
||||
LOGIN_TYPE_IUCZNIOWIE -> return "LOGIN_TYPE_IDZIENNIK"
|
||||
LOGIN_TYPE_VULCAN -> return "LOGIN_TYPE_VULCAN"
|
||||
LOGIN_TYPE_DEMO -> return "LOGIN_TYPE_DEMO"
|
||||
else -> return "LOGIN_TYPE_UNKNOWN"
|
||||
return when (loginStoreType) {
|
||||
LOGIN_TYPE_MOBIDZIENNIK -> "LOGIN_TYPE_MOBIDZIENNIK"
|
||||
LOGIN_TYPE_LIBRUS -> "LOGIN_TYPE_LIBRUS"
|
||||
LOGIN_TYPE_IUCZNIOWIE -> "LOGIN_TYPE_IDZIENNIK"
|
||||
LOGIN_TYPE_VULCAN -> "LOGIN_TYPE_VULCAN"
|
||||
LOGIN_TYPE_DEMO -> "LOGIN_TYPE_DEMO"
|
||||
else -> "LOGIN_TYPE_UNKNOWN"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,9 @@ public abstract class TeamDao {
|
||||
@Query("SELECT * FROM teams WHERE profileId = :profileId ORDER BY teamType, teamName ASC")
|
||||
public abstract List<Team> getAllNow(int profileId);
|
||||
|
||||
@Query("SELECT * FROM teams ORDER BY teamType, teamName ASC")
|
||||
public abstract List<Team> getAllNow();
|
||||
|
||||
@Query("SELECT * FROM teams WHERE profileId = :profileId AND teamType = 1")
|
||||
public abstract LiveData<Team> getClass(int profileId);
|
||||
|
||||
|
@ -103,6 +103,13 @@ public class Time implements Comparable<Time> {
|
||||
}
|
||||
}
|
||||
|
||||
public static Time fromValue(int value) {
|
||||
int hours = value / 10000;
|
||||
int minutes = (value - hours * 10000) / 100;
|
||||
int seconds = (value - hours * 10000 - minutes * 100);
|
||||
return new Time(hours, minutes, seconds);
|
||||
}
|
||||
|
||||
public long getInMillis() {
|
||||
Calendar c = Calendar.getInstance();
|
||||
c.set(2000, 0, 1, hour, minute, second);
|
||||
|
@ -47,7 +47,9 @@ buildscript {
|
||||
|
||||
navlib : "8b31921697",
|
||||
|
||||
gifdrawable : "1.2.15"
|
||||
gifdrawable : "1.2.15",
|
||||
|
||||
retrofit : '2.6.2'
|
||||
]
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user