mirror of
https://github.com/szkolny-eu/szkolny-android.git
synced 2025-01-31 13:48:20 +01:00
[API] Implement Librus Captcha. Refactor notification constants. Update empty account error as a dialog.
This commit is contained in:
parent
f5e1e9fdd9
commit
4ad826ebe8
@ -4,9 +4,6 @@
|
||||
|
||||
package pl.szczodrzynski.edziennik
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.ShortcutInfo
|
||||
@ -48,6 +45,8 @@ import pl.szczodrzynski.edziennik.sync.SyncWorker
|
||||
import pl.szczodrzynski.edziennik.sync.UpdateWorker
|
||||
import pl.szczodrzynski.edziennik.ui.modules.base.CrashActivity
|
||||
import pl.szczodrzynski.edziennik.utils.*
|
||||
import pl.szczodrzynski.edziennik.utils.managers.NotificationChannelsManager
|
||||
import pl.szczodrzynski.edziennik.utils.managers.UserActionManager
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
@ -63,25 +62,8 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
|
||||
var devMode = false
|
||||
}
|
||||
|
||||
val notifications by lazy { Notifications() }
|
||||
inner class Notifications {
|
||||
val syncId = 1
|
||||
val syncKey = "pl.szczodrzynski.edziennik.SYNC"
|
||||
val syncChannelName: String by lazy { getString(R.string.notification_channel_get_data_name) }
|
||||
val syncChannelDesc: String by lazy { getString(R.string.notification_channel_get_data_desc) }
|
||||
val dataId = 50
|
||||
val dataKey = "pl.szczodrzynski.edziennik.DATA"
|
||||
val dataChannelName: String by lazy { getString(R.string.notification_channel_notifications_name) }
|
||||
val dataChannelDesc: String by lazy { getString(R.string.notification_channel_notifications_desc) }
|
||||
val dataQuietId = 60
|
||||
val dataQuietKey = "pl.szczodrzynski.edziennik.DATA_QUIET"
|
||||
val dataQuietChannelName: String by lazy { getString(R.string.notification_channel_notifications_quiet_name) }
|
||||
val dataQuietChannelDesc: String by lazy { getString(R.string.notification_channel_notifications_quiet_desc) }
|
||||
val updatesId = 100
|
||||
val updatesKey = "pl.szczodrzynski.edziennik.UPDATES"
|
||||
val updatesChannelName: String by lazy { getString(R.string.notification_channel_updates_name) }
|
||||
val updatesChannelDesc: String by lazy { getString(R.string.notification_channel_updates_desc) }
|
||||
}
|
||||
val notificationChannelsManager by lazy { NotificationChannelsManager(this) }
|
||||
val userActionManager by lazy { UserActionManager(this) }
|
||||
|
||||
val db
|
||||
get() = App.db
|
||||
@ -99,7 +81,6 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
|
||||
.setMinimumLoggingLevel(Log.VERBOSE)
|
||||
.build()
|
||||
|
||||
val preferences by lazy { getSharedPreferences(getString(R.string.preference_file), Context.MODE_PRIVATE) }
|
||||
val permissionChecker by lazy { PermissionChecker(this) }
|
||||
val networkUtils by lazy { NetworkUtils(this) }
|
||||
val gson by lazy { Gson() }
|
||||
@ -251,29 +232,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
|
||||
)
|
||||
} // shortcuts - end
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
||||
notificationManager.createNotificationChannel(
|
||||
NotificationChannel(notifications.syncKey, notifications.syncChannelName, NotificationManager.IMPORTANCE_MIN).apply {
|
||||
description = notifications.syncChannelDesc
|
||||
})
|
||||
notificationManager.createNotificationChannel(
|
||||
NotificationChannel(notifications.dataKey, notifications.dataChannelName, NotificationManager.IMPORTANCE_HIGH).apply {
|
||||
description = notifications.dataChannelDesc
|
||||
enableLights(true)
|
||||
lightColor = 0xff2196f3.toInt()
|
||||
})
|
||||
notificationManager.createNotificationChannel(
|
||||
NotificationChannel(notifications.dataQuietKey, notifications.dataQuietChannelName, NotificationManager.IMPORTANCE_LOW).apply {
|
||||
description = notifications.dataQuietChannelDesc
|
||||
setSound(null, null)
|
||||
enableVibration(false)
|
||||
})
|
||||
notificationManager.createNotificationChannel(
|
||||
NotificationChannel(notifications.updatesKey, notifications.updatesChannelName, NotificationManager.IMPORTANCE_DEFAULT).apply {
|
||||
description = notifications.updatesChannelDesc
|
||||
})
|
||||
}
|
||||
notificationChannelsManager.registerAllChannels()
|
||||
|
||||
|
||||
if (config.appInstalledTime == 0L)
|
||||
|
@ -659,6 +659,10 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
||||
.setCancelable(false)
|
||||
.show()
|
||||
}
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onUserActionRequiredEvent(event: UserActionRequiredEvent) {
|
||||
app.userActionManager.execute(this, event.profileId, event.type)
|
||||
}
|
||||
|
||||
private fun fragmentToSyncName(currentFragment: Int): Int {
|
||||
return when (currentFragment) {
|
||||
@ -713,11 +717,20 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
||||
intentTargetId = TARGET_FEEDBACK
|
||||
false
|
||||
}
|
||||
"userActionRequired" -> {
|
||||
app.userActionManager.execute(
|
||||
this,
|
||||
extras.getInt("profileId"),
|
||||
extras.getInt("type")
|
||||
)
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
if (handled)
|
||||
if (handled && !navLoading) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (extras?.containsKey("reloadProfileId") == true) {
|
||||
val reloadProfileId = extras.getInt("reloadProfileId", -1)
|
||||
|
@ -86,9 +86,16 @@ class ApiService : Service() {
|
||||
lastEventTime = System.currentTimeMillis()
|
||||
d(TAG, "Task $taskRunningId threw an error - $apiError")
|
||||
apiError.profileId = taskProfileId
|
||||
|
||||
if (app.userActionManager.requiresUserAction(apiError)) {
|
||||
app.userActionManager.sendToUser(apiError)
|
||||
}
|
||||
else {
|
||||
EventBus.getDefault().postSticky(ApiTaskErrorEvent(apiError))
|
||||
errorList.add(apiError)
|
||||
apiError.throwable?.printStackTrace()
|
||||
}
|
||||
|
||||
if (apiError.isCritical) {
|
||||
taskRunning?.cancel()
|
||||
notification.setCriticalError().post()
|
||||
@ -301,7 +308,7 @@ class ApiService : Service() {
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
d(TAG, "Foreground service onStartCommand")
|
||||
startForeground(app.notifications.syncId, notification.notification)
|
||||
startForeground(app.notificationChannelsManager.sync.id, notification.notification)
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
|
@ -134,7 +134,7 @@ class EdziennikNotification(val app: App) {
|
||||
fun post() {
|
||||
if (serviceClosed)
|
||||
return
|
||||
notificationManager.notify(app.notifications.syncId, notification)
|
||||
notificationManager.notify(app.notificationChannelsManager.sync.id, notification)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -59,6 +59,9 @@ const val ERROR_FILE_DOWNLOAD = 113
|
||||
|
||||
const val ERROR_NO_STUDENTS_IN_ACCOUNT = 115
|
||||
|
||||
const val ERROR_CAPTCHA_NEEDED = 3000
|
||||
const val ERROR_CAPTCHA_LIBRUS_PORTAL = 3001
|
||||
|
||||
const val CODE_INTERNAL_LIBRUS_ACCOUNT_410 = 120
|
||||
const val CODE_INTERNAL_LIBRUS_SYNERGIA_EXPIRED = 121
|
||||
const val ERROR_LOGIN_LIBRUS_API_CAPTCHA_NEEDED = 124
|
||||
|
@ -33,9 +33,8 @@ class LibrusFirstLogin(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
val accounts = json.getJsonArray("accounts")
|
||||
|
||||
if (accounts == null || accounts.size() < 1) {
|
||||
data.error(ApiError(TAG, ERROR_NO_STUDENTS_IN_ACCOUNT)
|
||||
.withResponse(response)
|
||||
.withApiResponse(json))
|
||||
EventBus.getDefault().post(FirstLoginFinishedEvent(listOf(), data.loginStore))
|
||||
onSuccess()
|
||||
return@portalGet
|
||||
}
|
||||
val accountDataTime = json.getLong("lastModification")
|
||||
|
@ -7,12 +7,10 @@ import im.wangchao.mhttp.Response
|
||||
import im.wangchao.mhttp.body.MediaTypeUtils
|
||||
import im.wangchao.mhttp.callback.JsonCallbackHandler
|
||||
import im.wangchao.mhttp.callback.TextCallbackHandler
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.api.*
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus
|
||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.getInt
|
||||
import pl.szczodrzynski.edziennik.getString
|
||||
import pl.szczodrzynski.edziennik.getUnixDate
|
||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||
import java.net.HttpURLConnection.HTTP_UNAUTHORIZED
|
||||
import java.util.*
|
||||
@ -38,11 +36,21 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
onSuccess()
|
||||
}
|
||||
else if (data.portalRefreshToken != null) {
|
||||
data.app.cookieJar.clearForDomain("portal.librus.pl")
|
||||
accessToken(null, data.portalRefreshToken)
|
||||
if (data.fakeLogin) {
|
||||
data.app.cookieJar.clearForDomain("librus.szkolny.eu")
|
||||
}
|
||||
else {
|
||||
data.app.cookieJar.clearForDomain("portal.librus.pl")
|
||||
}
|
||||
accessToken(null, data.portalRefreshToken)
|
||||
}
|
||||
else {
|
||||
if (data.fakeLogin) {
|
||||
data.app.cookieJar.clearForDomain("librus.szkolny.eu")
|
||||
}
|
||||
else {
|
||||
data.app.cookieJar.clearForDomain("portal.librus.pl")
|
||||
}
|
||||
authorize(if (data.fakeLogin) FAKE_LIBRUS_AUTHORIZE else LIBRUS_AUTHORIZE_URL)
|
||||
}
|
||||
}}
|
||||
@ -89,11 +97,20 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
private fun login(csrfToken: String) {
|
||||
d(TAG, "Request: Librus/Login/Portal - ${if (data.fakeLogin) FAKE_LIBRUS_LOGIN else LIBRUS_LOGIN_URL}")
|
||||
|
||||
val recaptchaCode = data.arguments?.getString("recaptchaCode") ?: data.loginStore.getLoginData("recaptchaCode", null)
|
||||
val recaptchaTime = data.arguments?.getLong("recaptchaTime") ?: data.loginStore.getLoginData("recaptchaTime", 0L)
|
||||
data.loginStore.removeLoginData("recaptchaCode")
|
||||
data.loginStore.removeLoginData("recaptchaTime")
|
||||
|
||||
Request.builder()
|
||||
.url(if (data.fakeLogin) FAKE_LIBRUS_LOGIN else LIBRUS_LOGIN_URL)
|
||||
.userAgent(LIBRUS_USER_AGENT)
|
||||
.addParameter("email", data.portalEmail)
|
||||
.addParameter("password", data.portalPassword)
|
||||
.also {
|
||||
if (recaptchaCode != null && System.currentTimeMillis() - recaptchaTime < 2*60*1000 /* 2 minutes */)
|
||||
it.addParameter("g-recaptcha-response", recaptchaCode)
|
||||
}
|
||||
.addHeader("X-CSRF-TOKEN", csrfToken)
|
||||
.contentType(MediaTypeUtils.APPLICATION_JSON)
|
||||
.post()
|
||||
@ -117,6 +134,12 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
.withResponse(response))
|
||||
return
|
||||
}
|
||||
if (json.getBoolean("captchaRequired") == true) {
|
||||
data.error(ApiError(TAG, ERROR_CAPTCHA_LIBRUS_PORTAL)
|
||||
.withResponse(response)
|
||||
.withApiResponse(json))
|
||||
return
|
||||
}
|
||||
if (json.get("errors") != null) {
|
||||
data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_ACTION_ERROR)
|
||||
.withResponse(response)
|
||||
|
@ -6,14 +6,12 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.firstlogin
|
||||
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.api.ERROR_NO_STUDENTS_IN_ACCOUNT
|
||||
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_VULCAN
|
||||
import pl.szczodrzynski.edziennik.data.api.VULCAN_API_ENDPOINT_STUDENT_LIST
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanApi
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.VulcanLoginApi
|
||||
import pl.szczodrzynski.edziennik.data.api.events.FirstLoginFinishedEvent
|
||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
|
||||
@ -35,9 +33,8 @@ class VulcanFirstLogin(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
val students = json.getJsonArray("Data")
|
||||
|
||||
if (students == null || students.isEmpty()) {
|
||||
data.error(ApiError(TAG, ERROR_NO_STUDENTS_IN_ACCOUNT)
|
||||
.withResponse(response)
|
||||
.withApiResponse(json))
|
||||
EventBus.getDefault().post(FirstLoginFinishedEvent(listOf(), data.loginStore))
|
||||
onSuccess()
|
||||
return@apiGet
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-2-15.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.events
|
||||
|
||||
data class UserActionRequiredEvent(val profileId: Int, val type: Int) {
|
||||
companion object {
|
||||
const val LOGIN_DATA_MOBIDZIENNIK = 101
|
||||
const val LOGIN_DATA_LIBRUS = 102
|
||||
const val LOGIN_DATA_IDZIENNIK = 103
|
||||
const val LOGIN_DATA_VULCAN = 104
|
||||
const val LOGIN_DATA_EDUDZIENNIK = 105
|
||||
const val CAPTCHA_LIBRUS = 202
|
||||
}
|
||||
}
|
@ -73,7 +73,7 @@ class PostNotifications(val app: App, nList: List<AppNotification>) {
|
||||
MainActivity::class.java,
|
||||
"fragmentId" to MainActivity.DRAWER_ITEM_NOTIFICATIONS
|
||||
)
|
||||
val summaryIntent = PendingIntent.getActivity(app, app.notifications.dataId, intent, PendingIntent.FLAG_ONE_SHOT)
|
||||
val summaryIntent = PendingIntent.getActivity(app, app.notificationChannelsManager.data.id, intent, PendingIntent.FLAG_ONE_SHOT)
|
||||
|
||||
// On Nougat or newer - show maximum 8 notifications
|
||||
// On Marshmallow or older - show maximum 4 notifications
|
||||
@ -89,7 +89,7 @@ class PostNotifications(val app: App, nList: List<AppNotification>) {
|
||||
}
|
||||
|
||||
// Create a summary to show *instead* of notifications
|
||||
val combined = NotificationCompat.Builder(app, app.notifications.dataKey)
|
||||
val combined = NotificationCompat.Builder(app, app.notificationChannelsManager.data.key)
|
||||
.setContentTitle(app.getString(R.string.app_name))
|
||||
.setContentText(buildSummaryText(summaryCounts))
|
||||
.setTicker(newNotificationsText)
|
||||
@ -112,7 +112,7 @@ class PostNotifications(val app: App, nList: List<AppNotification>) {
|
||||
.setLights(0xff2196f3.toInt(), 2000, 2000)
|
||||
.setPriority(NotificationCompat.PRIORITY_MAX)
|
||||
.setDefaults(NotificationCompat.DEFAULT_ALL)
|
||||
.setGroup(app.notifications.dataKey)
|
||||
.setGroup(app.notificationChannelsManager.data.key)
|
||||
.setContentIntent(summaryIntent)
|
||||
.setAutoCancel(true)
|
||||
.build()
|
||||
@ -122,7 +122,7 @@ class PostNotifications(val app: App, nList: List<AppNotification>) {
|
||||
// Less than 8 notifications
|
||||
val notifications = nList.map {
|
||||
summaryCounts[it.type]++
|
||||
NotificationCompat.Builder(app, app.notifications.dataKey)
|
||||
NotificationCompat.Builder(app, app.notificationChannelsManager.data.key)
|
||||
.setContentTitle(it.profileName ?: app.getString(R.string.app_name))
|
||||
.setContentText(it.text)
|
||||
.setSubText(if (it.type == TYPE_SERVER_MESSAGE) null else it.title)
|
||||
@ -135,7 +135,7 @@ class PostNotifications(val app: App, nList: List<AppNotification>) {
|
||||
.setLights(0xff2196f3.toInt(), 2000, 2000)
|
||||
.setPriority(NotificationCompat.PRIORITY_MAX)
|
||||
.setDefaults(NotificationCompat.DEFAULT_ALL)
|
||||
.setGroup(app.notifications.dataKey)
|
||||
.setGroup(app.notificationChannelsManager.data.key)
|
||||
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
|
||||
.setContentIntent(it.getPendingIntent(app))
|
||||
.setAutoCancel(true)
|
||||
@ -150,7 +150,7 @@ class PostNotifications(val app: App, nList: List<AppNotification>) {
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
val summary = NotificationCompat.Builder(app, app.notifications.dataKey)
|
||||
val summary = NotificationCompat.Builder(app, app.notificationChannelsManager.data.key)
|
||||
.setContentTitle(newNotificationsText)
|
||||
.setContentText(buildSummaryText(summaryCounts))
|
||||
.setTicker(newNotificationsText)
|
||||
@ -159,13 +159,13 @@ class PostNotifications(val app: App, nList: List<AppNotification>) {
|
||||
.setLights(0xff2196f3.toInt(), 2000, 2000)
|
||||
.setPriority(NotificationCompat.PRIORITY_MAX)
|
||||
.setDefaults(NotificationCompat.DEFAULT_ALL)
|
||||
.setGroup(app.notifications.dataKey)
|
||||
.setGroup(app.notificationChannelsManager.data.key)
|
||||
.setGroupSummary(true)
|
||||
.setContentIntent(summaryIntent)
|
||||
.setAutoCancel(true)
|
||||
.build()
|
||||
|
||||
notificationManager.notify(app.notifications.dataId, summary)
|
||||
notificationManager.notify(app.notificationChannelsManager.data.id, summary)
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
@ -92,7 +92,7 @@ class UpdateDownloaderService : IntentService(UpdateDownloaderService::class.jav
|
||||
return
|
||||
}
|
||||
|
||||
(app.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).cancel(app.notifications.updatesId)
|
||||
(app.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).cancel(app.notificationChannelsManager.updates.id)
|
||||
|
||||
val dir: File? = app.getExternalFilesDir(null)
|
||||
if (dir?.isDirectory == true) {
|
||||
|
@ -94,7 +94,7 @@ class UpdateWorker(val context: Context, val params: WorkerParameters) : Worker(
|
||||
|
||||
val notificationIntent = Intent(app, UpdateDownloaderService::class.java)
|
||||
val pendingIntent = PendingIntent.getService(app, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
val notification = NotificationCompat.Builder(app, app.notifications.updatesKey)
|
||||
val notification = NotificationCompat.Builder(app, app.notificationChannelsManager.updates.key)
|
||||
.setContentTitle(app.getString(R.string.notification_updates_title))
|
||||
.setContentText(app.getString(R.string.notification_updates_text, update.versionName))
|
||||
.setTicker(app.getString(R.string.notification_updates_summary))
|
||||
@ -108,11 +108,11 @@ class UpdateWorker(val context: Context, val params: WorkerParameters) : Worker(
|
||||
.setLights(0xFF00FFFF.toInt(), 2000, 2000)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setDefaults(NotificationCompat.DEFAULT_ALL)
|
||||
.setGroup(app.notifications.updatesKey)
|
||||
.setGroup(app.notificationChannelsManager.updates.key)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setAutoCancel(false)
|
||||
.build()
|
||||
(app.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).notify(app.notifications.updatesId, notification)
|
||||
(app.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).notify(app.notificationChannelsManager.updates.id, notification)
|
||||
|
||||
} catch (ignore: Exception) { }
|
||||
}
|
||||
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-1-8.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.dialogs
|
||||
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.databinding.DialogTemplateBinding
|
||||
import pl.szczodrzynski.edziennik.onClick
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class TemplateDialog(
|
||||
val activity: AppCompatActivity,
|
||||
val onActionPerformed: (() -> Unit)? = null,
|
||||
val onShowListener: ((tag: String) -> Unit)? = null,
|
||||
val onDismissListener: ((tag: String) -> Unit)? = null
|
||||
) : CoroutineScope {
|
||||
companion object {
|
||||
private const val TAG = "TemplateDialog"
|
||||
}
|
||||
|
||||
private lateinit var app: App
|
||||
private lateinit var b: DialogTemplateBinding
|
||||
private lateinit var dialog: AlertDialog
|
||||
|
||||
private val job = Job()
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = job + Dispatchers.Main
|
||||
|
||||
// local variables go here
|
||||
|
||||
init { run {
|
||||
if (activity.isFinishing)
|
||||
return@run
|
||||
onShowListener?.invoke(TAG)
|
||||
app = activity.applicationContext as App
|
||||
b = DialogTemplateBinding.inflate(activity.layoutInflater)
|
||||
dialog = MaterialAlertDialogBuilder(activity)
|
||||
.setView(b.root)
|
||||
.setPositiveButton(R.string.close) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
.setNeutralButton(R.string.add, null)
|
||||
.setOnDismissListener {
|
||||
onDismissListener?.invoke(TAG)
|
||||
}
|
||||
.show()
|
||||
|
||||
dialog.getButton(AlertDialog.BUTTON_NEUTRAL)?.onClick {
|
||||
// do custom action on neutral button click
|
||||
// (does not dismiss the dialog)
|
||||
}
|
||||
|
||||
b.clickMe.onClick {
|
||||
onActionPerformed?.invoke()
|
||||
dialog.dismiss()
|
||||
}
|
||||
}}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-2-15.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.dialogs.captcha
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.databinding.RecaptchaViewBinding
|
||||
import pl.szczodrzynski.edziennik.onClick
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class LibrusCaptchaDialog(
|
||||
val activity: AppCompatActivity,
|
||||
val onSuccess: (recaptchaCode: String) -> Unit,
|
||||
val onFailure: (() -> Unit)?,
|
||||
val onShowListener: ((tag: String) -> Unit)? = null,
|
||||
val onDismissListener: ((tag: String) -> Unit)? = null
|
||||
) : CoroutineScope {
|
||||
companion object {
|
||||
private const val TAG = "LibrusCaptchaDialog"
|
||||
}
|
||||
|
||||
private lateinit var app: App
|
||||
private lateinit var b: RecaptchaViewBinding
|
||||
private lateinit var dialog: AlertDialog
|
||||
|
||||
private val job = Job()
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = job + Dispatchers.Main
|
||||
|
||||
private lateinit var checkboxBackground: Drawable
|
||||
private lateinit var checkboxForeground: Drawable
|
||||
private var success = false
|
||||
|
||||
init { run {
|
||||
if (activity.isFinishing)
|
||||
return@run
|
||||
onShowListener?.invoke(TAG)
|
||||
app = activity.applicationContext as App
|
||||
b = RecaptchaViewBinding.inflate(activity.layoutInflater)
|
||||
dialog = MaterialAlertDialogBuilder(activity)
|
||||
.setView(b.root)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setOnDismissListener {
|
||||
if (!success)
|
||||
onFailure?.invoke()
|
||||
onDismissListener?.invoke(TAG)
|
||||
}
|
||||
.show()
|
||||
|
||||
checkboxBackground = b.checkbox.background
|
||||
checkboxForeground = b.checkbox.foreground
|
||||
success = false
|
||||
|
||||
b.root.onClick {
|
||||
b.checkbox.performClick()
|
||||
}
|
||||
b.checkbox.onClick {
|
||||
b.checkbox.background = null
|
||||
b.checkbox.foreground = null
|
||||
b.progress.visibility = View.VISIBLE
|
||||
RecaptchaDialog(
|
||||
activity,
|
||||
siteKey = "6Lf48moUAAAAAB9ClhdvHr46gRWR-CN31CXQPG2U",
|
||||
referer = "https://portal.librus.pl/rodzina/login",
|
||||
onSuccess = { recaptchaCode ->
|
||||
b.checkbox.background = checkboxBackground
|
||||
b.checkbox.foreground = checkboxForeground
|
||||
b.progress.visibility = View.GONE
|
||||
success = true
|
||||
onSuccess(recaptchaCode)
|
||||
dialog.dismiss()
|
||||
},
|
||||
onFailure = {
|
||||
b.checkbox.background = checkboxBackground
|
||||
b.checkbox.foreground = checkboxForeground
|
||||
b.progress.visibility = View.GONE
|
||||
}
|
||||
)
|
||||
}
|
||||
}}
|
||||
}
|
@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-2-15.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.dialogs.captcha
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.os.Handler
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.*
|
||||
import okhttp3.*
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.databinding.RecaptchaDialogBinding
|
||||
import java.io.IOException
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class RecaptchaDialog(
|
||||
val activity: AppCompatActivity,
|
||||
val siteKey: String,
|
||||
val referer: String,
|
||||
val autoRetry: Boolean = true,
|
||||
val onSuccess: (recaptchaCode: String) -> Unit,
|
||||
val onFailure: (() -> Unit)? = null,
|
||||
val onShowListener: ((tag: String) -> Unit)? = null,
|
||||
val onDismissListener: ((tag: String) -> Unit)? = null
|
||||
) : CoroutineScope {
|
||||
companion object {
|
||||
private const val TAG = "RecaptchaDialog"
|
||||
}
|
||||
|
||||
private lateinit var app: App
|
||||
private val b by lazy { RecaptchaDialogBinding.inflate(LayoutInflater.from(activity)) }
|
||||
private var dialog: AlertDialog? = null
|
||||
|
||||
private val captchaUrl = "https://www.google.com/recaptcha/api/fallback?k=$siteKey"
|
||||
private var success = false
|
||||
|
||||
private val job = Job()
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = job + Dispatchers.Main
|
||||
|
||||
private var code = ""
|
||||
private var payload = ""
|
||||
|
||||
init { run {
|
||||
if (activity.isFinishing)
|
||||
return@run
|
||||
app = activity.applicationContext as App
|
||||
onShowListener?.invoke(TAG)
|
||||
success = false
|
||||
|
||||
launch { initCaptcha() }
|
||||
}}
|
||||
|
||||
private suspend fun initCaptcha() {
|
||||
withContext(Dispatchers.Default) {
|
||||
val request = Request.Builder()
|
||||
.url(captchaUrl)
|
||||
.addHeader("Referer", referer)
|
||||
.addHeader("Accept-Language", "pl")
|
||||
.build()
|
||||
app.http.newCall(request).enqueue(object : Callback {
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
val html = response.body()?.string() ?: return
|
||||
Log.d(TAG, html)
|
||||
parseHtml(html)
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call, e: IOException) {
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseHtml(html: String) {
|
||||
launch {
|
||||
"class=\"rc-imageselect-desc(?:-no-canonical)?\">(.+?) <strong>(.+?)</strong>".toRegex().find(html)?.let {
|
||||
b.descTitle.text = it.groupValues[1]
|
||||
b.descText.text = it.groupValues[2]
|
||||
}
|
||||
code = "name=\"c\" value=\"([A-z0-9-_]+)\"".toRegex().find(html)?.let { it.groupValues[1] } ?: return@launch
|
||||
payload = "https://www.google.com/recaptcha/api2/payload?c=$code&k=$siteKey"
|
||||
withContext(Dispatchers.Default) {
|
||||
val request = Request.Builder()
|
||||
.url(payload)
|
||||
.addHeader("Referer", captchaUrl)
|
||||
.addHeader("Accept-Language", "pl")
|
||||
.build()
|
||||
app.http.newCall(request).enqueue(object : Callback {
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
val bitmap: Bitmap? = BitmapFactory.decodeStream(response.body()?.byteStream())
|
||||
Handler(activity.mainLooper).post {
|
||||
if (bitmap == null) {
|
||||
onFailure?.invoke()
|
||||
Toast.makeText(activity, "Nie udało się załadować reCAPTCHA.", Toast.LENGTH_SHORT).show()
|
||||
return@post
|
||||
}
|
||||
b.payload.setImageBitmap(bitmap)
|
||||
showDialog()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call, e: IOException) {
|
||||
onFailure?.invoke()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showDialog() {
|
||||
if (dialog == null) {
|
||||
dialog = MaterialAlertDialogBuilder(activity)
|
||||
.setView(b.root)
|
||||
.setPositiveButton("OK") { _, _ ->
|
||||
validateAnswer()
|
||||
}
|
||||
.setOnDismissListener {
|
||||
if (!success)
|
||||
onFailure?.invoke()
|
||||
onDismissListener?.invoke(TAG)
|
||||
}
|
||||
.create()
|
||||
}
|
||||
b.image0.isChecked = false
|
||||
b.image1.isChecked = false
|
||||
b.image2.isChecked = false
|
||||
b.image3.isChecked = false
|
||||
b.image4.isChecked = false
|
||||
b.image5.isChecked = false
|
||||
b.image6.isChecked = false
|
||||
b.image7.isChecked = false
|
||||
b.image8.isChecked = false
|
||||
dialog!!.show()
|
||||
}
|
||||
|
||||
private fun validateAnswer() {
|
||||
launch {
|
||||
val list = mutableListOf(
|
||||
"c=$code"
|
||||
)
|
||||
if (b.image0.isChecked) list += "response=0"
|
||||
if (b.image1.isChecked) list += "response=1"
|
||||
if (b.image2.isChecked) list += "response=2"
|
||||
if (b.image3.isChecked) list += "response=3"
|
||||
if (b.image4.isChecked) list += "response=4"
|
||||
if (b.image5.isChecked) list += "response=5"
|
||||
if (b.image6.isChecked) list += "response=6"
|
||||
if (b.image7.isChecked) list += "response=7"
|
||||
if (b.image8.isChecked) list += "response=8"
|
||||
val request = Request.Builder()
|
||||
.url(captchaUrl)
|
||||
.addHeader("Referer", captchaUrl)
|
||||
.addHeader("Accept-Language", "pl")
|
||||
.addHeader("Origin", "https://www.google.com")
|
||||
.addHeader("Content-Type", "application/x-www-form-urlencoded")
|
||||
.post(RequestBody.create(MediaType.parse("application/x-www-form-urlencoded"), list.joinToString("&")))
|
||||
.build()
|
||||
withContext(Dispatchers.Default) {
|
||||
app.http.newCall(request).enqueue(object : Callback {
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
val html = response.body()?.string() ?: return
|
||||
val match = "<textarea.+?>([A-z0-9-_]+)</textarea>".toRegex().find(html)
|
||||
if (match == null) {
|
||||
parseHtml(html)
|
||||
return
|
||||
}
|
||||
Handler(activity.mainLooper).post {
|
||||
success = true
|
||||
onSuccess(match.groupValues[1])
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call, e: IOException) {
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -41,6 +41,7 @@ class ErrorSnackbar(val activity: AppCompatActivity) : CoroutineScope {
|
||||
val message = errors.map {
|
||||
listOf(
|
||||
it.getStringReason(activity).asBoldSpannable().asColoredSpannable(R.attr.colorOnBackground.resolveAttr(activity)),
|
||||
activity.getString(R.string.error_unknown_format, it.errorCode, it.tag),
|
||||
if (App.devMode)
|
||||
it.throwable?.stackTraceString ?: it.throwable?.localizedMessage
|
||||
else
|
||||
|
@ -4,6 +4,8 @@
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.home.cards
|
||||
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.view.LayoutInflater
|
||||
@ -25,10 +27,14 @@ import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||
import pl.szczodrzynski.edziennik.databinding.CardHomeDebugBinding
|
||||
import pl.szczodrzynski.edziennik.dp
|
||||
import pl.szczodrzynski.edziennik.onClick
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.captcha.LibrusCaptchaDialog
|
||||
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCard
|
||||
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCardAdapter
|
||||
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment
|
||||
import pl.szczodrzynski.edziennik.ui.modules.login.LoginLibrusCaptchaActivity
|
||||
import pl.szczodrzynski.edziennik.ui.widgets.WidgetConfigActivity
|
||||
import pl.szczodrzynski.edziennik.ui.widgets.luckynumber.WidgetLuckyNumberProvider
|
||||
import pl.szczodrzynski.edziennik.ui.widgets.notifications.WidgetNotificationsProvider
|
||||
import pl.szczodrzynski.edziennik.ui.widgets.timetable.WidgetTimetableProvider
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class HomeDebugCard(
|
||||
@ -54,10 +60,6 @@ class HomeDebugCard(
|
||||
}
|
||||
holder.root += b.root
|
||||
|
||||
b.composeNewButton.onClick {
|
||||
activity.loadTarget(MainActivity.TARGET_MESSAGES_COMPOSE)
|
||||
}
|
||||
|
||||
b.migrate71.onClick {
|
||||
app.db.compileStatement("DELETE FROM messages WHERE profileId IN (SELECT profileId FROM profiles WHERE archived = 0);").executeUpdateDelete()
|
||||
app.db.compileStatement("DELETE FROM messageRecipients WHERE profileId IN (SELECT profileId FROM profiles WHERE archived = 0);").executeUpdateDelete()
|
||||
@ -84,7 +86,8 @@ class HomeDebugCard(
|
||||
}
|
||||
|
||||
b.librusCaptchaButton.onClick {
|
||||
app.startActivity(Intent(activity, LoginLibrusCaptchaActivity::class.java))
|
||||
//app.startActivity(Intent(activity, LoginLibrusCaptchaActivity::class.java))
|
||||
LibrusCaptchaDialog(activity, onSuccess = {}, onFailure = {})
|
||||
}
|
||||
|
||||
b.getLogs.onClick {
|
||||
@ -100,6 +103,21 @@ class HomeDebugCard(
|
||||
}
|
||||
}
|
||||
|
||||
b.refreshWidget.onClick {
|
||||
for (widgetType in 0..2) {
|
||||
val theClass = when (widgetType) {
|
||||
WidgetConfigActivity.WIDGET_TIMETABLE -> WidgetTimetableProvider::class.java
|
||||
WidgetConfigActivity.WIDGET_NOTIFICATIONS -> WidgetNotificationsProvider::class.java
|
||||
WidgetConfigActivity.WIDGET_LUCKY_NUMBER -> WidgetLuckyNumberProvider::class.java
|
||||
else -> WidgetTimetableProvider::class.java
|
||||
}
|
||||
val intent = Intent(app, theClass)
|
||||
intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
|
||||
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, AppWidgetManager.getInstance(app).getAppWidgetIds(ComponentName(app, theClass)))
|
||||
app.sendBroadcast(intent)
|
||||
}
|
||||
}
|
||||
|
||||
holder.root.onClick {
|
||||
// do stuff
|
||||
}
|
||||
|
@ -19,10 +19,12 @@ import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.api.ERROR_CAPTCHA_NEEDED
|
||||
import pl.szczodrzynski.edziennik.data.api.LOGIN_NO_ARGUMENTS
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
|
||||
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskErrorEvent
|
||||
import pl.szczodrzynski.edziennik.data.api.events.FirstLoginFinishedEvent
|
||||
import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent
|
||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
|
||||
import pl.szczodrzynski.edziennik.databinding.FragmentLoginProgressBinding
|
||||
@ -57,7 +59,13 @@ class LoginProgressFragment : Fragment(), CoroutineScope {
|
||||
return
|
||||
}
|
||||
|
||||
doFirstLogin(args)
|
||||
}
|
||||
|
||||
private fun doFirstLogin(args: Bundle) {
|
||||
launch {
|
||||
activity.errorSnackbar.dismiss()
|
||||
|
||||
val firstProfileId = (app.db.profileDao().lastId ?: 0) + 1
|
||||
val loginType = args.getInt("loginType", -1)
|
||||
val loginMode = args.getInt("loginMode", 0)
|
||||
@ -88,6 +96,7 @@ class LoginProgressFragment : Fragment(), CoroutineScope {
|
||||
}
|
||||
activity.loginStores += event.loginStore
|
||||
activity.profiles += event.profileList.map { LoginSummaryProfileAdapter.Item(it) }
|
||||
activity.errorSnackbar.dismiss()
|
||||
nav.navigate(R.id.loginSummaryFragment, null, LoginActivity.navOptions)
|
||||
}
|
||||
|
||||
@ -98,6 +107,24 @@ class LoginProgressFragment : Fragment(), CoroutineScope {
|
||||
nav.navigateUp()
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onUserActionRequiredEvent(event: UserActionRequiredEvent) {
|
||||
val args = arguments ?: run {
|
||||
activity.error(ApiError(TAG, LOGIN_NO_ARGUMENTS))
|
||||
nav.navigateUp()
|
||||
return
|
||||
}
|
||||
|
||||
app.userActionManager.execute(activity, event.profileId, event.type, onSuccess = { code ->
|
||||
args.putString("recaptchaCode", code)
|
||||
args.putLong("recaptchaTime", System.currentTimeMillis())
|
||||
doFirstLogin(args)
|
||||
}, onFailure = {
|
||||
activity.error(ApiError(TAG, ERROR_CAPTCHA_NEEDED))
|
||||
nav.navigateUp()
|
||||
})
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
EventBus.getDefault().register(this)
|
||||
super.onStart()
|
||||
|
@ -767,7 +767,7 @@ public class SettingsNewFragment extends MaterialAboutFragment {
|
||||
.color(IconicsColor.colorInt(iconColor))
|
||||
)
|
||||
.setOnClickAction(() -> {
|
||||
String channel = app.getNotifications().getDataKey();
|
||||
String channel = app.getNotificationChannelsManager().getData().getKey();
|
||||
Intent intent = new Intent();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
intent.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-1-8.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.Checkable;
|
||||
|
||||
import androidx.appcompat.widget.AppCompatImageView;
|
||||
|
||||
public class CheckableImageView extends AppCompatImageView implements Checkable {
|
||||
|
||||
private boolean checked;
|
||||
private boolean broadcasting;
|
||||
|
||||
private OnCheckedChangeListener onCheckedChangeListener;
|
||||
|
||||
private static final int[] CHECKED_STATE_SET = {
|
||||
android.R.attr.state_checked
|
||||
};
|
||||
|
||||
public interface OnCheckedChangeListener {
|
||||
void onCheckedChanged(CheckableImageView checkableImageView, boolean isChecked);
|
||||
}
|
||||
|
||||
public CheckableImageView(final Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public CheckableImageView(final Context context, final AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public CheckableImageView(final Context context, final AttributeSet attrs, final int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
setOnClickListener(v -> toggle());
|
||||
}
|
||||
|
||||
@Override public int[] onCreateDrawableState(final int extraSpace) {
|
||||
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
|
||||
if (isChecked()) {
|
||||
mergeDrawableStates(drawableState, CHECKED_STATE_SET);
|
||||
}
|
||||
return drawableState;
|
||||
}
|
||||
|
||||
@Override public void toggle() {
|
||||
setChecked(!checked);
|
||||
}
|
||||
|
||||
@Override public boolean isChecked() {
|
||||
return checked;
|
||||
}
|
||||
|
||||
@Override public void setChecked(final boolean checked) {
|
||||
if (this.checked != checked) {
|
||||
this.checked = checked;
|
||||
refreshDrawableState();
|
||||
|
||||
// Avoid infinite recursions if setChecked() is called from a listener
|
||||
if (broadcasting) {
|
||||
return;
|
||||
}
|
||||
broadcasting = true;
|
||||
if (onCheckedChangeListener != null) {
|
||||
onCheckedChangeListener.onCheckedChanged(this, checked);
|
||||
}
|
||||
broadcasting = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void setOnCheckedChangeListener( final OnCheckedChangeListener onCheckedChangeListener) {
|
||||
this.onCheckedChangeListener = onCheckedChangeListener;
|
||||
}
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-2-15.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.utils.managers
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.core.app.NotificationCompat.*
|
||||
import androidx.core.app.NotificationManagerCompat.*
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
|
||||
class NotificationChannelsManager(val c: Context) {
|
||||
data class Channel(
|
||||
val id: Int,
|
||||
val key: String,
|
||||
val name: String,
|
||||
val description: String,
|
||||
val importance: Int,
|
||||
val priority: Int,
|
||||
val quiet: Boolean = false,
|
||||
val lightColor: Int? = null
|
||||
)
|
||||
|
||||
fun registerAllChannels() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
|
||||
return
|
||||
val manager = c.getSystemService(NotificationManager::class.java)
|
||||
|
||||
val registered = manager.notificationChannels.map { it.id }
|
||||
val all = all.map { it.key }
|
||||
|
||||
val toRegister = all - registered
|
||||
val toDelete = registered - all
|
||||
|
||||
for (key in toRegister) {
|
||||
val channel = this.all.firstOrNull { it.key == key } ?: continue
|
||||
manager.createNotificationChannel(NotificationChannel(key, channel.name, channel.importance).apply {
|
||||
description = channel.description
|
||||
if (channel.quiet) {
|
||||
enableVibration(false)
|
||||
setSound(null, null)
|
||||
}
|
||||
channel.lightColor?.let {
|
||||
enableLights(true)
|
||||
lightColor = it
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
for (key in toDelete) {
|
||||
manager.deleteNotificationChannel(key)
|
||||
}
|
||||
}
|
||||
|
||||
val sync by lazy {
|
||||
Channel(
|
||||
1,
|
||||
"pl.szczodrzynski.edziennik.SYNC",
|
||||
c.getString(R.string.notification_channel_get_data_name),
|
||||
c.getString(R.string.notification_channel_get_data_desc),
|
||||
IMPORTANCE_MIN,
|
||||
PRIORITY_MIN,
|
||||
quiet = true
|
||||
)
|
||||
}
|
||||
|
||||
val data by lazy {
|
||||
Channel(
|
||||
50,
|
||||
"pl.szczodrzynski.edziennik.DATA",
|
||||
c.getString(R.string.notification_channel_notifications_name),
|
||||
c.getString(R.string.notification_channel_notifications_desc),
|
||||
IMPORTANCE_HIGH,
|
||||
PRIORITY_MAX,
|
||||
lightColor = 0xff2196f3.toInt()
|
||||
)
|
||||
}
|
||||
|
||||
val dataQuiet by lazy {
|
||||
Channel(
|
||||
60,
|
||||
"pl.szczodrzynski.edziennik.DATA_QUIET",
|
||||
c.getString(R.string.notification_channel_notifications_quiet_name),
|
||||
c.getString(R.string.notification_channel_notifications_quiet_desc),
|
||||
IMPORTANCE_LOW,
|
||||
PRIORITY_LOW,
|
||||
quiet = true
|
||||
)
|
||||
}
|
||||
|
||||
val updates by lazy {
|
||||
Channel(
|
||||
100,
|
||||
"pl.szczodrzynski.edziennik.UPDATES",
|
||||
c.getString(R.string.notification_channel_updates_name),
|
||||
c.getString(R.string.notification_channel_updates_desc),
|
||||
IMPORTANCE_DEFAULT,
|
||||
PRIORITY_DEFAULT
|
||||
)
|
||||
}
|
||||
|
||||
val userAttention by lazy {
|
||||
Channel(
|
||||
200,
|
||||
"pl.szczodrzynski.edziennik.USER_ATTENTION",
|
||||
c.getString(R.string.notification_channel_user_attention_name),
|
||||
c.getString(R.string.notification_channel_user_attention_desc),
|
||||
IMPORTANCE_DEFAULT,
|
||||
PRIORITY_DEFAULT
|
||||
)
|
||||
}
|
||||
|
||||
val all by lazy { listOf(
|
||||
sync,
|
||||
data,
|
||||
dataQuiet,
|
||||
updates,
|
||||
userAttention
|
||||
) }
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-2-15.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.utils.managers
|
||||
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.NotificationCompat
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.api.ERROR_CAPTCHA_LIBRUS_PORTAL
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
|
||||
import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent
|
||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.captcha.LibrusCaptchaDialog
|
||||
|
||||
class UserActionManager(val app: App) {
|
||||
companion object {
|
||||
private const val TAG = "UserActionManager"
|
||||
}
|
||||
|
||||
fun requiresUserAction(apiError: ApiError): Boolean {
|
||||
return apiError.errorCode == ERROR_CAPTCHA_LIBRUS_PORTAL
|
||||
}
|
||||
|
||||
fun sendToUser(apiError: ApiError) {
|
||||
val type = when (apiError.errorCode) {
|
||||
ERROR_CAPTCHA_LIBRUS_PORTAL -> UserActionRequiredEvent.CAPTCHA_LIBRUS
|
||||
else -> 0
|
||||
}
|
||||
|
||||
if (EventBus.getDefault().hasSubscriberForEvent(UserActionRequiredEvent::class.java)) {
|
||||
EventBus.getDefault().post(UserActionRequiredEvent(apiError.profileId ?: -1, type))
|
||||
return
|
||||
}
|
||||
|
||||
val manager = app.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
val text = app.getString(when (type) {
|
||||
UserActionRequiredEvent.CAPTCHA_LIBRUS -> R.string.notification_user_action_required_captcha_librus
|
||||
else -> R.string.notification_user_action_required_text
|
||||
}, apiError.profileId)
|
||||
|
||||
val intent = Intent(
|
||||
app,
|
||||
MainActivity::class.java,
|
||||
"action" to "userActionRequired",
|
||||
"profileId" to (apiError.profileId ?: -1),
|
||||
"type" to type
|
||||
)
|
||||
val pendingIntent = PendingIntent.getActivity(app, System.currentTimeMillis().toInt(), intent, PendingIntent.FLAG_ONE_SHOT)
|
||||
|
||||
val notification = NotificationCompat.Builder(app, app.notificationChannelsManager.userAttention.key)
|
||||
.setContentTitle(app.getString(R.string.notification_user_action_required_title))
|
||||
.setContentText(text)
|
||||
.setSmallIcon(R.drawable.ic_error_outline)
|
||||
.setStyle(NotificationCompat.BigTextStyle().bigText(text))
|
||||
.setColor(0xff2196f3.toInt())
|
||||
.setLights(0xff2196f3.toInt(), 2000, 2000)
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
.setDefaults(NotificationCompat.DEFAULT_ALL)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setAutoCancel(true)
|
||||
.build()
|
||||
|
||||
manager.notify(System.currentTimeMillis().toInt(), notification)
|
||||
}
|
||||
|
||||
fun execute(
|
||||
activity: AppCompatActivity,
|
||||
profileId: Int?,
|
||||
type: Int,
|
||||
onSuccess: ((code: String) -> Unit)? = null,
|
||||
onFailure: (() -> Unit)? = null
|
||||
) {
|
||||
if (type != UserActionRequiredEvent.CAPTCHA_LIBRUS)
|
||||
return
|
||||
|
||||
if (profileId == null)
|
||||
return
|
||||
// show captcha dialog
|
||||
// use passed onSuccess listener, else sync profile
|
||||
LibrusCaptchaDialog(activity, onSuccess = onSuccess ?: { code ->
|
||||
EdziennikTask.syncProfile(profileId, arguments = JsonObject(
|
||||
"recaptchaCode" to code,
|
||||
"recaptchaTime" to System.currentTimeMillis()
|
||||
)).enqueue(activity)
|
||||
}, onFailure = onFailure)
|
||||
}
|
||||
}
|
BIN
app/src/main/res/drawable-xhdpi/recaptcha.png
Normal file
BIN
app/src/main/res/drawable-xhdpi/recaptcha.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
15
app/src/main/res/drawable/recaptcha_card_border.xml
Normal file
15
app/src/main/res/drawable/recaptcha_card_border.xml
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) Kuba Szczodrzyński 2020-1-7.
|
||||
-->
|
||||
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#d3ffffff" />
|
||||
<corners
|
||||
android:bottomLeftRadius="4dp"
|
||||
android:bottomRightRadius="4dp"
|
||||
android:topLeftRadius="4dp"
|
||||
android:topRightRadius="4dp" />
|
||||
</shape>
|
BIN
app/src/main/res/drawable/recaptcha_check.png
Normal file
BIN
app/src/main/res/drawable/recaptcha_check.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.2 KiB |
11
app/src/main/res/drawable/recaptcha_checkbox_border.xml
Normal file
11
app/src/main/res/drawable/recaptcha_checkbox_border.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) Kuba Szczodrzyński 2020-1-7.
|
||||
-->
|
||||
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_enabled="true" android:drawable="@drawable/recaptcha_checkbox_border_normal" />
|
||||
<item android:state_hovered="true" android:drawable="@drawable/recaptcha_checkbox_border_hovered" />
|
||||
<item android:state_focused="true" android:drawable="@drawable/recaptcha_checkbox_border_focused" />
|
||||
<item android:state_active="true" android:drawable="@drawable/recaptcha_checkbox_border_active" />
|
||||
</selector>
|
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) Kuba Szczodrzyński 2020-1-7.
|
||||
-->
|
||||
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="#c1c1c1" />
|
||||
<solid android:color="#ebebeb" />
|
||||
<corners
|
||||
android:bottomLeftRadius="2dp"
|
||||
android:bottomRightRadius="2dp"
|
||||
android:topLeftRadius="2dp"
|
||||
android:topRightRadius="2dp" />
|
||||
</shape>
|
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) Kuba Szczodrzyński 2020-1-7.
|
||||
-->
|
||||
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="#4d90fe" />
|
||||
<corners
|
||||
android:bottomLeftRadius="2dp"
|
||||
android:bottomRightRadius="2dp"
|
||||
android:topLeftRadius="2dp"
|
||||
android:topRightRadius="2dp" />
|
||||
</shape>
|
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) Kuba Szczodrzyński 2020-1-7.
|
||||
-->
|
||||
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="#b2b2b2" />
|
||||
<solid android:color="#e0e0e0" />
|
||||
<corners
|
||||
android:bottomLeftRadius="2dp"
|
||||
android:bottomRightRadius="2dp"
|
||||
android:topLeftRadius="2dp"
|
||||
android:topRightRadius="2dp" />
|
||||
</shape>
|
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) Kuba Szczodrzyński 2020-1-7.
|
||||
-->
|
||||
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="#c1c1c1" />
|
||||
<corners
|
||||
android:bottomLeftRadius="2dp"
|
||||
android:bottomRightRadius="2dp"
|
||||
android:topLeftRadius="2dp"
|
||||
android:topRightRadius="2dp" />
|
||||
</shape>
|
9
app/src/main/res/drawable/recaptcha_image.xml
Normal file
9
app/src/main/res/drawable/recaptcha_image.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) Kuba Szczodrzyński 2020-1-7.
|
||||
-->
|
||||
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_checkable="true" android:drawable="@drawable/recaptcha_image_pressed" /> <!-- pressed -->
|
||||
<item android:state_checked="true" android:drawable="@drawable/recaptcha_image_checked" /> <!-- focused -->
|
||||
</selector>
|
19
app/src/main/res/drawable/recaptcha_image_checked.xml
Normal file
19
app/src/main/res/drawable/recaptcha_image_checked.xml
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) Kuba Szczodrzyński 2020-1-7.
|
||||
-->
|
||||
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="#7fffffff"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<inset android:drawable="@drawable/recaptcha_check"
|
||||
android:insetLeft="45dp"
|
||||
android:insetBottom="45dp"
|
||||
android:insetRight="45dp"
|
||||
android:insetTop="45dp"/>
|
||||
</item>
|
||||
</layer-list>
|
8
app/src/main/res/drawable/recaptcha_image_pressed.xml
Normal file
8
app/src/main/res/drawable/recaptcha_image_pressed.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) Kuba Szczodrzyński 2020-1-7.
|
||||
-->
|
||||
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="#30000000"/>
|
||||
</shape>
|
10
app/src/main/res/layout/captcha_dialog_librus.xml
Normal file
10
app/src/main/res/layout/captcha_dialog_librus.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) Kuba Szczodrzyński 2020-1-8.
|
||||
-->
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -32,6 +32,13 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Librus Captcha" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/refreshWidget"
|
||||
style="@style/Widget.MaterialComponents.Button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Refresh all widgets" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@ -51,13 +58,6 @@
|
||||
android:text="Messages"
|
||||
android:textSize="16sp"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/composeNewButton"
|
||||
style="@style/Widget.MaterialComponents.Button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Compose 2" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/migrate71"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
|
25
app/src/main/res/layout/dialog_template.xml
Normal file
25
app/src/main/res/layout/dialog_template.xml
Normal file
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) Kuba Szczodrzyński 2020-1-8.
|
||||
-->
|
||||
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingTop="24dp"
|
||||
android:paddingRight="16dp">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Hello world"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/clickMe"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Click me"/>
|
||||
</LinearLayout>
|
||||
</layout>
|
193
app/src/main/res/layout/recaptcha_dialog.xml
Normal file
193
app/src/main/res/layout/recaptcha_dialog.xml
Normal file
@ -0,0 +1,193 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) Kuba Szczodrzyński 2020-1-7.
|
||||
-->
|
||||
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="386dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="113dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:background="#4a90e2"
|
||||
android:padding="24dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/descTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="Wybierz wszystkie zdjęcia, na których jest"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@android:color/white" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/descText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="pan fotograf"
|
||||
android:textSize="28sp"
|
||||
android:textColor="@android:color/white"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/payload"
|
||||
android:layout_width="386dp"
|
||||
android:layout_height="386dp"
|
||||
android:scaleType="centerCrop"
|
||||
tools:srcCompat="@tools:sample/backgrounds/scenic[1]" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="386dp"
|
||||
android:layout_height="386dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<pl.szczodrzynski.edziennik.utils.CheckableImageView
|
||||
android:id="@+id/image0"
|
||||
android:layout_width="126dp"
|
||||
android:layout_height="126dp"
|
||||
android:clickable="true"
|
||||
android:background="@drawable/recaptcha_image"
|
||||
android:focusable="true" />
|
||||
|
||||
<View
|
||||
android:layout_width="4dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?colorSurface" />
|
||||
|
||||
<pl.szczodrzynski.edziennik.utils.CheckableImageView
|
||||
android:id="@+id/image1"
|
||||
android:layout_width="126dp"
|
||||
android:layout_height="126dp"
|
||||
android:clickable="true"
|
||||
android:background="@drawable/recaptcha_image"
|
||||
android:focusable="true" />
|
||||
|
||||
<View
|
||||
android:layout_width="4dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?colorSurface" />
|
||||
|
||||
<pl.szczodrzynski.edziennik.utils.CheckableImageView
|
||||
android:id="@+id/image2"
|
||||
android:layout_width="126dp"
|
||||
android:layout_height="126dp"
|
||||
android:clickable="true"
|
||||
android:background="@drawable/recaptcha_image"
|
||||
android:focusable="true" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="4dp"
|
||||
android:background="?colorSurface" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<pl.szczodrzynski.edziennik.utils.CheckableImageView
|
||||
android:id="@+id/image3"
|
||||
android:layout_width="126dp"
|
||||
android:layout_height="126dp"
|
||||
android:clickable="true"
|
||||
android:background="@drawable/recaptcha_image"
|
||||
android:focusable="true" />
|
||||
|
||||
<View
|
||||
android:layout_width="4dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?colorSurface" />
|
||||
|
||||
<pl.szczodrzynski.edziennik.utils.CheckableImageView
|
||||
android:id="@+id/image4"
|
||||
android:layout_width="126dp"
|
||||
android:layout_height="126dp"
|
||||
android:clickable="true"
|
||||
android:background="@drawable/recaptcha_image"
|
||||
android:focusable="true" />
|
||||
|
||||
<View
|
||||
android:layout_width="4dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?colorSurface" />
|
||||
|
||||
<pl.szczodrzynski.edziennik.utils.CheckableImageView
|
||||
android:id="@+id/image5"
|
||||
android:layout_width="126dp"
|
||||
android:layout_height="126dp"
|
||||
android:clickable="true"
|
||||
android:background="@drawable/recaptcha_image"
|
||||
android:focusable="true" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="4dp"
|
||||
android:background="?colorSurface" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<pl.szczodrzynski.edziennik.utils.CheckableImageView
|
||||
android:id="@+id/image6"
|
||||
android:layout_width="126dp"
|
||||
android:layout_height="126dp"
|
||||
android:clickable="true"
|
||||
android:background="@drawable/recaptcha_image"
|
||||
android:focusable="true" />
|
||||
|
||||
<View
|
||||
android:layout_width="4dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?colorSurface" />
|
||||
|
||||
<pl.szczodrzynski.edziennik.utils.CheckableImageView
|
||||
android:id="@+id/image7"
|
||||
android:layout_width="126dp"
|
||||
android:layout_height="126dp"
|
||||
android:clickable="true"
|
||||
android:background="@drawable/recaptcha_image"
|
||||
android:focusable="true" />
|
||||
|
||||
<View
|
||||
android:layout_width="4dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?colorSurface" />
|
||||
|
||||
<pl.szczodrzynski.edziennik.utils.CheckableImageView
|
||||
android:id="@+id/image8"
|
||||
android:layout_width="126dp"
|
||||
android:layout_height="126dp"
|
||||
android:clickable="true"
|
||||
android:background="@drawable/recaptcha_image"
|
||||
android:focusable="true" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
</layout>
|
100
app/src/main/res/layout/recaptcha_view.xml
Normal file
100
app/src/main/res/layout/recaptcha_view.xml
Normal file
@ -0,0 +1,100 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) Kuba Szczodrzyński 2020-1-7.
|
||||
-->
|
||||
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingTop="24dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingBottom="8dp">
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="304dp"
|
||||
android:layout_height="78dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="8dp"
|
||||
app:cardBackgroundColor="#f9f9f9"
|
||||
app:cardForegroundColor="@android:color/transparent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/checkbox"
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="28dp"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:background="@drawable/recaptcha_checkbox_border"
|
||||
android:foreground="?selectableItemBackgroundBorderless">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="152dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="14sp"
|
||||
android:text="Nie jestem robotem"
|
||||
android:textColor="@android:color/black"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingEnd="13dp"
|
||||
android:paddingRight="13dp"
|
||||
android:orientation="vertical"
|
||||
android:gravity="end">
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="26dp"
|
||||
android:layout_marginStart="26dp"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal">
|
||||
<ImageView
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginLeft="13dp"
|
||||
android:layout_marginRight="13dp"
|
||||
app:srcCompat="@drawable/recaptcha"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="5dp"
|
||||
android:text="reCAPTCHA"
|
||||
android:textColor="#555"
|
||||
android:textSize="10sp"/>
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Prywatność - Warunki"
|
||||
android:textColor="#555"
|
||||
android:textSize="8dp"
|
||||
android:singleLine="true"
|
||||
android:visibility="invisible"/>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/recaptcha_card_border"/>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
</FrameLayout>
|
||||
</layout>
|
@ -33,6 +33,9 @@
|
||||
|
||||
<string name="error_115" translatable="false">ERROR_NO_STUDENTS_IN_ACCOUNT</string>
|
||||
|
||||
<string name="error_3000" translatable="false">ERROR_CAPTCHA_NEEDED</string>
|
||||
<string name="error_3001" translatable="false">ERROR_CAPTCHA_LIBRUS_PORTAL</string>
|
||||
|
||||
<string name="error_120" translatable="false">CODE_INTERNAL_LIBRUS_ACCOUNT_410</string>
|
||||
<string name="error_121" translatable="false">CODE_INTERNAL_LIBRUS_SYNERGIA_EXPIRED</string>
|
||||
<string name="error_124" translatable="false">ERROR_LOGIN_LIBRUS_API_CAPTCHA_NEEDED</string>
|
||||
@ -200,6 +203,9 @@
|
||||
|
||||
<string name="error_115_reason">Brak uczniów przypisanych do konta</string>
|
||||
|
||||
<string name="error_3000_reason">Wymagane rozwiązanie zadania Captcha</string>
|
||||
<string name="error_3001_reason">Librus: wymagane rozwiązanie zadania Captcha</string>
|
||||
|
||||
<string name="error_120_reason">CODE_INTERNAL_LIBRUS_ACCOUNT_410</string>
|
||||
<string name="error_121_reason">CODE_INTERNAL_LIBRUS_SYNERGIA_EXPIRED</string>
|
||||
<string name="error_124_reason">Wymagane wypełnienie CAPTCHA</string>
|
||||
|
@ -1176,4 +1176,9 @@
|
||||
<string name="timetable_generate_show_teachers_names">Pokaż imiona i nazwiska nauczycieli</string>
|
||||
<string name="messages_compose_confirm_title">Potwierdź wysłanie wiadomości</string>
|
||||
<string name="messages_compose_confirm_text">Czy na pewno chcesz wysłać wiadomość do wybranych odbiorców?</string>
|
||||
<string name="notification_channel_user_attention_name">Wymagane działanie</string>
|
||||
<string name="notification_channel_user_attention_desc">Powiadomienia o problemie, który wymaga działania użytkownika (np. weryfikacja Captcha). Zalecane jest pozostawienie tej kategorii włączonej.</string>
|
||||
<string name="notification_user_action_required_title">Wymagane działanie w aplikacji</string>
|
||||
<string name="notification_user_action_required_text">Problem, który uniemożliwia synchronizację musi być rozwiązany przez użytkownika. Kliknij, aby uzyskać więcej informacji.</string>
|
||||
<string name="notification_user_action_required_captcha_librus">Librus: wymagane rozwiązanie zadania Captcha. Kliknij, aby kontynuować logowanie do dziennika.</string>
|
||||
</resources>
|
||||
|
Loading…
x
Reference in New Issue
Block a user