diff --git a/.circleci/config.yml b/.circleci/config.yml index d4e59be1..f6646b52 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,11 +7,11 @@ references: container_config: &container_config docker: - - image: circleci/android:api-28-alpha + - image: circleci/android:api-28 working_directory: *workspace_root environment: environment: - JVM_OPTS: -Xmx3200m + _JAVA_OPTS: -Xmx2048m attach_workspace: &attach_workspace attach_workspace: diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5b281926..d670ef44 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -50,7 +50,16 @@ android:name=".ui.modules.timetablewidget.TimetableWidgetConfigureActivity" android:noHistory="true" android:excludeFromRecents="true" - android:theme="@style/WulkanowyTheme.TimetableWidgetAccount"> + android:theme="@style/WulkanowyTheme.WidgetAccountSwitcher"> + + + + + @@ -72,6 +81,17 @@ android:resource="@xml/provider_widget_timetable" /> + + + + + + + > + + @Inject + lateinit var presenter: LuckyNumberWidgetConfigurePresenter + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setResult(RESULT_CANCELED) + setContentView(R.layout.activity_widget_configure) + + intent.extras.let { + presenter.onAttachView(this, it?.getInt(EXTRA_APPWIDGET_ID)) + } + } + + override fun initView() { + widgetConfigureRecycler.apply { + adapter = configureAdapter + layoutManager = SmoothScrollLinearLayoutManager(context) + } + configureAdapter.setOnItemClickListener { presenter.onItemSelect(it) } + } + + override fun updateData(data: List) { + configureAdapter.updateDataSet(data) + } + + override fun updateLuckyNumberWidget(widgetId: Int) { + sendBroadcast(Intent(this, LuckyNumberWidgetProvider::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, Toast.LENGTH_LONG).show() + } + + override fun finishView() { + finish() + } + + override fun openLoginView() { + startActivity(LoginActivity.getStartIntent(this)) + } + + override fun onDestroy() { + super.onDestroy() + presenter.onDetachView() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureItem.kt new file mode 100644 index 00000000..bba0974b --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureItem.kt @@ -0,0 +1,54 @@ +package io.github.wulkanowy.ui.modules.luckynumberwidget + +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 io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetConfigureItem +import kotlinx.android.extensions.LayoutContainer +import kotlinx.android.synthetic.main.item_account.* + +class LuckyNumberWidgetConfigureItem(var student: Student, val isCurrent: Boolean) : + AbstractFlexibleItem() { + + override fun getLayoutRes() = R.layout.item_account + + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): ViewHolder { + return ViewHolder(view, adapter) + } + + @SuppressLint("SetTextI18n") + override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ViewHolder, position: Int, payloads: MutableList) { + 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 + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt new file mode 100644 index 00000000..3f808f09 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt @@ -0,0 +1,62 @@ +package io.github.wulkanowy.ui.modules.luckynumberwidget + +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.luckynumberwidget.LuckyNumberWidgetProvider.Companion.createWidgetKey +import io.github.wulkanowy.utils.SchedulersProvider +import javax.inject.Inject + +class LuckyNumberWidgetConfigurePresenter @Inject constructor( + private val errorHandler: ErrorHandler, + private val schedulers: SchedulersProvider, + private val studentRepository: StudentRepository, + private val sharedPref: SharedPrefHelper +) : BasePresenter(errorHandler) { + + private var appWidgetId: Int? = null + + fun onAttachView(view: LuckyNumberWidgetConfigureView, appWidgetId: Int?) { + super.onAttachView(view) + this.appWidgetId = appWidgetId + view.initView() + loadData() + } + + fun onItemSelect(item: AbstractFlexibleItem<*>) { + if (item is LuckyNumberWidgetConfigureItem) { + registerStudent(item.student) + } + } + + private fun loadData() { + disposable.add(studentRepository.getSavedStudents(false) + .map { it to appWidgetId?.let { id -> sharedPref.getLong(createWidgetKey(id), 0) } } + .map { (students, currentStudentId) -> + students.map { student -> LuckyNumberWidgetConfigureItem(student, student.id == currentStudentId) } + } + .subscribeOn(schedulers.backgroundThread) + .observeOn(schedulers.mainThread) + .subscribe({ + when { + it.isEmpty() -> view?.openLoginView() + it.size == 1 -> registerStudent(it.single().student) + else -> view?.updateData(it) + } + }, { errorHandler.dispatch(it) })) + } + + private fun registerStudent(student: Student) { + appWidgetId?.also { + sharedPref.putLong(createWidgetKey(it), student.id) + view?.apply { + updateLuckyNumberWidget(it) + setSuccessResult(it) + } + } + view?.finishView() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureView.kt new file mode 100644 index 00000000..49c3f1dc --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureView.kt @@ -0,0 +1,19 @@ +package io.github.wulkanowy.ui.modules.luckynumberwidget + +import io.github.wulkanowy.ui.base.BaseView +import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetConfigureItem + +interface LuckyNumberWidgetConfigureView : BaseView { + + fun initView() + + fun updateData(data: List) + + fun updateLuckyNumberWidget(widgetId: Int) + + fun setSuccessResult(widgetId: Int) + + fun finishView() + + fun openLoginView() +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt new file mode 100644 index 00000000..40352279 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt @@ -0,0 +1,180 @@ +package io.github.wulkanowy.ui.modules.luckynumberwidget + +import android.annotation.TargetApi +import android.app.PendingIntent +import android.appwidget.AppWidgetManager +import android.appwidget.AppWidgetManager.ACTION_APPWIDGET_DELETED +import android.appwidget.AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED +import android.appwidget.AppWidgetManager.ACTION_APPWIDGET_UPDATE +import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID +import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_IDS +import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_OPTIONS +import android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT +import android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.os.Build +import android.view.View.GONE +import android.view.View.VISIBLE +import android.widget.RemoteViews +import dagger.android.AndroidInjection +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.SharedPrefHelper +import io.github.wulkanowy.data.db.entities.LuckyNumber +import io.github.wulkanowy.data.repositories.luckynumber.LuckyNumberRepository +import io.github.wulkanowy.data.repositories.semester.SemesterRepository +import io.github.wulkanowy.data.repositories.student.StudentRepository +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.utils.SchedulersProvider +import io.reactivex.Maybe +import timber.log.Timber +import javax.inject.Inject + +class LuckyNumberWidgetProvider : BroadcastReceiver() { + + @Inject + lateinit var studentRepository: StudentRepository + + @Inject + lateinit var semesterRepository: SemesterRepository + + @Inject + lateinit var luckyNumberRepository: LuckyNumberRepository + + @Inject + lateinit var schedulers: SchedulersProvider + + @Inject + lateinit var appWidgetManager: AppWidgetManager + + @Inject + lateinit var sharedPref: SharedPrefHelper + + companion object { + fun createWidgetKey(appWidgetId: Int) = "lucky_number_widget_$appWidgetId" + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + override fun onReceive(context: Context, intent: Intent) { + AndroidInjection.inject(this, context) + when (intent.action) { + ACTION_APPWIDGET_UPDATE -> onUpdate(context, intent) + ACTION_APPWIDGET_DELETED -> onDelete(intent) + ACTION_APPWIDGET_OPTIONS_CHANGED -> onOptionsChange(context, intent) + } + } + + private fun onUpdate(context: Context, intent: Intent) { + intent.getIntArrayExtra(EXTRA_APPWIDGET_IDS).forEach { appWidgetId -> + RemoteViews(context.packageName, R.layout.widget_luckynumber).apply { + setTextViewText(R.id.luckyNumberWidgetNumber, + getLuckyNumber(sharedPref.getLong(createWidgetKey(appWidgetId), 0), appWidgetId)?.luckyNumber?.toString() ?: "#" + ) + setOnClickPendingIntent(R.id.luckyNumberWidgetContainer, + PendingIntent.getActivity(context, 2, MainActivity.getStartIntent(context).apply { + putExtra(EXTRA_START_MENU_INDEX, 4) + }, PendingIntent.FLAG_UPDATE_CURRENT)) + }.also { + setStyles(it, intent) + appWidgetManager.updateAppWidget(appWidgetId, it) + } + } + } + + private fun onDelete(intent: Intent) { + intent.getIntExtra(EXTRA_APPWIDGET_ID, 0).let { + if (it != 0) sharedPref.delete(createWidgetKey(it)) + } + } + + private fun getLuckyNumber(studentId: Long, appWidgetId: Int): LuckyNumber? { + return try { + studentRepository.isStudentSaved() + .filter { true } + .flatMap { studentRepository.getSavedStudents().toMaybe() } + .flatMap { students -> + students.singleOrNull { student -> student.id == studentId } + .let { student -> + if (student != null) { + Maybe.just(student) + } else { + studentRepository.getCurrentStudent(false) + .toMaybe() + .doOnSuccess { sharedPref.putLong(createWidgetKey(appWidgetId), it.id) } + } + } + } + .flatMap { semesterRepository.getCurrentSemester(it).toMaybe() } + .flatMap { luckyNumberRepository.getLuckyNumber(it) } + .subscribeOn(schedulers.backgroundThread) + .blockingGet() + } catch (e: Exception) { + Timber.e(e, "An error has occurred in lucky number provider") + null + } + } + + private fun onOptionsChange(context: Context, intent: Intent) { + intent.extras?.let { extras -> + RemoteViews(context.packageName, R.layout.widget_luckynumber).apply { + setStyles(this, intent) + appWidgetManager.updateAppWidget(extras.getInt(EXTRA_APPWIDGET_ID), this) + } + } + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + private fun setStyles(views: RemoteViews, intent: Intent) { + val options = intent.extras?.getBundle(EXTRA_APPWIDGET_OPTIONS) + + val maxWidth = options?.getInt(OPTION_APPWIDGET_MAX_WIDTH) ?: 150 + val maxHeight = options?.getInt(OPTION_APPWIDGET_MAX_HEIGHT) ?: 40 + + Timber.d("New luckynumber widget measurement: %dx%d", maxWidth, maxHeight) + + when { + // 1x1 + maxWidth < 150 && maxHeight < 110 -> { + Timber.d("Luckynumber widget size: 1x1") + views.run { + setViewVisibility(R.id.luckyNumberWidgetImageTop, GONE) + setViewVisibility(R.id.luckyNumberWidgetImageLeft, GONE) + setViewVisibility(R.id.luckyNumberWidgetTitle, GONE) + setViewVisibility(R.id.luckyNumberWidgetNumber, VISIBLE) + } + } + // 1x2 + maxWidth < 150 && maxHeight > 110 -> { + Timber.d("Luckynumber widget size: 1x2") + views.run { + setViewVisibility(R.id.luckyNumberWidgetImageTop, VISIBLE) + setViewVisibility(R.id.luckyNumberWidgetImageLeft, GONE) + setViewVisibility(R.id.luckyNumberWidgetTitle, GONE) + setViewVisibility(R.id.luckyNumberWidgetNumber, VISIBLE) + } + } + // 2x1 + maxWidth >= 150 && maxHeight <= 110 -> { + Timber.d("Luckynumber widget size: 2x1") + views.run { + setViewVisibility(R.id.luckyNumberWidgetImageTop, GONE) + setViewVisibility(R.id.luckyNumberWidgetImageLeft, VISIBLE) + setViewVisibility(R.id.luckyNumberWidgetTitle, GONE) + setViewVisibility(R.id.luckyNumberWidgetNumber, VISIBLE) + } + } + // 2x2 and bigger + else -> { + Timber.d("Luckynumber widget size: 2x2 and bigger") + views.run { + setViewVisibility(R.id.luckyNumberWidgetImageTop, GONE) + setViewVisibility(R.id.luckyNumberWidgetImageLeft, GONE) + setViewVisibility(R.id.luckyNumberWidgetTitle, VISIBLE) + setViewVisibility(R.id.luckyNumberWidgetNumber, VISIBLE) + } + } + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureActivity.kt index 37d0571a..468567f1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureActivity.kt @@ -15,7 +15,7 @@ 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 kotlinx.android.synthetic.main.activity_widget_configure.* import javax.inject.Inject class TimetableWidgetConfigureActivity : BaseActivity(), TimetableWidgetConfigureView { @@ -29,7 +29,7 @@ class TimetableWidgetConfigureActivity : BaseActivity(), TimetableWidgetConfigur override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setResult(RESULT_CANCELED) - setContentView(R.layout.activity_timetable_widget_configure) + setContentView(R.layout.activity_widget_configure) intent.extras.let { presenter.onAttachView(this, it?.getInt(EXTRA_APPWIDGET_ID), it?.getBoolean(EXTRA_FROM_PROVIDER)) @@ -37,7 +37,7 @@ class TimetableWidgetConfigureActivity : BaseActivity(), TimetableWidgetConfigur } override fun initView() { - timetableWidgetConfigureRecycler.apply { + widgetConfigureRecycler.apply { adapter = configureAdapter layoutManager = SmoothScrollLinearLayoutManager(context) } diff --git a/app/src/main/res/drawable/background_rounded_corner.xml b/app/src/main/res/drawable/background_rounded_corner.xml new file mode 100644 index 00000000..116973b5 --- /dev/null +++ b/app/src/main/res/drawable/background_rounded_corner.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_widget_clover_24dp.png b/app/src/main/res/drawable/ic_widget_clover_24dp.png new file mode 100644 index 00000000..b1a7832a Binary files /dev/null and b/app/src/main/res/drawable/ic_widget_clover_24dp.png differ diff --git a/app/src/main/res/drawable/img_luckynumber_widget_preview.png b/app/src/main/res/drawable/img_luckynumber_widget_preview.png new file mode 100644 index 00000000..539b0a59 Binary files /dev/null and b/app/src/main/res/drawable/img_luckynumber_widget_preview.png differ diff --git a/app/src/main/res/layout/activity_timetable_widget_configure.xml b/app/src/main/res/layout/activity_widget_configure.xml similarity index 85% rename from app/src/main/res/layout/activity_timetable_widget_configure.xml rename to app/src/main/res/layout/activity_widget_configure.xml index 2a95e01d..192776e6 100644 --- a/app/src/main/res/layout/activity_timetable_widget_configure.xml +++ b/app/src/main/res/layout/activity_widget_configure.xml @@ -6,7 +6,7 @@ android:layout_height="wrap_content"> + + + + + + + + + + diff --git a/app/src/main/res/values/api_endpoints.xml b/app/src/main/res/values/api_endpoints.xml index ff8cd23c..e07f2b4b 100644 --- a/app/src/main/res/values/api_endpoints.xml +++ b/app/src/main/res/values/api_endpoints.xml @@ -14,6 +14,6 @@ https://edu.gdansk.pl https://umt.tarnow.pl https://resman.pl - https://fakelog.cf + http://fakelog.cf diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 685bda9f..58699c9e 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -41,7 +41,7 @@ @android:color/white -