forked from github/szkolny
[UI/WebPush] Implement Web Push pairing fragment and API. Add more templates.
This commit is contained in:
parent
b905283b61
commit
3e4accb82c
@ -67,6 +67,7 @@ import pl.szczodrzynski.edziennik.ui.modules.notifications.NotificationsFragment
|
|||||||
import pl.szczodrzynski.edziennik.ui.modules.settings.ProfileManagerFragment
|
import pl.szczodrzynski.edziennik.ui.modules.settings.ProfileManagerFragment
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsNewFragment
|
import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsNewFragment
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.timetable.v2.TimetableFragment
|
import pl.szczodrzynski.edziennik.ui.modules.timetable.v2.TimetableFragment
|
||||||
|
import pl.szczodrzynski.edziennik.ui.modules.webpush.WebPushFragment
|
||||||
import pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoTouch
|
import pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoTouch
|
||||||
import pl.szczodrzynski.edziennik.utils.Themes
|
import pl.szczodrzynski.edziennik.utils.Themes
|
||||||
import pl.szczodrzynski.edziennik.utils.Utils
|
import pl.szczodrzynski.edziennik.utils.Utils
|
||||||
@ -118,6 +119,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
const val TARGET_HELP = 502
|
const val TARGET_HELP = 502
|
||||||
const val TARGET_FEEDBACK = 120
|
const val TARGET_FEEDBACK = 120
|
||||||
const val TARGET_MESSAGES_DETAILS = 503
|
const val TARGET_MESSAGES_DETAILS = 503
|
||||||
|
const val TARGET_WEB_PUSH = 140
|
||||||
|
|
||||||
const val HOME_ID = DRAWER_ITEM_HOME
|
const val HOME_ID = DRAWER_ITEM_HOME
|
||||||
|
|
||||||
@ -209,6 +211,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
list += NavTarget(TARGET_HELP, R.string.menu_help, HelpFragment::class)
|
list += NavTarget(TARGET_HELP, R.string.menu_help, HelpFragment::class)
|
||||||
list += NavTarget(TARGET_FEEDBACK, R.string.menu_feedback, FeedbackFragment::class)
|
list += NavTarget(TARGET_FEEDBACK, R.string.menu_feedback, FeedbackFragment::class)
|
||||||
list += NavTarget(TARGET_MESSAGES_DETAILS, R.string.menu_message, MessageFragment::class)
|
list += NavTarget(TARGET_MESSAGES_DETAILS, R.string.menu_message, MessageFragment::class)
|
||||||
|
list += NavTarget(TARGET_WEB_PUSH, R.string.menu_web_push, WebPushFragment::class)
|
||||||
list += NavTarget(DRAWER_ITEM_DEBUG, R.string.menu_debug, DebugFragment::class)
|
list += NavTarget(DRAWER_ITEM_DEBUG, R.string.menu_debug, DebugFragment::class)
|
||||||
|
|
||||||
list
|
list
|
||||||
|
@ -14,6 +14,8 @@ import pl.szczodrzynski.edziennik.data.api.szkolny.adapter.TimeAdapter
|
|||||||
import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.SignatureInterceptor
|
import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.SignatureInterceptor
|
||||||
import pl.szczodrzynski.edziennik.data.api.szkolny.request.EventShareRequest
|
import pl.szczodrzynski.edziennik.data.api.szkolny.request.EventShareRequest
|
||||||
import pl.szczodrzynski.edziennik.data.api.szkolny.request.ServerSyncRequest
|
import pl.szczodrzynski.edziennik.data.api.szkolny.request.ServerSyncRequest
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.szkolny.request.WebPushRequest
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.szkolny.response.WebPushResponse
|
||||||
import pl.szczodrzynski.edziennik.data.db.modules.events.EventFull
|
import pl.szczodrzynski.edziennik.data.db.modules.events.EventFull
|
||||||
import pl.szczodrzynski.edziennik.data.db.modules.profiles.ProfileFull
|
import pl.szczodrzynski.edziennik.data.db.modules.profiles.ProfileFull
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
@ -121,4 +123,34 @@ class SzkolnyApi(val app: App) {
|
|||||||
eventId = event.id
|
eventId = event.id
|
||||||
)).execute()
|
)).execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun pairBrowser(browserId: String?, pairToken: String?): List<WebPushResponse.Browser> {
|
||||||
|
val response = api.webPush(WebPushRequest(
|
||||||
|
action = "pairBrowser",
|
||||||
|
deviceId = app.deviceId,
|
||||||
|
browserId = browserId,
|
||||||
|
pairToken = pairToken
|
||||||
|
)).execute().body()
|
||||||
|
|
||||||
|
return response?.data?.browsers ?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun listBrowsers(): List<WebPushResponse.Browser> {
|
||||||
|
val response = api.webPush(WebPushRequest(
|
||||||
|
action = "listBrowsers",
|
||||||
|
deviceId = app.deviceId
|
||||||
|
)).execute().body()
|
||||||
|
|
||||||
|
return response?.data?.browsers ?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unpairBrowser(browserId: String): List<WebPushResponse.Browser> {
|
||||||
|
val response = api.webPush(WebPushRequest(
|
||||||
|
action = "unpairBrowser",
|
||||||
|
deviceId = app.deviceId,
|
||||||
|
browserId = browserId
|
||||||
|
)).execute().body()
|
||||||
|
|
||||||
|
return response?.data?.browsers ?: emptyList()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,10 @@ package pl.szczodrzynski.edziennik.data.api.szkolny
|
|||||||
|
|
||||||
import pl.szczodrzynski.edziennik.data.api.szkolny.request.EventShareRequest
|
import pl.szczodrzynski.edziennik.data.api.szkolny.request.EventShareRequest
|
||||||
import pl.szczodrzynski.edziennik.data.api.szkolny.request.ServerSyncRequest
|
import pl.szczodrzynski.edziennik.data.api.szkolny.request.ServerSyncRequest
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.szkolny.request.WebPushRequest
|
||||||
import pl.szczodrzynski.edziennik.data.api.szkolny.response.ApiResponse
|
import pl.szczodrzynski.edziennik.data.api.szkolny.response.ApiResponse
|
||||||
import pl.szczodrzynski.edziennik.data.api.szkolny.response.ServerSyncResponse
|
import pl.szczodrzynski.edziennik.data.api.szkolny.response.ServerSyncResponse
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.szkolny.response.WebPushResponse
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.POST
|
import retrofit2.http.POST
|
||||||
@ -19,4 +21,7 @@ interface SzkolnyService {
|
|||||||
|
|
||||||
@POST("share")
|
@POST("share")
|
||||||
fun shareEvent(@Body request: EventShareRequest): Call<ApiResponse<Nothing>>
|
fun shareEvent(@Body request: EventShareRequest): Call<ApiResponse<Nothing>>
|
||||||
|
|
||||||
|
@POST("webPush")
|
||||||
|
fun webPush(@Body request: WebPushRequest): Call<ApiResponse<WebPushResponse>>
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2019-12-19.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.data.api.szkolny.request
|
||||||
|
|
||||||
|
data class WebPushRequest(
|
||||||
|
|
||||||
|
val action: String,
|
||||||
|
val deviceId: String,
|
||||||
|
|
||||||
|
val browserId: String? = null,
|
||||||
|
val pairToken: String? = null
|
||||||
|
)
|
@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2019-12-19.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.data.api.szkolny.response
|
||||||
|
|
||||||
|
data class WebPushResponse(val browsers: List<Browser>) {
|
||||||
|
data class Browser(
|
||||||
|
val id: Int,
|
||||||
|
val browserId: String,
|
||||||
|
val userAgent: String,
|
||||||
|
val dateRegistered: String
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2019-12-19.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.ui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import pl.szczodrzynski.edziennik.App
|
||||||
|
import pl.szczodrzynski.edziennik.databinding.TemplateListItemBinding
|
||||||
|
import pl.szczodrzynski.edziennik.onClick
|
||||||
|
|
||||||
|
class TemplateAdapter(
|
||||||
|
val context: Context,
|
||||||
|
val onItemClick: ((item: TemplateItem) -> Unit)? = null,
|
||||||
|
val onItemButtonClick: ((item: TemplateItem) -> Unit)? = null
|
||||||
|
) : RecyclerView.Adapter<TemplateAdapter.ViewHolder>() {
|
||||||
|
|
||||||
|
private val app by lazy { context.applicationContext as App }
|
||||||
|
|
||||||
|
var items = listOf<TemplateItem>()
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
val inflater = LayoutInflater.from(parent.context)
|
||||||
|
val view = TemplateListItemBinding.inflate(inflater, parent, false)
|
||||||
|
return ViewHolder(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val item = items[position]
|
||||||
|
val b = holder.b
|
||||||
|
|
||||||
|
onItemClick?.let { listener ->
|
||||||
|
b.root.onClick { listener(item) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/*b.someButton.visibility = if (buttonVisible) View.VISIBLE else View.GONE
|
||||||
|
onItemButtonClick?.let { listener ->
|
||||||
|
b.someButton.onClick { listener(item) }
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = items.size
|
||||||
|
|
||||||
|
class ViewHolder(val b: TemplateListItemBinding) : RecyclerView.ViewHolder(b.root)
|
||||||
|
|
||||||
|
data class TemplateItem(val text: String)
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2019-12-19.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 me.dm7.barcodescanner.zxing.ZXingScannerView
|
||||||
|
import pl.szczodrzynski.edziennik.App
|
||||||
|
import pl.szczodrzynski.edziennik.R
|
||||||
|
import pl.szczodrzynski.edziennik.dp
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
|
class QrScannerDialog(
|
||||||
|
val activity: AppCompatActivity,
|
||||||
|
val onCodeScanned: (text: String) -> Unit,
|
||||||
|
val onShowListener: ((tag: String) -> Unit)? = null,
|
||||||
|
val onDismissListener: ((tag: String) -> Unit)? = null
|
||||||
|
) : CoroutineScope {
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "QrScannerDialog"
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var app: App
|
||||||
|
private lateinit var scannerView: ZXingScannerView
|
||||||
|
private lateinit var dialog: AlertDialog
|
||||||
|
|
||||||
|
private val job = Job()
|
||||||
|
override val coroutineContext: CoroutineContext
|
||||||
|
get() = job + Dispatchers.Main
|
||||||
|
|
||||||
|
init { run {
|
||||||
|
if (activity.isFinishing)
|
||||||
|
return@run
|
||||||
|
onShowListener?.invoke(TAG)
|
||||||
|
app = activity.applicationContext as App
|
||||||
|
scannerView = ZXingScannerView(activity)
|
||||||
|
scannerView.setPadding(0, 16.dp, 0, 0)
|
||||||
|
dialog = MaterialAlertDialogBuilder(activity)
|
||||||
|
.setTitle(R.string.qr_scanner_dialog_title)
|
||||||
|
.setView(scannerView)
|
||||||
|
.setPositiveButton(R.string.close) { dialog, _ ->
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
.setOnDismissListener {
|
||||||
|
onDismissListener?.invoke(TAG)
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
|
||||||
|
scannerView.setResultHandler {
|
||||||
|
scannerView.stopCamera()
|
||||||
|
dialog.dismiss()
|
||||||
|
onCodeScanned(it.text)
|
||||||
|
}
|
||||||
|
scannerView.startCamera()
|
||||||
|
}}
|
||||||
|
}
|
@ -5,25 +5,34 @@ import android.view.LayoutInflater
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.navigation.NavController
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import androidx.navigation.Navigation
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
import pl.szczodrzynski.edziennik.App
|
import pl.szczodrzynski.edziennik.App
|
||||||
import pl.szczodrzynski.edziennik.R
|
|
||||||
import pl.szczodrzynski.edziennik.MainActivity
|
import pl.szczodrzynski.edziennik.MainActivity
|
||||||
|
import pl.szczodrzynski.edziennik.R
|
||||||
import pl.szczodrzynski.edziennik.databinding.FragmentTemplateBinding
|
import pl.szczodrzynski.edziennik.databinding.FragmentTemplateBinding
|
||||||
import pl.szczodrzynski.edziennik.utils.Themes
|
import pl.szczodrzynski.edziennik.utils.Themes
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
class TemplateFragment : Fragment() {
|
class TemplateFragment : Fragment(), CoroutineScope {
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "TemplateFragment"
|
||||||
|
}
|
||||||
|
|
||||||
private lateinit var app: App
|
private lateinit var app: App
|
||||||
private lateinit var activity: MainActivity
|
private lateinit var activity: MainActivity
|
||||||
private lateinit var b: FragmentTemplateBinding
|
private lateinit var b: FragmentTemplateBinding
|
||||||
private val navController: NavController by lazy { Navigation.findNavController(b.root) }
|
|
||||||
|
private val job: Job = Job()
|
||||||
|
override val coroutineContext: CoroutineContext
|
||||||
|
get() = job + Dispatchers.Main
|
||||||
|
|
||||||
|
// local/private variables go here
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
activity = (getActivity() as MainActivity?) ?: return null
|
activity = (getActivity() as MainActivity?) ?: return null
|
||||||
if (context == null)
|
context ?: return null
|
||||||
return null
|
|
||||||
app = activity.application as App
|
app = activity.application as App
|
||||||
context!!.theme.applyStyle(Themes.appTheme, true)
|
context!!.theme.applyStyle(Themes.appTheme, true)
|
||||||
if (app.profile == null)
|
if (app.profile == null)
|
||||||
|
@ -34,8 +34,7 @@ class NotificationsFragment : Fragment() {
|
|||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
activity = (getActivity() as MainActivity?) ?: return null
|
activity = (getActivity() as MainActivity?) ?: return null
|
||||||
if (context == null)
|
context ?: return null
|
||||||
return null
|
|
||||||
app = activity.application as App
|
app = activity.application as App
|
||||||
context!!.theme.applyStyle(Themes.appTheme, true)
|
context!!.theme.applyStyle(Themes.appTheme, true)
|
||||||
if (app.profile == null)
|
if (app.profile == null)
|
||||||
|
@ -53,7 +53,6 @@ import pl.szczodrzynski.edziennik.sync.SyncWorker;
|
|||||||
import pl.szczodrzynski.edziennik.ui.dialogs.changelog.ChangelogDialog;
|
import pl.szczodrzynski.edziennik.ui.dialogs.changelog.ChangelogDialog;
|
||||||
import pl.szczodrzynski.edziennik.ui.dialogs.settings.ProfileRemoveDialog;
|
import pl.szczodrzynski.edziennik.ui.dialogs.settings.ProfileRemoveDialog;
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment;
|
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment;
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.webpush.WebPushConfigActivity;
|
|
||||||
import pl.szczodrzynski.edziennik.utils.Themes;
|
import pl.szczodrzynski.edziennik.utils.Themes;
|
||||||
import pl.szczodrzynski.edziennik.utils.Utils;
|
import pl.szczodrzynski.edziennik.utils.Utils;
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date;
|
import pl.szczodrzynski.edziennik.utils.models.Date;
|
||||||
@ -718,8 +717,7 @@ public class SettingsNewFragment extends MaterialAboutFragment {
|
|||||||
.color(IconicsColor.colorInt(iconColor))
|
.color(IconicsColor.colorInt(iconColor))
|
||||||
)
|
)
|
||||||
.setOnClickAction(() -> {
|
.setOnClickAction(() -> {
|
||||||
Intent i = new Intent(activity, WebPushConfigActivity.class);
|
activity.loadTarget(MainActivity.TARGET_WEB_PUSH, null);
|
||||||
startActivity(i);
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -53,8 +53,7 @@ class TimetableFragment : Fragment(), CoroutineScope {
|
|||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
activity = (getActivity() as MainActivity?) ?: return null
|
activity = (getActivity() as MainActivity?) ?: return null
|
||||||
if (context == null)
|
context ?: return null
|
||||||
return null
|
|
||||||
app = activity.application as App
|
app = activity.application as App
|
||||||
job = Job()
|
job = Job()
|
||||||
context!!.theme.applyStyle(Themes.appTheme, true)
|
context!!.theme.applyStyle(Themes.appTheme, true)
|
||||||
|
@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2019-12-19.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.ui.modules.webpush
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import pl.szczodrzynski.edziennik.App
|
||||||
|
import pl.szczodrzynski.edziennik.R
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.szkolny.response.WebPushResponse
|
||||||
|
import pl.szczodrzynski.edziennik.databinding.WebPushBrowserItemBinding
|
||||||
|
import pl.szczodrzynski.edziennik.onClick
|
||||||
|
import pl.szczodrzynski.edziennik.setText
|
||||||
|
|
||||||
|
class WebPushBrowserAdapter(
|
||||||
|
val context: Context,
|
||||||
|
val onItemClick: ((browser: WebPushResponse.Browser) -> Unit)? = null,
|
||||||
|
val onUnpairButtonClick: ((browser: WebPushResponse.Browser) -> Unit)? = null
|
||||||
|
) : RecyclerView.Adapter<WebPushBrowserAdapter.ViewHolder>() {
|
||||||
|
|
||||||
|
private val app by lazy { context.applicationContext as App }
|
||||||
|
|
||||||
|
var items = listOf<WebPushResponse.Browser>()
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
val inflater = LayoutInflater.from(parent.context)
|
||||||
|
val view = WebPushBrowserItemBinding.inflate(inflater, parent, false)
|
||||||
|
return ViewHolder(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val browser = items[position]
|
||||||
|
val b = holder.b
|
||||||
|
|
||||||
|
onItemClick?.let { listener ->
|
||||||
|
b.root.onClick { listener(browser) }
|
||||||
|
}
|
||||||
|
|
||||||
|
b.browserName.text = browser.userAgent
|
||||||
|
b.datePaired.setText(R.string.web_push_date_paired_format, browser.dateRegistered)
|
||||||
|
|
||||||
|
onUnpairButtonClick?.let { listener ->
|
||||||
|
b.unpair.onClick { listener(browser) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = items.size
|
||||||
|
|
||||||
|
class ViewHolder(val b: WebPushBrowserItemBinding) : RecyclerView.ViewHolder(b.root)
|
||||||
|
}
|
@ -0,0 +1,149 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2019-12-19.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.ui.modules.webpush
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import pl.szczodrzynski.edziennik.*
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.szkolny.response.WebPushResponse
|
||||||
|
import pl.szczodrzynski.edziennik.databinding.WebPushFragmentBinding
|
||||||
|
import pl.szczodrzynski.edziennik.ui.dialogs.QrScannerDialog
|
||||||
|
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
|
||||||
|
import pl.szczodrzynski.edziennik.utils.Themes
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
|
class WebPushFragment : Fragment(), CoroutineScope {
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "TemplateFragment"
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var app: App
|
||||||
|
private lateinit var activity: MainActivity
|
||||||
|
private lateinit var b: WebPushFragmentBinding
|
||||||
|
|
||||||
|
private val job: Job = Job()
|
||||||
|
override val coroutineContext: CoroutineContext
|
||||||
|
get() = job + Dispatchers.Main
|
||||||
|
|
||||||
|
private lateinit var adapter: WebPushBrowserAdapter
|
||||||
|
private val api by lazy {
|
||||||
|
SzkolnyApi(app)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
activity = (getActivity() as MainActivity?) ?: return null
|
||||||
|
context ?: return null
|
||||||
|
app = activity.application as App
|
||||||
|
context!!.theme.applyStyle(Themes.appTheme, true)
|
||||||
|
if (app.profile == null)
|
||||||
|
return inflater.inflate(R.layout.fragment_loading, container, false)
|
||||||
|
// activity, context and profile is valid
|
||||||
|
b = WebPushFragmentBinding.inflate(inflater)
|
||||||
|
return b.root
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DefaultLocale")
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
// TODO check if app, activity, b can be null
|
||||||
|
if (app.profile == null || !isAdded)
|
||||||
|
return
|
||||||
|
|
||||||
|
b.scanQrCode.onClick {
|
||||||
|
val result = ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA)
|
||||||
|
if (result == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
QrScannerDialog(activity, {
|
||||||
|
b.tokenEditText.setText(it.crc32().toString(36).toUpperCase())
|
||||||
|
pairBrowser(browserId = it)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.CAMERA), 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.tokenAccept.onClick {
|
||||||
|
val pairToken = b.tokenEditText.text.toString().toUpperCase()
|
||||||
|
if (!"[0-9A-Z]{3,13}".toRegex().matches(pairToken)) {
|
||||||
|
b.tokenLayout.error = app.getString(R.string.web_push_token_invalid)
|
||||||
|
return@onClick
|
||||||
|
}
|
||||||
|
b.tokenLayout.error = null
|
||||||
|
b.tokenEditText.setText(pairToken)
|
||||||
|
pairBrowser(pairToken = pairToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter = WebPushBrowserAdapter(
|
||||||
|
activity,
|
||||||
|
onItemClick = null,
|
||||||
|
onUnpairButtonClick = {
|
||||||
|
unpairBrowser(it.browserId)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
launch {
|
||||||
|
val browsers = withContext(Dispatchers.Default) {
|
||||||
|
api.listBrowsers()
|
||||||
|
}
|
||||||
|
updateBrowserList(browsers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateBrowserList(browsers: List<WebPushResponse.Browser>) {
|
||||||
|
adapter.items = browsers
|
||||||
|
if (b.browsersView.adapter == null) {
|
||||||
|
b.browsersView.adapter = adapter
|
||||||
|
b.browsersView.apply {
|
||||||
|
isNestedScrollingEnabled = false
|
||||||
|
setHasFixedSize(true)
|
||||||
|
layoutManager = LinearLayoutManager(context)
|
||||||
|
addItemDecoration(SimpleDividerItemDecoration(context))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
|
|
||||||
|
if (browsers.isNotEmpty()) {
|
||||||
|
b.browsersView.visibility = View.VISIBLE
|
||||||
|
b.browsersNoData.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
b.browsersView.visibility = View.GONE
|
||||||
|
b.browsersNoData.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun pairBrowser(browserId: String? = null, pairToken: String? = null) {
|
||||||
|
b.scanQrCode.isEnabled = false
|
||||||
|
b.tokenAccept.isEnabled = false
|
||||||
|
b.tokenEditText.isEnabled = false
|
||||||
|
b.tokenEditText.clearFocus()
|
||||||
|
launch {
|
||||||
|
val browsers = withContext(Dispatchers.Default) {
|
||||||
|
api.pairBrowser(browserId, pairToken)
|
||||||
|
}
|
||||||
|
b.scanQrCode.isEnabled = true
|
||||||
|
b.tokenAccept.isEnabled = true
|
||||||
|
b.tokenEditText.isEnabled = true
|
||||||
|
updateBrowserList(browsers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unpairBrowser(browserId: String) {
|
||||||
|
launch {
|
||||||
|
val browsers = withContext(Dispatchers.Default) {
|
||||||
|
api.unpairBrowser(browserId)
|
||||||
|
}
|
||||||
|
updateBrowserList(browsers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
52
app/src/main/res/drawable/ic_web_push_no_browsers.xml
Normal file
52
app/src/main/res/drawable/ic_web_push_no_browsers.xml
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright (c) Kuba Szczodrzyński 2019-12-19.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="64dp"
|
||||||
|
android:height="64dp"
|
||||||
|
android:viewportWidth="64"
|
||||||
|
android:viewportHeight="64">
|
||||||
|
<path
|
||||||
|
android:pathData="M56,46.668L8,46.668L8,9.332C8,8.5938 8.5977,8 9.332,8L54.668,8C55.4063,8 56,8.5938 56,9.332Z"
|
||||||
|
android:fillColor="#455A64"
|
||||||
|
android:fillAlpha="1"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:strokeColor="#00000000"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M10.668,10.668L53.332,10.668L53.332,37.332L10.668,37.332Z"
|
||||||
|
android:fillColor="#BBDEFB"
|
||||||
|
android:fillAlpha="1"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:strokeColor="#00000000"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M0,53.332L64,53.332L64,56L0,56Z"
|
||||||
|
android:fillColor="#B0BEC5"
|
||||||
|
android:fillAlpha="1"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:strokeColor="#00000000"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M56,40L8,40L0,53.332L64,53.332Z"
|
||||||
|
android:fillColor="#CFD8DC"
|
||||||
|
android:fillAlpha="1"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:strokeColor="#00000000"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M10.668,42.668L53.332,42.668L57.332,50.668L6.668,50.668Z"
|
||||||
|
android:fillColor="#546E7A"
|
||||||
|
android:fillAlpha="1"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:strokeColor="#00000000"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M22.0586,50.668L21.332,53.332L42.668,53.332L41.9414,50.668Z"
|
||||||
|
android:fillColor="#90A4AE"
|
||||||
|
android:fillAlpha="1"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:strokeColor="#00000000"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M37.332,24C37.332,21.668 38.668,21.332 38.668,21.332L45.332,21.332C44.3047,18.9805 41.3984,17.332 38.668,17.332C35.9336,17.332 33.5938,18.9805 32.5625,21.332L21.332,21.332C19.8594,21.332 18.668,22.5273 18.668,24C18.668,25.4727 19.8594,26.668 21.332,26.668L32.5625,26.668C33.5938,29.0195 35.9336,30.668 38.668,30.668C41.3984,30.668 44.3047,29.0195 45.332,26.668L38.668,26.668C38.668,26.668 37.332,26.332 37.332,24Z"
|
||||||
|
android:fillColor="#1976D2"
|
||||||
|
android:fillAlpha="1"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:strokeColor="#00000000"/>
|
||||||
|
</vector>
|
18
app/src/main/res/layout/template_list_item.xml
Normal file
18
app/src/main/res/layout/template_list_item.xml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (c) Kuba Szczodrzyński 2019-12-19.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="?selectableItemBackground">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</layout>
|
48
app/src/main/res/layout/web_push_browser_item.xml
Normal file
48
app/src/main/res/layout/web_push_browser_item.xml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (c) Kuba Szczodrzyński 2019-12-19.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<layout xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:background="?selectableItemBackground"
|
||||||
|
android:baselineAligned="false">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/browserName"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="@style/NavView.TextView.Medium"
|
||||||
|
tools:text="Firefox 71.0 @ Windows 7 64-bit"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/datePaired"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:text="Połączono 2019-12-12 19:54:09"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/unpair"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:text="@string/web_push_unpair_button" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</layout>
|
111
app/src/main/res/layout/web_push_fragment.xml
Normal file
111
app/src/main/res/layout/web_push_fragment.xml
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (c) Kuba Szczodrzyński 2019-12-19.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<androidx.core.widget.NestedScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Przekazywanie powiadomień pozwala na połączenie komputera, na którym będą pokazywane powiadomienia otrzymane w aplikacji Szkolny.eu na telefonie.\n\nDzięki temu będziesz zawsze wiedział wszystko na bieżąco, nie patrząc nawet na swój telefon."/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:fontFamily="sans-serif-light"
|
||||||
|
android:text="Jak połączyć komputer z aplikacją?"
|
||||||
|
android:textSize="24sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="1. Na komputerze otwórz stronę http://szkolny.eu/wp\n2. Zezwól stronie na wyświetlanie powiadomień dot. danych z dziennika.\n3. Kliknij przycisk "Skanuj" poniżej lub przepisz token ze strony."/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:fontFamily="sans-serif-light"
|
||||||
|
android:text="Sparuj przeglądarkę"
|
||||||
|
android:textSize="24sp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/scanQrCode"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Skanuj kod QR" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/tokenLayout"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:hint="Token">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/tokenEditText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:text="1A22IT" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/tokenAccept"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:text="OK" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:fontFamily="sans-serif-light"
|
||||||
|
android:text="Połączone przeglądarki"
|
||||||
|
android:textSize="24sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/browsersNoData"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:drawableTop="@drawable/ic_web_push_no_browsers"
|
||||||
|
android:drawablePadding="16dp"
|
||||||
|
android:fontFamily="sans-serif-light"
|
||||||
|
android:text="Brak połączonych przeglądarek"
|
||||||
|
android:textSize="18sp"/>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/browsersView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
tools:visibility="visible"
|
||||||
|
tools:listitem="@layout/web_push_browser_item" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
</layout>
|
@ -1104,4 +1104,9 @@
|
|||||||
<string name="event_details_added_by_self_format">%1$s przez Ciebie</string>
|
<string name="event_details_added_by_self_format">%1$s przez Ciebie</string>
|
||||||
<string name="event_details_shared_by_format">{cmd-share-variant} %1$s przez %2$s</string>
|
<string name="event_details_shared_by_format">{cmd-share-variant} %1$s przez %2$s</string>
|
||||||
<string name="event_details_shared_by_self_format">{cmd-share-variant} %1$s przez Ciebie</string>
|
<string name="event_details_shared_by_self_format">{cmd-share-variant} %1$s przez Ciebie</string>
|
||||||
|
<string name="web_push_token_invalid">Token nie wygląda na prawidłowy</string>
|
||||||
|
<string name="menu_web_push">Przekazywanie powiadomień</string>
|
||||||
|
<string name="qr_scanner_dialog_title">Skanuj kod QR</string>
|
||||||
|
<string name="web_push_unpair_button">Odłącz</string>
|
||||||
|
<string name="web_push_date_paired_format">Połączono %s</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user