Add account picker for timetable widget (#314)

Close #281
This commit is contained in:
Rafał Borcz 2019-04-08 00:18:45 +02:00 committed by Mikołaj Pich
parent aa6dcaff94
commit c18877466f
26 changed files with 437 additions and 102 deletions

View File

@ -45,13 +45,22 @@
android:configChanges="orientation|screenSize" android:configChanges="orientation|screenSize"
android:label="@string/send_message_title" android:label="@string/send_message_title"
android:theme="@style/WulkanowyTheme.NoActionBar" /> android:theme="@style/WulkanowyTheme.NoActionBar" />
<activity
android:name=".ui.modules.timetablewidget.TimetableWidgetConfigureActivity"
android:noHistory="true"
android:excludeFromRecents="true"
android:theme="@style/WulkanowyTheme.TimetableWidgetAccount">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
<service <service
android:name=".services.widgets.TimetableWidgetService" android:name=".services.widgets.TimetableWidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS" /> android:permission="android.permission.BIND_REMOTEVIEWS" />
<receiver <receiver
android:name=".ui.widgets.timetable.TimetableWidgetProvider" android:name=".ui.modules.timetablewidget.TimetableWidgetProvider"
android:exported="true" android:exported="true"
android:label="@string/timetable_title"> android:label="@string/timetable_title">
<intent-filter> <intent-filter>

View File

@ -6,18 +6,16 @@ import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
@SuppressLint("ApplySharedPref")
class SharedPrefHelper @Inject constructor(private val sharedPref: SharedPreferences) { class SharedPrefHelper @Inject constructor(private val sharedPref: SharedPreferences) {
@SuppressLint("ApplySharedPref")
fun putLong(key: String, value: Long, sync: Boolean = false) { fun putLong(key: String, value: Long, sync: Boolean = false) {
sharedPref.edit().putLong(key, value).apply { sharedPref.edit().putLong(key, value).apply {
if (sync) commit() else apply() if (sync) commit() else apply()
} }
} }
fun getLong(key: String, defaultValue: Long): Long { fun getLong(key: String, defaultValue: Long) = sharedPref.getLong(key, defaultValue)
return sharedPref.getLong(key, defaultValue)
}
fun delete(key: String) { fun delete(key: String) {
sharedPref.edit().remove(key).apply() sharedPref.edit().remove(key).apply()

View File

@ -9,7 +9,8 @@ import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainModule import io.github.wulkanowy.ui.modules.main.MainModule
import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity
import io.github.wulkanowy.ui.modules.splash.SplashActivity import io.github.wulkanowy.ui.modules.splash.SplashActivity
import io.github.wulkanowy.ui.widgets.timetable.TimetableWidgetProvider import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetConfigureActivity
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider
@Module @Module
internal abstract class BuilderModule { internal abstract class BuilderModule {
@ -29,6 +30,9 @@ internal abstract class BuilderModule {
@ContributesAndroidInjector @ContributesAndroidInjector
abstract fun bindMessageSendActivity(): SendMessageActivity abstract fun bindMessageSendActivity(): SendMessageActivity
@ContributesAndroidInjector
abstract fun bindTimetableWidgetAccountActivity(): TimetableWidgetConfigureActivity
@ContributesAndroidInjector @ContributesAndroidInjector
abstract fun bindTimetableWidgetProvider(): TimetableWidgetProvider abstract fun bindTimetableWidgetProvider(): TimetableWidgetProvider
} }

View File

@ -7,7 +7,7 @@ import io.github.wulkanowy.data.db.SharedPrefHelper
import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.data.repositories.timetable.TimetableRepository import io.github.wulkanowy.data.repositories.timetable.TimetableRepository
import io.github.wulkanowy.ui.widgets.timetable.TimetableWidgetFactory import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetFactory
import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.SchedulersProvider
import javax.inject.Inject import javax.inject.Inject

View File

@ -0,0 +1,79 @@
package io.github.wulkanowy.ui.modules.timetablewidget
import android.appwidget.AppWidgetManager.ACTION_APPWIDGET_UPDATE
import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID
import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_IDS
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import android.widget.Toast.LENGTH_LONG
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.EXTRA_FROM_PROVIDER
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.activity_timetable_widget_configure.*
import javax.inject.Inject
class TimetableWidgetConfigureActivity : BaseActivity(), TimetableWidgetConfigureView {
@Inject
lateinit var configureAdapter: FlexibleAdapter<AbstractFlexibleItem<*>>
@Inject
lateinit var presenter: TimetableWidgetConfigurePresenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setResult(RESULT_CANCELED)
setContentView(R.layout.activity_timetable_widget_configure)
intent.extras.let {
presenter.onAttachView(this, it?.getInt(EXTRA_APPWIDGET_ID), it?.getBoolean(EXTRA_FROM_PROVIDER))
}
}
override fun initView() {
timetableWidgetConfigureRecycler.apply {
adapter = configureAdapter
layoutManager = SmoothScrollLinearLayoutManager(context)
}
configureAdapter.setOnItemClickListener { presenter.onItemSelect(it) }
}
override fun updateData(data: List<TimetableWidgetConfigureItem>) {
configureAdapter.updateDataSet(data)
}
override fun updateTimetableWidget(widgetId: Int) {
sendBroadcast(Intent(this, TimetableWidgetProvider::class.java)
.apply {
action = ACTION_APPWIDGET_UPDATE
putExtra(EXTRA_APPWIDGET_IDS, intArrayOf(widgetId))
})
}
override fun setSuccessResult(widgetId: Int) {
setResult(RESULT_OK, Intent().apply { putExtra(EXTRA_APPWIDGET_ID, widgetId) })
}
override fun showError(text: String, error: Throwable) {
Toast.makeText(this, text, LENGTH_LONG).show()
}
override fun finishView() {
finish()
}
override fun openLoginView() {
startActivity(LoginActivity.getStartIntent(this))
}
override fun onDestroy() {
super.onDestroy()
presenter.onDetachView()
}
}

View File

@ -0,0 +1,53 @@
package io.github.wulkanowy.ui.modules.timetablewidget
import android.annotation.SuppressLint
import android.view.View
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_account.*
class TimetableWidgetConfigureItem(val student: Student, private val isCurrent: Boolean) :
AbstractFlexibleItem<TimetableWidgetConfigureItem.ViewHolder>() {
override fun getLayoutRes() = R.layout.item_account
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ViewHolder {
return ViewHolder(view, adapter)
}
@SuppressLint("SetTextI18n")
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
holder.apply {
accountItemName.text = "${student.studentName} ${student.className}"
accountItemSchool.text = student.schoolName
accountItemImage.setBackgroundResource(if (isCurrent) R.drawable.ic_account_circular_border else 0)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as TimetableWidgetConfigureItem
if (student != other.student) return false
return true
}
override fun hashCode(): Int {
var result = student.hashCode()
result = 31 * result + student.id.toInt()
return result
}
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer {
override val containerView: View
get() = contentView
}
}

View File

@ -0,0 +1,65 @@
package io.github.wulkanowy.ui.modules.timetablewidget
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.db.SharedPrefHelper
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getStudentWidgetKey
import io.github.wulkanowy.utils.SchedulersProvider
import javax.inject.Inject
class TimetableWidgetConfigurePresenter @Inject constructor(
private val errorHandler: ErrorHandler,
private val schedulers: SchedulersProvider,
private val studentRepository: StudentRepository,
private val sharedPref: SharedPrefHelper
) : BasePresenter<TimetableWidgetConfigureView>(errorHandler) {
private var appWidgetId: Int? = null
private var isFromProvider = false
fun onAttachView(view: TimetableWidgetConfigureView, appWidgetId: Int?, isFromProvider: Boolean?) {
super.onAttachView(view)
this.appWidgetId = appWidgetId
this.isFromProvider = isFromProvider ?: false
view.initView()
loadData()
}
fun onItemSelect(item: AbstractFlexibleItem<*>) {
if (item is TimetableWidgetConfigureItem) {
registerStudent(item.student)
}
}
private fun loadData() {
disposable.add(studentRepository.getSavedStudents(false)
.map { it to appWidgetId?.let { id -> sharedPref.getLong(getStudentWidgetKey(id), 0) } }
.map { (students, currentStudentId) ->
students.map { student -> TimetableWidgetConfigureItem(student, student.id == currentStudentId) }
}
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.subscribe({
when {
it.isEmpty() -> view?.openLoginView()
it.size == 1 && !isFromProvider -> registerStudent(it.single().student)
else -> view?.updateData(it)
}
}, { errorHandler.dispatch(it) }))
}
private fun registerStudent(student: Student) {
appWidgetId?.also {
sharedPref.putLong(getStudentWidgetKey(it), student.id)
view?.apply {
updateTimetableWidget(it)
setSuccessResult(it)
}
}
view?.finishView()
}
}

View File

@ -0,0 +1,18 @@
package io.github.wulkanowy.ui.modules.timetablewidget
import io.github.wulkanowy.ui.base.BaseView
interface TimetableWidgetConfigureView : BaseView {
fun initView()
fun updateData(data: List<TimetableWidgetConfigureItem>)
fun updateTimetableWidget(widgetId: Int)
fun setSuccessResult(widgetId: Int)
fun finishView()
fun openLoginView()
}

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.widgets.timetable package io.github.wulkanowy.ui.modules.timetablewidget
import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Paint.ANTI_ALIAS_FLAG import android.graphics.Paint.ANTI_ALIAS_FLAG
@ -15,9 +16,11 @@ import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.data.repositories.timetable.TimetableRepository import io.github.wulkanowy.data.repositories.timetable.TimetableRepository
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getDateWidgetKey
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getStudentWidgetKey
import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.toFormattedString
import io.reactivex.Single import io.reactivex.Maybe
import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate
import timber.log.Timber import timber.log.Timber
@ -48,28 +51,37 @@ class TimetableWidgetFactory(
override fun onDestroy() {} override fun onDestroy() {}
override fun onDataSetChanged() { override fun onDataSetChanged() {
intent?.action?.let { LocalDate.ofEpochDay(sharedPref.getLong(it, 0)) } intent?.extras?.getInt(EXTRA_APPWIDGET_ID)?.let { appWidgetId ->
?.let { date -> val date = LocalDate.ofEpochDay(sharedPref.getLong(getDateWidgetKey(appWidgetId), 0))
try { val studentId = sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0)
lessons = studentRepository.isStudentSaved()
.flatMap { isSaved -> lessons = try {
if (isSaved) { studentRepository.isStudentSaved()
studentRepository.getCurrentStudent() .filter { true }
.flatMap { semesterRepository.getCurrentSemester(it) } .flatMap { studentRepository.getSavedStudents().toMaybe() }
.flatMap { timetableRepository.getTimetable(it, date, date) } .flatMap {
} else Single.just(emptyList()) if (studentId == 0L) throw IllegalArgumentException("Student id is 0")
it.singleOrNull { student -> student.id == studentId }
.let { student ->
if (student != null) Maybe.just(student)
else Maybe.empty()
} }
}
.flatMap { semesterRepository.getCurrentSemester(it).toMaybe() }
.flatMap { timetableRepository.getTimetable(it, date, date).toMaybe() }
.map { item -> item.sortedBy { it.number } } .map { item -> item.sortedBy { it.number } }
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.blockingGet() .blockingGet(emptyList())
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "An error has occurred while downloading data for the widget") Timber.e(e, "An error has occurred in timetable widget factory")
emptyList()
} }
} }
} }
override fun getViewAt(position: Int): RemoteViews? { override fun getViewAt(position: Int): RemoteViews? {
if (position == INVALID_POSITION || lessons.getOrNull(position) === null) return null if (position == INVALID_POSITION || lessons.getOrNull(position) == null) return null
return RemoteViews(context.packageName, R.layout.item_widget_timetable).apply { return RemoteViews(context.packageName, R.layout.item_widget_timetable).apply {
lessons[position].let { lessons[position].let {

View File

@ -1,4 +1,4 @@
package io.github.wulkanowy.ui.widgets.timetable package io.github.wulkanowy.ui.modules.timetablewidget
import android.app.PendingIntent import android.app.PendingIntent
import android.app.PendingIntent.FLAG_UPDATE_CURRENT import android.app.PendingIntent.FLAG_UPDATE_CURRENT
@ -10,21 +10,28 @@ import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_IDS
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.widget.RemoteViews import android.widget.RemoteViews
import dagger.android.AndroidInjection import dagger.android.AndroidInjection
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.SharedPrefHelper import io.github.wulkanowy.data.db.SharedPrefHelper
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.services.widgets.TimetableWidgetService import io.github.wulkanowy.services.widgets.TimetableWidgetService
import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainActivity.Companion.EXTRA_START_MENU_INDEX import io.github.wulkanowy.ui.modules.main.MainActivity.Companion.EXTRA_START_MENU_INDEX
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.nextOrSameSchoolDay import io.github.wulkanowy.utils.nextOrSameSchoolDay
import io.github.wulkanowy.utils.nextSchoolDay import io.github.wulkanowy.utils.nextSchoolDay
import io.github.wulkanowy.utils.previousSchoolDay import io.github.wulkanowy.utils.previousSchoolDay
import io.github.wulkanowy.utils.shortcutWeekDayName
import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.toFormattedString
import io.github.wulkanowy.utils.weekDayName import io.reactivex.Maybe
import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDate.now import org.threeten.bp.LocalDate.now
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class TimetableWidgetProvider : BroadcastReceiver() { class TimetableWidgetProvider : BroadcastReceiver() {
@ -32,13 +39,21 @@ class TimetableWidgetProvider : BroadcastReceiver() {
@Inject @Inject
lateinit var appWidgetManager: AppWidgetManager lateinit var appWidgetManager: AppWidgetManager
@Inject
lateinit var studentRepository: StudentRepository
@Inject @Inject
lateinit var sharedPref: SharedPrefHelper lateinit var sharedPref: SharedPrefHelper
@Inject
lateinit var schedulers: SchedulersProvider
@Inject @Inject
lateinit var analytics: FirebaseAnalyticsHelper lateinit var analytics: FirebaseAnalyticsHelper
companion object { companion object {
const val EXTRA_FROM_PROVIDER = "extraFromProvider"
const val EXTRA_TOGGLED_WIDGET_ID = "extraToggledWidget" const val EXTRA_TOGGLED_WIDGET_ID = "extraToggledWidget"
const val EXTRA_BUTTON_TYPE = "extraButtonType" const val EXTRA_BUTTON_TYPE = "extraButtonType"
@ -49,7 +64,9 @@ class TimetableWidgetProvider : BroadcastReceiver() {
const val BUTTON_RESET = "buttonReset" const val BUTTON_RESET = "buttonReset"
fun createWidgetKey(appWidgetId: Int) = "timetable_widget_$appWidgetId" fun getDateWidgetKey(appWidgetId: Int) = "timetable_widget_date_$appWidgetId"
fun getStudentWidgetKey(appWidgetId: Int) = "timetable_widget_student_$appWidgetId"
} }
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
@ -63,12 +80,14 @@ class TimetableWidgetProvider : BroadcastReceiver() {
private fun onUpdate(context: Context, intent: Intent) { private fun onUpdate(context: Context, intent: Intent) {
if (intent.getStringExtra(EXTRA_BUTTON_TYPE) === null) { if (intent.getStringExtra(EXTRA_BUTTON_TYPE) === null) {
intent.getIntArrayExtra(EXTRA_APPWIDGET_IDS)?.forEach { appWidgetId -> intent.getIntArrayExtra(EXTRA_APPWIDGET_IDS)?.forEach { appWidgetId ->
updateWidget(context, appWidgetId, now().nextOrSameSchoolDay) val student = getStudent(sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0), appWidgetId)
updateWidget(context, appWidgetId, now().nextOrSameSchoolDay, student)
} }
} else { } else {
val buttonType = intent.getStringExtra(EXTRA_BUTTON_TYPE) val buttonType = intent.getStringExtra(EXTRA_BUTTON_TYPE)
val toggledWidgetId = intent.getIntExtra(EXTRA_TOGGLED_WIDGET_ID, 0) val toggledWidgetId = intent.getIntExtra(EXTRA_TOGGLED_WIDGET_ID, 0)
val savedDate = LocalDate.ofEpochDay(sharedPref.getLong(createWidgetKey(toggledWidgetId), 0)) val student = getStudent(sharedPref.getLong(getStudentWidgetKey(toggledWidgetId), 0), toggledWidgetId)
val savedDate = LocalDate.ofEpochDay(sharedPref.getLong(getDateWidgetKey(toggledWidgetId), 0))
val date = when (buttonType) { val date = when (buttonType) {
BUTTON_RESET -> now().nextOrSameSchoolDay BUTTON_RESET -> now().nextOrSameSchoolDay
BUTTON_NEXT -> savedDate.nextSchoolDay BUTTON_NEXT -> savedDate.nextSchoolDay
@ -76,35 +95,46 @@ class TimetableWidgetProvider : BroadcastReceiver() {
else -> now().nextOrSameSchoolDay else -> now().nextOrSameSchoolDay
} }
if (!buttonType.isNullOrBlank()) analytics.logEvent("changed_timetable_widget_day", "button" to buttonType) if (!buttonType.isNullOrBlank()) analytics.logEvent("changed_timetable_widget_day", "button" to buttonType)
updateWidget(context, toggledWidgetId, date) updateWidget(context, toggledWidgetId, date, student)
} }
} }
private fun onDelete(intent: Intent) { private fun onDelete(intent: Intent) {
intent.getIntExtra(EXTRA_APPWIDGET_ID, 0).let { intent.getIntExtra(EXTRA_APPWIDGET_ID, 0).let {
if (it != 0) sharedPref.delete(createWidgetKey(it)) if (it != 0) {
sharedPref.apply {
delete(getStudentWidgetKey(it))
delete(getDateWidgetKey(it))
}
}
} }
} }
private fun updateWidget(context: Context, appWidgetId: Int, date: LocalDate) { private fun updateWidget(context: Context, appWidgetId: Int, date: LocalDate, student: Student?) {
RemoteViews(context.packageName, R.layout.widget_timetable).apply { RemoteViews(context.packageName, R.layout.widget_timetable).apply {
setEmptyView(R.id.timetableWidgetList, R.id.timetableWidgetEmpty) setEmptyView(R.id.timetableWidgetList, R.id.timetableWidgetEmpty)
setTextViewText(R.id.timetableWidgetDay, date.weekDayName.capitalize()) setTextViewText(R.id.timetableWidgetDate, "${date.shortcutWeekDayName.capitalize()} ${date.toFormattedString()}")
setTextViewText(R.id.timetableWidgetDate, date.toFormattedString()) setTextViewText(R.id.timetableWidgetName, student?.studentName ?: context.getString(R.string.all_no_data))
setRemoteAdapter(R.id.timetableWidgetList, Intent(context, TimetableWidgetService::class.java) setRemoteAdapter(R.id.timetableWidgetList, Intent(context, TimetableWidgetService::class.java)
.apply { action = createWidgetKey(appWidgetId) }) .apply { putExtra(EXTRA_APPWIDGET_ID, appWidgetId) })
setOnClickPendingIntent(R.id.timetableWidgetNext, createNavIntent(context, appWidgetId, appWidgetId, BUTTON_NEXT)) setOnClickPendingIntent(R.id.timetableWidgetNext, createNavIntent(context, appWidgetId, appWidgetId, BUTTON_NEXT))
setOnClickPendingIntent(R.id.timetableWidgetPrev, createNavIntent(context, -appWidgetId, appWidgetId, BUTTON_PREV)) setOnClickPendingIntent(R.id.timetableWidgetPrev, createNavIntent(context, -appWidgetId, appWidgetId, BUTTON_PREV))
createNavIntent(context, Int.MAX_VALUE - appWidgetId, appWidgetId, BUTTON_RESET).also { createNavIntent(context, Int.MAX_VALUE - appWidgetId, appWidgetId, BUTTON_RESET).let {
setOnClickPendingIntent(R.id.timetableWidgetDate, it) setOnClickPendingIntent(R.id.timetableWidgetDate, it)
setOnClickPendingIntent(R.id.timetableWidgetDay, it) setOnClickPendingIntent(R.id.timetableWidgetName, it)
} }
setOnClickPendingIntent(R.id.timetableWidgetAccount, PendingIntent.getActivity(context, -Int.MAX_VALUE + appWidgetId,
Intent(context, TimetableWidgetConfigureActivity::class.java).apply {
addFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK)
putExtra(EXTRA_APPWIDGET_ID, appWidgetId)
putExtra(EXTRA_FROM_PROVIDER, true)
}, FLAG_UPDATE_CURRENT))
setPendingIntentTemplate(R.id.timetableWidgetList, setPendingIntentTemplate(R.id.timetableWidgetList,
PendingIntent.getActivity(context, 1, MainActivity.getStartIntent(context).apply { PendingIntent.getActivity(context, 1, MainActivity.getStartIntent(context).apply {
putExtra(EXTRA_START_MENU_INDEX, 3) putExtra(EXTRA_START_MENU_INDEX, 3)
}, FLAG_UPDATE_CURRENT)) }, FLAG_UPDATE_CURRENT))
}.also { }.also {
sharedPref.putLong(createWidgetKey(appWidgetId), date.toEpochDay(), true) sharedPref.putLong(getDateWidgetKey(appWidgetId), date.toEpochDay(), true)
appWidgetManager.apply { appWidgetManager.apply {
notifyAppWidgetViewDataChanged(appWidgetId, R.id.timetableWidgetList) notifyAppWidgetViewDataChanged(appWidgetId, R.id.timetableWidgetList)
updateAppWidget(appWidgetId, it) updateAppWidget(appWidgetId, it)
@ -120,4 +150,29 @@ class TimetableWidgetProvider : BroadcastReceiver() {
putExtra(EXTRA_TOGGLED_WIDGET_ID, appWidgetId) putExtra(EXTRA_TOGGLED_WIDGET_ID, appWidgetId)
}, FLAG_UPDATE_CURRENT) }, FLAG_UPDATE_CURRENT)
} }
private fun getStudent(id: Long, appWidgetId: Int): Student? {
return try {
studentRepository.isStudentSaved()
.filter { true }
.flatMap { studentRepository.getSavedStudents(false).toMaybe() }
.flatMap { students ->
students.singleOrNull { student -> student.id == id }
.let { student ->
if (student != null) {
Maybe.just(student)
} else {
studentRepository.getCurrentStudent(false)
.toMaybe()
.doOnSuccess { sharedPref.putLong(getStudentWidgetKey(appWidgetId), it.id) }
}
}
}
.subscribeOn(schedulers.backgroundThread)
.blockingGet()
} catch (e: Exception) {
Timber.e(e, "An error has occurred in timetable widget provider")
null
}
}
} }

View File

@ -99,6 +99,9 @@ inline val LocalDate.previousOrSameSchoolDay: LocalDate
inline val LocalDate.weekDayName: String inline val LocalDate.weekDayName: String
get() = this.format(ofPattern("EEEE", Locale.getDefault())) get() = this.format(ofPattern("EEEE", Locale.getDefault()))
inline val LocalDate.shortcutWeekDayName: String
get() = this.format(ofPattern("EEE", Locale.getDefault()))
inline val LocalDate.monday: LocalDate inline val LocalDate.monday: LocalDate
get() = this.with(MONDAY) get() = this.with(MONDAY)

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 596 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 922 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="280dp"
android:layout_height="wrap_content">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/timetableWidgetConfigureTitle"
android:layout_width="match_parent"
android:layout_height="64dp"
android:paddingStart="24dp"
android:paddingLeft="24dp"
android:paddingEnd="24dp"
android:paddingRight="24dp"
android:text="@string/account_title"
android:textColor="?android:textColorPrimary"
android:textSize="20sp"
android:textStyle="bold"
app:firstBaselineToTopHeight="40dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/timetableWidgetConfigureRecycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/timetableWidgetConfigureTitle"
android:layout_marginBottom="16dp"
android:overScrollMode="never"
tools:itemCount="3"
tools:listitem="@layout/item_account" />
</RelativeLayout>

View File

@ -33,7 +33,8 @@
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
android:textSize="16sp" android:textSize="16sp"
tools:text="@tools:sample/lorem/random" /> tools:text="@tools:sample/lorem/random"
android:textColor="?android:textColorSecondary"/>
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/accountItemSchool" android:id="@+id/accountItemSchool"
@ -47,6 +48,7 @@
android:layout_toRightOf="@id/accountItemImage" android:layout_toRightOf="@id/accountItemImage"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
android:textColor="?android:textColorSecondary"
android:textSize="12sp" android:textSize="12sp"
tools:text="@tools:sample/lorem/random" /> tools:text="@tools:sample/lorem/random" />
</RelativeLayout> </RelativeLayout>

View File

@ -7,7 +7,7 @@
android:background="@drawable/ic_all_divider" android:background="@drawable/ic_all_divider"
android:minHeight="45dp" android:minHeight="45dp"
android:orientation="horizontal" android:orientation="horizontal"
tools:context=".ui.widgets.timetable.TimetableWidgetFactory"> tools:context=".ui.modules.timetablewidget.TimetableWidgetFactory">
<TextView <TextView
android:id="@+id/timetableWidgetItemNumber" android:id="@+id/timetableWidgetItemNumber"

View File

@ -4,80 +4,87 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@android:color/white" android:background="@android:color/white"
tools:context=".ui.widgets.timetable.TimetableWidgetProvider"> tools:context=".ui.modules.timetablewidget.TimetableWidgetProvider">
<RelativeLayout <RelativeLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/timetable_widget_bar_height" android:layout_height="56dp"
android:background="@color/colorPrimary"> android:background="@color/colorPrimary">
<ImageButton <LinearLayout
android:id="@+id/timetableWidgetPrev"
android:layout_width="70dp"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="5dp"
android:layout_marginLeft="5dp"
android:backgroundTint="@color/colorPrimaryDark"
android:contentDescription="@string/all_prev"
android:src="@drawable/ic_widget_chevron_24dp"
tools:targetApi="lollipop" />
<TextView
android:id="@+id/timetableWidgetDay"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignTop="@+id/timetableWidgetPrev" android:layout_centerVertical="true"
android:layout_centerHorizontal="true" android:layout_toStartOf="@id/timetableWidgetAccount"
android:layout_toStartOf="@id/timetableWidgetNext" android:layout_toLeftOf="@id/timetableWidgetAccount"
android:layout_toLeftOf="@id/timetableWidgetNext" android:layout_toEndOf="@id/timetableWidgetNext"
android:layout_toEndOf="@id/timetableWidgetPrev" android:layout_toRightOf="@id/timetableWidgetNext"
android:layout_toRightOf="@id/timetableWidgetPrev" android:orientation="vertical">
android:gravity="center_horizontal"
android:maxLines="1"
android:textColor="@android:color/white"
android:textSize="15sp"
tools:text="Poniedziałek" />
<TextView <TextView
android:id="@+id/timetableWidgetDate" android:id="@+id/timetableWidgetDate"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/timetableWidgetDay" android:layout_marginStart="10dp"
android:layout_centerInParent="true" android:layout_marginLeft="10dp"
android:layout_marginTop="3dp"
android:layout_toStartOf="@id/timetableWidgetNext"
android:layout_toLeftOf="@id/timetableWidgetNext"
android:layout_toEndOf="@id/timetableWidgetPrev"
android:layout_toRightOf="@id/timetableWidgetPrev"
android:gravity="center_horizontal"
android:maxLines="1" android:maxLines="1"
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:textSize="14sp" android:textSize="15sp"
tools:text="12.03.2019" /> tools:text="Pon. 30.03.2019" />
<TextView
android:id="@+id/timetableWidgetName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:maxLines="1"
android:textColor="@android:color/white"
android:textSize="13sp"
tools:text="Jan Kowalski" />
</LinearLayout>
<ImageButton
android:id="@+id/timetableWidgetAccount"
android:layout_width="50dp"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:background="?android:selectableItemBackground"
android:contentDescription="@string/account_title"
android:src="@drawable/ic_widget_account" />
<ImageButton
android:id="@+id/timetableWidgetPrev"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:backgroundTint="@color/colorPrimaryDark"
android:contentDescription="@string/all_prev"
android:src="@drawable/ic_widget_chevron_24dp"
tools:targetApi="lollipop" />
<ImageButton <ImageButton
android:id="@+id/timetableWidgetNext" android:id="@+id/timetableWidgetNext"
android:layout_width="70dp" android:layout_width="50dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_marginEnd="5dp" android:layout_toEndOf="@id/timetableWidgetPrev"
android:layout_marginRight="5dp" android:layout_toRightOf="@id/timetableWidgetPrev"
android:backgroundTint="@color/colorPrimaryDark" android:backgroundTint="@color/colorPrimaryDark"
android:contentDescription="@string/all_next" android:contentDescription="@string/all_next"
android:rotation="180" android:rotation="180"
android:src="@drawable/ic_widget_chevron_24dp" android:src="@drawable/ic_widget_chevron_24dp"
tools:targetApi="lollipop" /> tools:targetApi="lollipop" />
</RelativeLayout> </RelativeLayout>
<ListView <ListView
android:id="@+id/timetableWidgetList" android:id="@+id/timetableWidgetList"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginTop="@dimen/timetable_widget_bar_height" android:layout_marginTop="56dp"
tools:listitem="@layout/item_widget_timetable" /> tools:listitem="@layout/item_widget_timetable" />
<TextView <TextView

View File

@ -24,9 +24,4 @@
<item name="about_libraries_dividerDark_openSource">@color/about_libraries_dividerDark_openSource_dark</item> <item name="about_libraries_dividerDark_openSource">@color/about_libraries_dividerDark_openSource_dark</item>
<item name="about_libraries_dividerLight_openSource">@color/about_libraries_dividerLight_openSource_dark</item> <item name="about_libraries_dividerLight_openSource">@color/about_libraries_dividerLight_openSource_dark</item>
</style> </style>
<style name="WulkanowyTheme.NoActionBar" parent="WulkanowyTheme">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
</resources> </resources>

View File

@ -233,8 +233,6 @@
<!--Timetable Widget--> <!--Timetable Widget-->
<string name="widget_timetable_no_items">Brak lekcji</string> <string name="widget_timetable_no_items">Brak lekcji</string>
<string name="widget_timetable_today">Dziś</string>
<string name="widget_timetable_tomorrow">Jutro</string>
<!--Preferences--> <!--Preferences-->

View File

@ -2,7 +2,4 @@
<!--BottomNav--> <!--BottomNav-->
<dimen name="bottom_navigation_margin_top_active" tools:override="true">8dp</dimen> <dimen name="bottom_navigation_margin_top_active" tools:override="true">8dp</dimen>
<dimen name="bottom_navigation_margin_top_inactive" tools:override="true">8dp</dimen> <dimen name="bottom_navigation_margin_top_inactive" tools:override="true">8dp</dimen>
<!--Timetable Widget-->
<dimen name="timetable_widget_bar_height">60dp</dimen>
</resources> </resources>

View File

@ -218,8 +218,6 @@
<!--Timetable Widget--> <!--Timetable Widget-->
<string name="widget_timetable_no_items">No lessons</string> <string name="widget_timetable_no_items">No lessons</string>
<string name="widget_timetable_today">Today</string>
<string name="widget_timetable_tomorrow">Tomorrow</string>
<!--Preferences--> <!--Preferences-->

View File

@ -40,4 +40,12 @@
<style name="WulkanowyTheme.ActionBar" parent="WulkanowyTheme"> <style name="WulkanowyTheme.ActionBar" parent="WulkanowyTheme">
<item name="colorControlNormal">@android:color/white</item> <item name="colorControlNormal">@android:color/white</item>
</style> </style>
<style name="WulkanowyTheme.TimetableWidgetAccount" parent="Theme.AppCompat.Light.Dialog">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:textColorPrimary">@android:color/primary_text_light</item>
<item name="android:textColorSecondary">@android:color/secondary_text_light</item>
<item name="android:textColorSecondaryInverse">@android:color/primary_text_dark</item>
</style>
</resources> </resources>

View File

@ -1,8 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:configure="io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetConfigureActivity"
android:initialLayout="@layout/widget_timetable" android:initialLayout="@layout/widget_timetable"
android:minWidth="150dp" android:minWidth="250dp"
android:minHeight="100dp" android:minHeight="110dp"
android:minResizeWidth="250dp"
android:minResizeHeight="110dp"
android:previewImage="@drawable/img_timetable_widget_preview" android:previewImage="@drawable/img_timetable_widget_preview"
android:resizeMode="horizontal|vertical" android:resizeMode="horizontal|vertical"
android:updatePeriodMillis="3600000" android:updatePeriodMillis="3600000"