[APIv2] Finalize the basic API service. Add notification. Add exported broadcast receiver.

This commit is contained in:
Kuba Szczodrzyński 2019-10-01 21:27:09 +02:00
parent 92880d40cf
commit 4c6b467847
21 changed files with 615 additions and 124 deletions

View File

@ -218,6 +218,13 @@
android:name=".sync.SyncService"
android:icon="@mipmap/ic_launcher"
android:label="@string/sync_service" />
<receiver
android:name=".receivers.SzkolnyReceiver"
android:exported="true">
<intent-filter>
<action android:name="pl.szczodrzynski.edziennik.SZKOLNY_MAIN" />
</intent-filter>
</receiver>
<service android:name=".api.v2.ApiService" />
</application>

View File

@ -71,7 +71,7 @@ public class Notifier {
notificationColor = ContextCompat.getColor(app.getContext(), R.color.colorPrimary);
notificationManager = (NotificationManager) app.getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channelGetData = new NotificationChannel(GROUP_KEY_GET_DATA, CHANNEL_GET_DATA_NAME, NotificationManager.IMPORTANCE_LOW);
NotificationChannel channelGetData = new NotificationChannel(GROUP_KEY_GET_DATA, CHANNEL_GET_DATA_NAME, NotificationManager.IMPORTANCE_MIN);
channelGetData.setDescription(CHANNEL_GET_DATA_DESC);
notificationManager.createNotificationChannel(channelGetData);

View File

@ -14,12 +14,10 @@ import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.api.v2.events.requests.MessageGetRequest
import pl.szczodrzynski.edziennik.api.v2.events.SyncProgressEvent
import pl.szczodrzynski.edziennik.api.v2.events.requests.SyncProfileRequest
import pl.szczodrzynski.edziennik.api.v2.events.requests.SyncRequest
import pl.szczodrzynski.edziennik.api.v2.events.requests.SyncViewRequest
import pl.szczodrzynski.edziennik.api.v2.events.*
import pl.szczodrzynski.edziennik.api.v2.events.requests.*
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikInterface
import pl.szczodrzynski.edziennik.api.v2.librus.Librus
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.api.v2.models.ApiTask
@ -37,46 +35,99 @@ class ApiService : Service() {
private val taskQueue = mutableListOf<ApiTask>()
private val errorList = mutableListOf<ApiError>()
private var queueHasErrorReportTask = false
private var taskCancelled = false
private var taskRunning = false
private var taskRunningId = -1
private var taskMaximumId = 0
private var edziennikInterface: EdziennikInterface? = null
private var taskProfileId = -1
private var taskProfileName: String? = null
private var taskProgress = 0
private var taskProgressRes: Int? = null
private val notification by lazy { EdziennikNotification(this) }
/* ______ _ _ _ _ _____ _ _ _ _
| ____| | | (_) (_) | / ____| | | | | | |
| |__ __| |_____ ___ _ __ _ __ _| | __ | | __ _| | | |__ __ _ ___| | __
| __| / _` |_ / |/ _ \ '_ \| '_ \| | |/ / | | / _` | | | '_ \ / _` |/ __| |/ /
| |___| (_| |/ /| | __/ | | | | | | | < | |___| (_| | | | |_) | (_| | (__| <
|______\__,_/___|_|\___|_| |_|_| |_|_|_|\_\ \_____\__,_|_|_|_.__/ \__,_|\___|_|\*/
private val taskCallback = object : EdziennikCallback {
override fun onCompleted() {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
edziennikInterface = null
if (!taskCancelled) {
EventBus.getDefault().post(SyncProfileFinishedEvent(taskProfileId))
}
notification.setIdle().post()
taskRunning = false
taskRunningId = -1
sync()
}
override fun onError(apiError: ApiError) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
if (!queueHasErrorReportTask) {
queueHasErrorReportTask = true
taskQueue += ErrorReportTask().apply {
taskId = ++taskMaximumId
}
}
EventBus.getDefault().post(SyncErrorEvent(apiError))
errorList.add(apiError)
if (apiError.isCritical) {
notification.setCriticalError().post()
taskRunning = false
taskRunningId = -1
sync()
}
else {
notification.addError().post()
}
}
override fun onProgress(step: Int) {
taskProgress += step
taskProgress = min(100, taskProgress)
EventBus.getDefault().post(SyncProgressEvent(taskProfileId, taskProfileName, taskProgress, taskProgressRes))
notification.setProgress(taskProgress).post()
}
override fun onStartProgress(stringRes: Int) {
taskProgressRes = stringRes
EventBus.getDefault().post(SyncProgressEvent(taskProfileId, taskProfileName, taskProgress, taskProgressRes))
notification.setProgressRes(taskProgressRes!!).post()
}
}
/* _______ _ _ _
|__ __| | | | | (_)
| | __ _ ___| | __ _____ _____ ___ _ _| |_ _ ___ _ __
| |/ _` / __| |/ / / _ \ \/ / _ \/ __| | | | __| |/ _ \| '_ \
| | (_| \__ \ < | __/> < __/ (__| |_| | |_| | (_) | | | |
|_|\__,_|___/_|\_\ \___/_/\_\___|\___|\__,_|\__|_|\___/|_| |*/
private fun sync() {
if (taskRunning)
return
if (taskQueue.size <= 0)
return // TODO stopSelf() or sth
if (taskQueue.size <= 0) {
allCompleted()
return
}
val task = taskQueue.removeAt(0)
taskCancelled = false
taskRunning = true
taskRunningId = task.taskId
if (task is ErrorReportTask) {
notification
.setCurrentTask(taskRunningId, null)
.setProgressRes(R.string.edziennik_notification_api_error_report_title)
return
}
// get the requested profile and login store
val profile: Profile? = app.db.profileDao().getByIdNow(task.profileId)
if (profile == null || !profile.syncEnabled) {
@ -92,8 +143,10 @@ class ApiService : Service() {
taskProgress = 0
taskProgressRes = null
// update the notification
notification.setCurrentTask(taskRunningId, taskProfileName).post()
val edziennikInterface = when (loginStore.type) {
edziennikInterface = when (loginStore.type) {
LOGIN_TYPE_LIBRUS -> Librus(app, profile, loginStore, taskCallback)
else -> null
}
@ -102,17 +155,29 @@ class ApiService : Service() {
}
when (task) {
is SyncProfileRequest -> edziennikInterface.sync(task.featureIds ?: Features.getAllIds())
is SyncViewRequest -> edziennikInterface.sync(Features.getIdsByView(task.targetId))
is MessageGetRequest -> edziennikInterface.getMessage(task.messageId)
is SyncProfileRequest -> edziennikInterface?.sync(task.featureIds ?: Features.getAllIds())
is SyncViewRequest -> edziennikInterface?.sync(Features.getIdsByView(task.targetId))
is MessageGetRequest -> edziennikInterface?.getMessage(task.messageId)
}
}
private fun allCompleted() {
EventBus.getDefault().post(SyncFinishedEvent())
stopSelf()
}
/* ______ _ ____
| ____| | | | _ \
| |____ _____ _ __ | |_| |_) |_ _ ___
| __\ \ / / _ \ '_ \| __| _ <| | | / __|
| |___\ V / __/ | | | |_| |_) | |_| \__ \
|______\_/ \___|_| |_|\__|____/ \__,_|__*/
@Subscribe(threadMode = ThreadMode.ASYNC)
fun onSyncRequest(syncRequest: SyncRequest) {
app.db.profileDao().idsForSyncNow.forEach { id ->
taskQueue += SyncProfileRequest(id, null)
taskQueue += SyncProfileRequest(id, null).apply {
taskId = ++taskMaximumId
}
}
sync()
}
@ -120,31 +185,49 @@ class ApiService : Service() {
@Subscribe(threadMode = ThreadMode.ASYNC)
fun onSyncProfileRequest(syncProfileRequest: SyncProfileRequest) {
Log.d(TAG, syncProfileRequest.toString())
taskQueue += syncProfileRequest
taskQueue += syncProfileRequest.apply {
taskId = ++taskMaximumId
}
sync()
}
@Subscribe(threadMode = ThreadMode.ASYNC)
fun onSyncViewRequest(syncViewRequest: SyncViewRequest) {
Log.d(TAG, syncViewRequest.toString())
taskQueue += syncViewRequest.apply {
taskId = ++taskMaximumId
}
sync()
}
@Subscribe(threadMode = ThreadMode.ASYNC)
fun onMessageGetRequest(messageGetRequest: MessageGetRequest) {
Log.d(TAG, messageGetRequest.toString())
taskQueue += messageGetRequest
taskQueue += messageGetRequest.apply {
taskId = ++taskMaximumId
}
sync()
}
private val notification by lazy {
NotificationCompat.Builder(this, NOTIFICATION_API_CHANNEL_ID)
.setContentTitle("API")
.setContentText("API is running")
.setSmallIcon(R.drawable.ic_notification)
.build()
@Subscribe(threadMode = ThreadMode.ASYNC)
fun onTaskCancelRequest(taskCancelRequest: TaskCancelRequest) {
taskCancelled = true
edziennikInterface?.cancel()
}
/* _____ _ _ _
/ ____| (_) (_) | |
| (___ ___ _ ____ ___ ___ ___ _____ _____ _ __ _ __ _ __| | ___ ___
\___ \ / _ \ '__\ \ / / |/ __/ _ \ / _ \ \ / / _ \ '__| '__| |/ _` |/ _ \/ __|
____) | __/ | \ V /| | (_| __/ | (_) \ V / __/ | | | | | (_| | __/\__ \
|_____/ \___|_| \_/ |_|\___\___| \___/ \_/ \___|_| |_| |_|\__,_|\___||__*/
override fun onCreate() {
EventBus.getDefault().register(this)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
startForeground(1, notification)
startForeground(EdziennikNotification.NOTIFICATION_ID, notification.notification)
notification.setIdle().setCloseAction().post()
return START_NOT_STICKY
}

View File

@ -0,0 +1,140 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-1.
*/
package pl.szczodrzynski.edziennik.api.v2
import android.app.Notification
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationCompat.PRIORITY_LOW
import androidx.core.app.NotificationCompat.PRIORITY_MIN
import pl.szczodrzynski.edziennik.R
class EdziennikNotification(val context: Context) {
companion object {
const val NOTIFICATION_ID = 20191001
}
private val notificationManager by lazy { context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager }
private val notificationBuilder: NotificationCompat.Builder by lazy {
NotificationCompat.Builder(context, ApiService.NOTIFICATION_API_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setPriority(PRIORITY_MIN)
.setOngoing(true)
.setLocalOnly(true)
}
val notification: Notification
get() = notificationBuilder.build()
private var errorCount = 0
private var criticalErrorCount = 0
private fun cancelPendingIntent(taskId: Int): PendingIntent {
val intent = Intent("pl.szczodrzynski.edziennik.SZKOLNY_MAIN")
intent.putExtra("task", "TaskCancelRequest")
intent.putExtra("taskId", taskId)
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT) as PendingIntent
}
private val closePendingIntent: PendingIntent
get() {
val intent = Intent("pl.szczodrzynski.edziennik.SZKOLNY_MAIN")
intent.putExtra("task", "ServiceCloseRequest")
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT) as PendingIntent
}
private fun errorCountText(): String? {
var result = ""
if (criticalErrorCount > 0) {
result += context.resources.getQuantityString(R.plurals.critical_errors_format, criticalErrorCount, criticalErrorCount)
}
if (criticalErrorCount > 0 && errorCount > 0) {
result += ", "
}
if (errorCount > 0) {
result += context.resources.getQuantityString(R.plurals.normal_errors_format, errorCount, errorCount)
}
return if (result.isEmpty()) null else result
}
fun setIdle(): EdziennikNotification {
notificationBuilder.setContentTitle(context.getString(R.string.edziennik_notification_api_title))
notificationBuilder.setProgress(0, 0, false)
notificationBuilder.apply {
val str = context.getString(R.string.edziennik_notification_api_text)
setStyle(NotificationCompat.BigTextStyle().bigText(str))
setContentText(str)
}
setCloseAction()
return this
}
fun addError(): EdziennikNotification {
errorCount++
return this
}
fun setCriticalError(): EdziennikNotification {
criticalErrorCount++
notificationBuilder.setContentTitle(context.getString(R.string.edziennik_notification_api_error_title))
notificationBuilder.setProgress(0, 0, false)
notificationBuilder.apply {
val str = errorCountText()
setStyle(NotificationCompat.BigTextStyle().bigText(str))
setContentText(str)
}
setCloseAction()
return this
}
fun setProgress(progress: Int): EdziennikNotification {
notificationBuilder.setProgress(100, progress, false)
return this
}
fun setProgressRes(progressRes: Int): EdziennikNotification {
notificationBuilder.setContentTitle(context.getString(progressRes))
return this
}
fun setCurrentTask(taskId: Int, profileName: String?): EdziennikNotification {
notificationBuilder.setProgress(100, 0, false)
notificationBuilder.setContentTitle(context.getString(R.string.edziennik_notification_api_sync_title_format, profileName))
notificationBuilder.apply {
val str = errorCountText()
setStyle(NotificationCompat.BigTextStyle().bigText(str))
setContentText(str)
}
setCancelAction(taskId)
return this
}
fun setCloseAction(): EdziennikNotification {
notificationBuilder.mActions.clear()
notificationBuilder.addAction(
NotificationCompat.Action(
R.drawable.ic_notification,
context.getString(R.string.edziennik_notification_api_close),
closePendingIntent
))
return this
}
private fun setCancelAction(taskId: Int) {
notificationBuilder.mActions.clear()
notificationBuilder.addAction(
NotificationCompat.Action(
R.drawable.ic_notification,
context.getString(R.string.edziennik_notification_api_cancel),
cancelPendingIntent(taskId)
))
}
fun post() {
notificationManager.notify(NOTIFICATION_ID, notification)
}
}

View File

@ -6,47 +6,47 @@ package pl.szczodrzynski.edziennik.api.v2
import pl.szczodrzynski.edziennik.api.v2.models.Endpoint
const val ENDPOINT_LIBRUS_API_ME = 101
const val ENDPOINT_LIBRUS_API_SCHOOLS = 102
const val ENDPOINT_LIBRUS_API_CLASSES = 103
const val ENDPOINT_LIBRUS_API_VIRTUAL_CLASSES = 104
const val ENDPOINT_LIBRUS_API_UNITS = 105
const val ENDPOINT_LIBRUS_API_USERS = 106
const val ENDPOINT_LIBRUS_API_SUBJECTS = 107
const val ENDPOINT_LIBRUS_API_CLASSROOMS = 108
const val ENDPOINT_LIBRUS_API_TIMETABLES = 109
const val ENDPOINT_LIBRUS_API_SUBSTITUTIONS = 110
const val ENDPOINT_LIBRUS_API_NORMAL_GC = 111
const val ENDPOINT_LIBRUS_API_POINT_GC = 112
const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_GC = 113
const val ENDPOINT_LIBRUS_API_TEXT_GC = 114
const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GC = 115
const val ENDPOINT_LIBRUS_API_BEHAVIOUR_GC = 116
const val ENDPOINT_LIBRUS_API_NORMAL_GRADES = 117
const val ENDPOINT_LIBRUS_API_POINT_GRADES = 118
const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES = 119
const val ENDPOINT_LIBRUS_API_TEXT_GRADES = 120
const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GRADES = 121
const val ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADES = 122
const val ENDPOINT_LIBRUS_API_EVENTS = 123
const val ENDPOINT_LIBRUS_API_EVENT_TYPES = 124
const val ENDPOINT_LIBRUS_API_HOMEWORK = 125
const val ENDPOINT_LIBRUS_API_LUCKY_NUMBER = 126
const val ENDPOINT_LIBRUS_API_NOTICES = 127
const val ENDPOINT_LIBRUS_API_ATTENDANCE_TYPES = 128
const val ENDPOINT_LIBRUS_API_ATTENDANCE = 129
const val ENDPOINT_LIBRUS_API_ANNOUNCEMENTS = 130
const val ENDPOINT_LIBRUS_API_PT_MEETINGS = 131
const val ENDPOINT_LIBRUS_API_TEACHER_FREE_DAYS = 132
const val ENDPOINT_LIBRUS_API_SCHOOL_FREE_DAYS = 133
const val ENDPOINT_LIBRUS_API_CLASS_FREE_DAYS = 134
const val ENDPOINT_LIBRUS_SYNERGIA_INFO = 201
const val ENDPOINT_LIBRUS_SYNERGIA_GRADES = 202
const val ENDPOINT_LIBRUS_MESSAGES_RECEIVED = 301
const val ENDPOINT_LIBRUS_MESSAGES_SENT = 302
const val ENDPOINT_LIBRUS_MESSAGES_TRASH = 303
const val ENDPOINT_LIBRUS_MESSAGES_RECEIVERS = 304
const val ENDPOINT_LIBRUS_MESSAGES_GET = 304
const val ENDPOINT_LIBRUS_API_ME = 1001
const val ENDPOINT_LIBRUS_API_SCHOOLS = 1002
const val ENDPOINT_LIBRUS_API_CLASSES = 1003
const val ENDPOINT_LIBRUS_API_VIRTUAL_CLASSES = 1004
const val ENDPOINT_LIBRUS_API_UNITS = 1005
const val ENDPOINT_LIBRUS_API_USERS = 1006
const val ENDPOINT_LIBRUS_API_SUBJECTS = 1007
const val ENDPOINT_LIBRUS_API_CLASSROOMS = 1008
const val ENDPOINT_LIBRUS_API_TIMETABLES = 1015
const val ENDPOINT_LIBRUS_API_SUBSTITUTIONS = 1016
const val ENDPOINT_LIBRUS_API_NORMAL_GC = 1021
const val ENDPOINT_LIBRUS_API_POINT_GC = 1022
const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_GC = 1023
const val ENDPOINT_LIBRUS_API_TEXT_GC = 1024
const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GC = 1025
const val ENDPOINT_LIBRUS_API_BEHAVIOUR_GC = 1026
const val ENDPOINT_LIBRUS_API_NORMAL_GRADES = 1031
const val ENDPOINT_LIBRUS_API_POINT_GRADES = 1032
const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES = 1033
const val ENDPOINT_LIBRUS_API_TEXT_GRADES = 1034
const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GRADES = 1035
const val ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADES = 1036
const val ENDPOINT_LIBRUS_API_EVENTS = 1040
const val ENDPOINT_LIBRUS_API_EVENT_TYPES = 1041
const val ENDPOINT_LIBRUS_API_HOMEWORK = 1050
const val ENDPOINT_LIBRUS_API_LUCKY_NUMBER = 1060
const val ENDPOINT_LIBRUS_API_NOTICES = 1070
const val ENDPOINT_LIBRUS_API_ATTENDANCE_TYPES = 1080
const val ENDPOINT_LIBRUS_API_ATTENDANCE = 1081
const val ENDPOINT_LIBRUS_API_ANNOUNCEMENTS = 1090
const val ENDPOINT_LIBRUS_API_PT_MEETINGS = 1100
const val ENDPOINT_LIBRUS_API_TEACHER_FREE_DAYS = 1110
const val ENDPOINT_LIBRUS_API_SCHOOL_FREE_DAYS = 1120
const val ENDPOINT_LIBRUS_API_CLASS_FREE_DAYS = 1130
const val ENDPOINT_LIBRUS_SYNERGIA_INFO = 2010
const val ENDPOINT_LIBRUS_SYNERGIA_GRADES = 2020
const val ENDPOINT_LIBRUS_MESSAGES_RECEIVED = 3010
const val ENDPOINT_LIBRUS_MESSAGES_SENT = 3020
const val ENDPOINT_LIBRUS_MESSAGES_TRASH = 3030
const val ENDPOINT_LIBRUS_MESSAGES_RECEIVERS = 3040
const val ENDPOINT_LIBRUS_MESSAGES_GET = 3040
val endpoints = listOf(

View File

@ -0,0 +1,9 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-1.
*/
package pl.szczodrzynski.edziennik.api.v2.events
import pl.szczodrzynski.edziennik.api.v2.models.ApiTask
class ErrorReportTask : ApiTask(-1)

View File

@ -0,0 +1,7 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-1.
*/
package pl.szczodrzynski.edziennik.api.v2.events.requests
class ServiceCloseRequest

View File

@ -4,4 +4,4 @@
package pl.szczodrzynski.edziennik.api.v2.events.requests
class SyncRequest()
class SyncRequest

View File

@ -0,0 +1,7 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-1.
*/
package pl.szczodrzynski.edziennik.api.v2.events.requests
class TaskCancelRequest(val taskId: Int)

View File

@ -7,4 +7,5 @@ package pl.szczodrzynski.edziennik.api.v2.interfaces
interface EdziennikInterface {
fun sync(featureIds: List<Int>)
fun getMessage(messageId: Int)
fun cancel()
}

View File

@ -6,25 +6,26 @@ package pl.szczodrzynski.edziennik.api.v2.librus
import android.util.Log
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.v2.CODE_INTERNAL_LIBRUS_ACCOUNT_410
import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_NOT_NEEDED
import pl.szczodrzynski.edziennik.api.v2.endpoints
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikInterface
import pl.szczodrzynski.edziennik.api.v2.librus.data.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librusLoginMethods
import pl.szczodrzynski.edziennik.api.v2.librus.login.*
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.api.v2.models.Endpoint
import pl.szczodrzynski.edziennik.datamodels.LoginStore
import pl.szczodrzynski.edziennik.datamodels.Profile
import pl.szczodrzynski.edziennik.utils.Utils.d
class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: EdziennikCallback) : EdziennikInterface {
companion object {
const val TAG = "Librus"
private const val TAG = "Librus"
}
val internalErrorList = mutableListOf<Int>()
val data: DataLibrus
private var cancelled = false
init {
data = DataLibrus(app, profile, loginStore).apply {
@ -33,6 +34,19 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va
data.satisfyLoginMethods()
}
private fun completed() {
data.saveData()
callback.onCompleted()
}
/* _______ _ _ _ _ _
|__ __| | /\ | | (_) | | |
| | | |__ ___ / \ | | __ _ ___ _ __ _| |_| |__ _ __ ___
| | | '_ \ / _ \ / /\ \ | |/ _` |/ _ \| '__| | __| '_ \| '_ ` _ \
| | | | | | __/ / ____ \| | (_| | (_) | | | | |_| | | | | | | | |
|_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_|
__/ |
|__*/
override fun sync(featureIds: List<Int>) {
val possibleLoginMethods = data.loginMethods.toMutableList()
@ -45,8 +59,8 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va
var endpointList = mutableListOf<Endpoint>()
val requiredLoginMethods = mutableListOf<Int>()
var targetEndpointIds = mutableListOf<Int>()
var targetLoginMethodIds = mutableListOf<Int>()
data.targetEndpointIds.clear()
data.targetLoginMethodIds.clear()
// get all endpoints for every feature, only if possible to login
for (featureId in featureIds) {
@ -72,7 +86,7 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va
.toMutableList()
// add all endpoint IDs and required login methods
.onEach { endpoint ->
targetEndpointIds.addAll(endpoint.endpointIds)
data.targetEndpointIds.addAll(endpoint.endpointIds)
requiredLoginMethods.addAll(endpoint.requiredLoginMethods)
}
@ -82,64 +96,38 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va
while (requiredLoginMethod != LOGIN_METHOD_NOT_NEEDED) {
librusLoginMethods.singleOrNull { it.loginMethodId == requiredLoginMethod }?.let { loginMethod ->
if (requiredLoginMethod != null)
targetLoginMethodIds.add(requiredLoginMethod!!)
data.targetLoginMethodIds.add(requiredLoginMethod!!)
requiredLoginMethod = loginMethod.requiredLoginMethod(data.profile, data.loginStore)
}
}
}
// sort and distinct every login method and endpoint
targetLoginMethodIds = targetLoginMethodIds.toHashSet().toMutableList()
targetLoginMethodIds.sort()
data.targetLoginMethodIds = data.targetLoginMethodIds.toHashSet().toMutableList()
data.targetLoginMethodIds.sort()
targetEndpointIds = targetEndpointIds.toHashSet().toMutableList()
targetLoginMethodIds.sort()
data.targetEndpointIds = data.targetEndpointIds.toHashSet().toMutableList()
data.targetEndpointIds.sort()
Log.d(TAG, "LoginMethod IDs: ${data.targetLoginMethodIds}")
Log.d(TAG, "Endpoint IDs: ${data.targetEndpointIds}")
//Log.d(TAG, endpointList.toString())
Log.d(TAG, "LoginMethod IDs: $targetLoginMethodIds")
Log.d(TAG, "Endpoint IDs: $targetEndpointIds")
/*
INPUT: [
FEATURE_GRADES,
FEATURE_STUDENT_INFO,
FEATURE_STUDENT_NUMBER
]
OUTPUT: [
Endpoint(loginType=2,
featureId=FEATURE_GRADES, endpointIds=[
ENDPOINT_LIBRUS_API_NORMAL_GC,
ENDPOINT_LIBRUS_API_NORMAL_GRADES,
ENDPOINT_LIBRUS_SYNERGIA_GRADES
], requiredLoginMethods=[
LOGIN_METHOD_LIBRUS_API,
LOGIN_METHOD_LIBRUS_SYNERGIA
]),
Endpoint(loginType=2,
featureId=FEATURE_STUDENT_INFO, endpointIds=[
ENDPOINT_LIBRUS_API_ME
], requiredLoginMethods=[
LOGIN_METHOD_LIBRUS_API
]),
Endpoint(loginType=2,
featureId=FEATURE_STUDENT_NUMBER, endpointIds=[
ENDPOINT_LIBRUS_SYNERGIA_INFO
], requiredLoginMethods=[
LOGIN_METHOD_LIBRUS_SYNERGIA
])
]
*/
LibrusLogin(data) {
LibrusEndpoints(data) {
completed()
}
}
}
override fun getMessage(messageId: Int) {
}
override fun cancel() {
d(TAG, "Cancelled")
cancelled = true
}
private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback {
return object : EdziennikCallback {
override fun onCompleted() {

View File

@ -0,0 +1,52 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-1.
*/
package pl.szczodrzynski.edziennik.api.v2.librus
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.librus.data.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApiMe
import pl.szczodrzynski.edziennik.api.v2.librus.login.LoginLibrusApi
import pl.szczodrzynski.edziennik.api.v2.librus.login.LoginLibrusMessages
import pl.szczodrzynski.edziennik.api.v2.librus.login.LoginLibrusPortal
import pl.szczodrzynski.edziennik.api.v2.librus.login.LoginLibrusSynergia
import pl.szczodrzynski.edziennik.utils.Utils
class LibrusEndpoints(val data: DataLibrus, val onSuccess: () -> Unit) {
companion object {
private const val TAG = "LibrusEndpoints"
}
private var cancelled = false
init {
nextEndpoint(onSuccess)
}
private fun nextEndpoint(onSuccess: () -> Unit) {
if (data.targetEndpointIds.isEmpty()) {
onSuccess()
return
}
useEndpoint(data.targetEndpointIds.removeAt(0)) {
if (cancelled) {
onSuccess()
return@useEndpoint
}
nextEndpoint(onSuccess)
}
}
private fun useEndpoint(endpointId: Int, onSuccess: () -> Unit) {
Utils.d(TAG, "Using endpoint $endpointId")
when (endpointId) {
ENDPOINT_LIBRUS_API_ME -> {
data.startProgress(R.string.edziennik_progress_endpoint_student_info)
LibrusApiMe(data) { onSuccess() }
}
else -> onSuccess()
}
}
}

View File

@ -0,0 +1,72 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-1.
*/
package pl.szczodrzynski.edziennik.api.v2.librus
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_LIBRUS_API
import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_LIBRUS_MESSAGES
import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_LIBRUS_PORTAL
import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_LIBRUS_SYNERGIA
import pl.szczodrzynski.edziennik.api.v2.librus.data.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.login.LoginLibrusApi
import pl.szczodrzynski.edziennik.api.v2.librus.login.LoginLibrusMessages
import pl.szczodrzynski.edziennik.api.v2.librus.login.LoginLibrusPortal
import pl.szczodrzynski.edziennik.api.v2.librus.login.LoginLibrusSynergia
import pl.szczodrzynski.edziennik.utils.Utils
class LibrusLogin(val data: DataLibrus, val onSuccess: () -> Unit) {
companion object {
private const val TAG = "LibrusLogin"
}
private var cancelled = false
init {
nextLoginMethod(onSuccess)
}
private fun nextLoginMethod(onSuccess: () -> Unit) {
if (data.targetLoginMethodIds.isEmpty()) {
onSuccess()
return
}
useLoginMethod(data.targetLoginMethodIds.removeAt(0)) { usedMethodId ->
if (usedMethodId != -1)
data.loginMethods.add(usedMethodId)
if (cancelled) {
onSuccess()
return@useLoginMethod
}
nextLoginMethod(onSuccess)
}
}
private fun useLoginMethod(loginMethodId: Int, onSuccess: (usedMethodId: Int) -> Unit) {
// this should never be true
if (data.loginMethods.contains(loginMethodId)) {
onSuccess(-1)
return
}
Utils.d(TAG, "Using login method $loginMethodId")
when (loginMethodId) {
LOGIN_METHOD_LIBRUS_PORTAL -> {
data.startProgress(R.string.edziennik_progress_login_librus_portal)
LoginLibrusPortal(data) { onSuccess(loginMethodId) }
}
LOGIN_METHOD_LIBRUS_API -> {
data.startProgress(R.string.edziennik_progress_login_librus_api)
LoginLibrusApi(data) { onSuccess(loginMethodId) }
}
LOGIN_METHOD_LIBRUS_SYNERGIA -> {
data.startProgress(R.string.edziennik_progress_login_librus_synergia)
LoginLibrusSynergia(data) { onSuccess(loginMethodId) }
}
LOGIN_METHOD_LIBRUS_MESSAGES -> {
data.startProgress(R.string.edziennik_progress_login_librus_messages)
LoginLibrusMessages(data) { onSuccess(loginMethodId) }
}
}
}
}

View File

@ -49,14 +49,18 @@ class LibrusTest(val app: App) {
fun go() {
Librus(app, profile, loginStore, object : EdziennikCallback {
/*Librus(app, profile, loginStore, object : EdziennikCallback {
override fun onCompleted() {}
override fun onError(apiError: ApiError) {}
override fun onProgress(step: Int) {}
override fun onStartProgress(stringRes: Int) {}
}).sync(listOf(FEATURE_GRADES, FEATURE_STUDENT_INFO, FEATURE_STUDENT_NUMBER))
}).sync(listOf(FEATURE_GRADES, FEATURE_STUDENT_INFO, FEATURE_STUDENT_NUMBER))*/
//app.startService(Intent(app, ApiService::class.java))
app.startService(Intent(app, ApiService::class.java))
if (false) {
}
/*val data = DataLibrus(app, profile, loginStore).apply {
callback = object : ProgressCallback {

View File

@ -14,6 +14,7 @@ class ApiError(val tag: String, val errorCode: Int) {
private var apiResponse: String? = null
private var request: Request? = null
private var response: Response? = null
var isCritical = true
fun withThrowable(throwable: Throwable?): ApiError {
this.throwable = throwable
@ -36,4 +37,9 @@ class ApiError(val tag: String, val errorCode: Int) {
this.request = response?.request()
return this
}
fun setCritical(isCritical: Boolean): ApiError {
this.isCritical = isCritical
return this
}
}

View File

@ -38,10 +38,26 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
* A method which may be overridden in child Data* classes.
*
* Calling it should populate [loginMethods] with all
* already available login methods (e.g. non-expired OAuth token).
* already available login methods (e.g. a non-expired OAuth token).
*/
open fun satisfyLoginMethods() {}
/**
* A list of Login method IDs that are still pending
* to run.
*/
var targetLoginMethodIds = mutableListOf<Int>()
/**
* A list of endpoint IDs that are still pending
* to run.
*/
var targetEndpointIds = mutableListOf<Int>()
/**
* A map of endpoint IDs to JSON objects, specifying their arguments bundle.
*/
var endpointArgs = mutableMapOf<Int, JsonObject>()
val teacherList = LongSparseArray<Teacher>()
val subjectList = LongSparseArray<Subject>()
val teamList = mutableListOf<Team>()
@ -106,6 +122,9 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
if (profile == null)
return
db.profileDao().add(profile)
db.loginStoreDao().add(loginStore)
if (teacherList.isNotEmpty()) {
val tempList: ArrayList<Teacher> = ArrayList()
teacherList.forEach { _, teacher ->

View File

@ -35,6 +35,8 @@ import com.mikepenz.iconics.IconicsDrawable;
import com.mikepenz.iconics.IconicsSize;
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial;
import org.greenrobot.eventbus.EventBus;
import java.util.ArrayList;
import java.util.List;
@ -44,6 +46,8 @@ import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.MainActivity;
import pl.szczodrzynski.edziennik.api.AppError;
import pl.szczodrzynski.edziennik.api.interfaces.SyncCallback;
import pl.szczodrzynski.edziennik.api.v2.events.requests.SyncProfileRequest;
import pl.szczodrzynski.edziennik.api.v2.events.requests.SyncViewRequest;
import pl.szczodrzynski.edziennik.api.v2.librus.LibrusOld;
import pl.szczodrzynski.edziennik.api.v2.librus.LibrusTest;
import pl.szczodrzynski.edziennik.databinding.CardLuckyNumberBinding;
@ -67,6 +71,8 @@ import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem;
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem;
import static pl.szczodrzynski.edziennik.App.UPDATES_ON_PLAY_STORE;
import static pl.szczodrzynski.edziennik.MainActivity.DRAWER_ITEM_GRADES;
import static pl.szczodrzynski.edziennik.api.v2.FeaturesKt.FEATURE_STUDENT_INFO;
import static pl.szczodrzynski.edziennik.datamodels.Grade.TYPE_SEMESTER1_FINAL;
import static pl.szczodrzynski.edziennik.datamodels.Grade.TYPE_SEMESTER1_PROPOSED;
import static pl.szczodrzynski.edziennik.datamodels.Grade.TYPE_SEMESTER2_FINAL;
@ -128,6 +134,23 @@ public class HomeFragment extends Fragment {
test.go();
}));
b.test2.setVisibility(BuildConfig.DEBUG ? View.VISIBLE : View.GONE);
b.test2.setOnClickListener((v -> {
List<Integer> list = new ArrayList<>();
list.add(FEATURE_STUDENT_INFO);
EventBus.getDefault().post(new SyncProfileRequest(16, list));
}));
b.test3.setVisibility(BuildConfig.DEBUG ? View.VISIBLE : View.GONE);
b.test3.setOnClickListener((v -> {
EventBus.getDefault().post(new SyncProfileRequest(16, null));
}));
b.test4.setVisibility(BuildConfig.DEBUG ? View.VISIBLE : View.GONE);
b.test4.setOnClickListener((v -> {
EventBus.getDefault().post(new SyncViewRequest(16, DRAWER_ITEM_GRADES));
}));
//((TextView)v.findViewById(R.id.nextSync)).setText(getString(R.string.next_sync_format,Time.fromMillis(app.appJobs.syncJobTime).getStringHMS()));
@ -559,7 +582,7 @@ public class HomeFragment extends Fragment {
Button cardGradesButton = root.findViewById(R.id.cardGradesButton);
buttonAddDrawable(c, cardGradesButton, CommunityMaterial.Icon.cmd_arrow_right);
cardGradesButton.setOnClickListener((v1 -> new Handler().postDelayed(() -> a.runOnUiThread(() -> {
activity.loadTarget(MainActivity.DRAWER_ITEM_GRADES, null);
activity.loadTarget(DRAWER_ITEM_GRADES, null);
}), 100)));
//new Handler().postDelayed(() -> a.runOnUiThread(() -> updateCardGrades(c, a, root)), newRefreshInterval);

View File

@ -0,0 +1,23 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-1.
*/
package pl.szczodrzynski.edziennik.receivers
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.api.v2.events.requests.ServiceCloseRequest
import pl.szczodrzynski.edziennik.api.v2.events.requests.SyncRequest
import pl.szczodrzynski.edziennik.api.v2.events.requests.TaskCancelRequest
class SzkolnyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.extras?.getString("task", null)) {
"ServiceCloseRequest" -> EventBus.getDefault().post(ServiceCloseRequest())
"TaskCancelRequest" -> EventBus.getDefault().post(TaskCancelRequest(intent.extras?.getInt("taskId", -1) ?: return))
"SyncRequest" -> EventBus.getDefault().post(SyncRequest())
}
}
}

View File

@ -55,7 +55,34 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="Test Librus"
android:text="Start Service"
android:visibility="gone"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/test2"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="Sync profile 16 - feature me"
android:visibility="gone"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/test3"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="Sync profile 16 - feature all"
android:visibility="gone"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/test4"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="Sync profile 16 - view grades"
android:visibility="gone"/>
</LinearLayout>

View File

@ -67,4 +67,18 @@
<item quantity="few">%1$s - %2$d nieprzeczytane</item>
<item quantity="other">%1$s - %2$d nieprzeczytanych</item>
</plurals>
<plurals name="critical_errors_format">
<item quantity="one">%d błąd krytyczny</item>
<item quantity="few">%d błędy krytyczne</item>
<item quantity="many">%d błędów krytycznych</item>
<item quantity="other">%d błędów krytycznych</item>
</plurals>
<plurals name="normal_errors_format">
<item quantity="one">%d błąd</item>
<item quantity="few">%d błędy</item>
<item quantity="many">%d błędów</item>
<item quantity="other">%d błędów</item>
</plurals>
</resources>

View File

@ -912,4 +912,13 @@
<string name="edziennik_progress_login_librus_api">Logowanie do API</string>
<string name="edziennik_progress_login_librus_synergia">Logowanie do Librus Synergia</string>
<string name="edziennik_progress_login_librus_messages">Logowanie do wiadomości Librus</string>
<string name="edziennik_notification_api_title">Usługa synchronizacji</string>
<string name="edziennik_notification_api_text">Dzięki niej, aplikacja Szkolny.eu może synchronizować dane z e-dziennikiem. Możesz ją zamknąć, ponieważ w tej chwili nic nie robi.</string>
<string name="edziennik_notification_api_close">Zamknij</string>
<string name="edziennik_notification_api_cancel">Anuluj</string>
<string name="edziennik_notification_api_sync_title_format">Trwa synchronizacja profilu %s...</string>
<string name="edziennik_notification_api_error_title">Synchronizacja przerwana</string>
<string name="edziennik_notification_api_error_report_task">Zgłaszanie błędów...</string>
<string name="edziennik_notification_api_error_report_title">Zgłaszanie błędów</string>
<string name="edziennik_progress_endpoint_student_info">Pobieram informacje o uczniu...</string>
</resources>