From 840b21a21350d639d9871a31682a7b97bf5e205f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sun, 13 Jan 2019 23:25:07 +0100 Subject: [PATCH 001/121] Fix dialog state (#214) --- .../wulkanowy/ui/modules/attendance/AttendanceFragment.kt | 2 +- .../java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt | 3 ++- .../ui/modules/grade/details/GradeDetailsFragment.kt | 3 ++- .../github/wulkanowy/ui/modules/homework/HomeworkFragment.kt | 3 ++- .../java/io/github/wulkanowy/ui/modules/main/MainActivity.kt | 5 +++++ .../java/io/github/wulkanowy/ui/modules/note/NoteFragment.kt | 3 ++- .../wulkanowy/ui/modules/timetable/TimetableFragment.kt | 3 ++- 7 files changed, 16 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt index 3b60d8cb7..cdbde5075 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt @@ -130,7 +130,7 @@ class AttendanceFragment : BaseSessionFragment(), AttendanceView, MainView.MainC } override fun showAttendanceDialog(lesson: Attendance) { - AttendanceDialog.newInstance(lesson).show(fragmentManager, lesson.toString()) + (activity as? MainActivity)?.showDialogFragment(AttendanceDialog.newInstance(lesson)) } override fun openSummaryView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt index d39688d4f..dcafeb2ee 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt @@ -13,6 +13,7 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.ui.base.session.BaseSessionFragment +import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.setOnItemClickListener import kotlinx.android.synthetic.main.fragment_exam.* @@ -106,7 +107,7 @@ class ExamFragment : BaseSessionFragment(), ExamView, MainView.MainChildView, Ma } override fun showExamDialog(exam: Exam) { - ExamDialog.newInstance(exam).show(fragmentManager, exam.toString()) + (activity as? MainActivity)?.showDialogFragment(ExamDialog.newInstance(exam)) } override fun onSaveInstanceState(outState: Bundle) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsFragment.kt index e1bc1e9b2..01b18f4ba 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsFragment.kt @@ -20,6 +20,7 @@ import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.ui.base.session.BaseSessionFragment import io.github.wulkanowy.ui.modules.grade.GradeFragment import io.github.wulkanowy.ui.modules.grade.GradeView +import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.utils.setOnItemClickListener import kotlinx.android.synthetic.main.fragment_grade_details.* import javax.inject.Inject @@ -131,7 +132,7 @@ class GradeDetailsFragment : BaseSessionFragment(), GradeDetailsView, GradeView. } override fun showGradeDialog(grade: Grade) { - GradeDetailsDialog.newInstance(grade).show(fragmentManager, grade.toString()) + (activity as? MainActivity)?.showDialogFragment(GradeDetailsDialog.newInstance(grade)) } override fun onParentLoadData(semesterId: Int, forceRefresh: Boolean) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt index 135098fbd..4d9b74708 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt @@ -10,6 +10,7 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Homework import io.github.wulkanowy.ui.base.session.BaseSessionFragment +import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.setOnItemClickListener import kotlinx.android.synthetic.main.fragment_homework.* @@ -95,7 +96,7 @@ class HomeworkFragment : BaseSessionFragment(), HomeworkView, MainView.TitledVie } override fun showTimetableDialog(homework: Homework) { - HomeworkDialog.newInstance(homework).show(fragmentManager, homework.toString()) + (activity as? MainActivity)?.showDialogFragment(HomeworkDialog.newInstance(homework)) } override fun onSaveInstanceState(outState: Bundle) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt index 58c2f7c95..58517302a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt @@ -7,6 +7,7 @@ import android.view.Menu import android.view.MenuItem import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat +import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment import com.aurelhubert.ahbottomnavigation.AHBottomNavigation.TitleState.ALWAYS_SHOW import com.aurelhubert.ahbottomnavigation.AHBottomNavigationItem @@ -147,6 +148,10 @@ class MainActivity : BaseActivity(), MainView { (navController.currentStack?.get(0) as? MainView.MainChildView)?.onFragmentReselected() } + fun showDialogFragment(dialog: DialogFragment) { + navController.showDialogFragment(dialog) + } + fun pushView(fragment: Fragment) { navController.pushFragment(fragment) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteFragment.kt index 243f25757..9ae8f83a2 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteFragment.kt @@ -12,6 +12,7 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Note import io.github.wulkanowy.ui.base.session.BaseSessionFragment +import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.setOnItemClickListener import kotlinx.android.synthetic.main.fragment_note.* @@ -57,7 +58,7 @@ class NoteFragment : BaseSessionFragment(), NoteView, MainView.TitledView { } override fun showNoteDialog(note: Note) { - NoteDialog.newInstance(note).show(fragmentManager, note.toString()) + (activity as? MainActivity)?.showDialogFragment(NoteDialog.newInstance(note)) } override fun updateData(data: List) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt index faf17665a..71c2f70ba 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt @@ -10,6 +10,7 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.ui.base.session.BaseSessionFragment +import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.setOnItemClickListener import kotlinx.android.synthetic.main.fragment_timetable.* @@ -107,7 +108,7 @@ class TimetableFragment : BaseSessionFragment(), TimetableView, MainView.MainChi } override fun showTimetableDialog(lesson: Timetable) { - TimetableDialog.newInstance(lesson).show(fragmentManager, lesson.toString()) + (activity as? MainActivity)?.showDialogFragment(TimetableDialog.newInstance(lesson)) } override fun onSaveInstanceState(outState: Bundle) { From 28f1430be0d64f134b2327243178296cf4d468f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sun, 13 Jan 2019 23:53:01 +0100 Subject: [PATCH 002/121] Fix crash on Meizu devices (#215) --- app/src/main/res/layout/fragment_login_form.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/layout/fragment_login_form.xml b/app/src/main/res/layout/fragment_login_form.xml index a2ee86474..e21e476b9 100644 --- a/app/src/main/res/layout/fragment_login_form.xml +++ b/app/src/main/res/layout/fragment_login_form.xml @@ -69,7 +69,7 @@ android:layout_marginBottom="15dp" android:hint="@string/login_nickname_hint"> - - Date: Fri, 18 Jan 2019 12:41:11 +0100 Subject: [PATCH 003/121] Fix exam_no_items english translation (#217) Changed `exam_no_items` from **No exams in this week** to **No exams this week** --- app/src/main/res/values/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f6140ad04..be6cec495 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -91,7 +91,7 @@ Group Hours Changes - No lessons on this day + No lessons this day @@ -115,7 +115,7 @@ - No exams in this week + No exams this week Type Entry date From e6d60e670e2929748496936754a29171d432e382 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Fri, 18 Jan 2019 19:10:14 +0100 Subject: [PATCH 004/121] Fix crash on duplicate notes (#218) --- .../java/io/github/wulkanowy/ui/modules/note/NoteItem.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteItem.kt index 97635a2c8..71562fc49 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteItem.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteItem.kt @@ -46,11 +46,14 @@ class NoteItem(val note: Note) : AbstractFlexibleItem() { other as NoteItem if (note != other.note) return false + if (note.id != other.note.id) return false return true } override fun hashCode(): Int { - return note.hashCode() + var result = note.hashCode() + result = 31 * result + note.id.toInt() + return result } class ViewHolder(val view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer { From c5bab52fa2824060a6f1bb0fbae873ff3289e61d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sat, 19 Jan 2019 23:15:14 +0100 Subject: [PATCH 005/121] Add more logging (#219) --- .../ui/modules/about/AboutPresenter.kt | 5 +++ .../ui/modules/account/AccountPresenter.kt | 38 ++++++++++++++++--- .../modules/attendance/AttendancePresenter.kt | 13 ++++++- .../summary/AttendanceSummaryPresenter.kt | 18 ++++++++- .../ui/modules/exam/ExamPresenter.kt | 13 ++++++- .../ui/modules/grade/GradePresenter.kt | 14 ++++++- .../grade/details/GradeDetailsPresenter.kt | 27 +++++++++++-- .../grade/summary/GradeSummaryPresenter.kt | 5 +++ .../ui/modules/homework/HomeworkPresenter.kt | 12 +++++- .../ui/modules/login/LoginPresenter.kt | 3 ++ .../modules/login/form/LoginFormPresenter.kt | 9 ++++- .../login/options/LoginOptionsPresenter.kt | 9 ++++- .../wulkanowy/ui/modules/main/MainActivity.kt | 4 +- .../ui/modules/main/MainPresenter.kt | 22 +++++++++-- .../ui/modules/message/MessagePresenter.kt | 3 ++ .../preview/MessagePreviewPresenter.kt | 6 ++- .../message/tab/MessageTabPresenter.kt | 15 ++++++-- .../ui/modules/more/MorePresenter.kt | 5 +++ .../ui/modules/note/NotePresenter.kt | 16 ++++++-- .../ui/modules/settings/SettingsPresenter.kt | 5 ++- .../modules/timetable/TimetablePresenter.kt | 15 +++++++- 21 files changed, 221 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutPresenter.kt index 9a512f70f..fa421b60a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutPresenter.kt @@ -15,6 +15,11 @@ class AboutPresenter @Inject constructor( private val analytics: FirebaseAnalyticsHelper ) : BasePresenter(errorHandler) { + override fun onAttachView(view: AboutView) { + super.onAttachView(view) + Timber.i("About view is attached") + } + fun onExtraSelect(type: Libs.SpecialButton?) { view?.run { when (type) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt index e1459fe7d..c5f9b35dd 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt @@ -6,6 +6,7 @@ import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.SchedulersProvider import io.reactivex.Single +import timber.log.Timber import javax.inject.Inject class AccountPresenter @Inject constructor( @@ -16,19 +17,23 @@ class AccountPresenter @Inject constructor( override fun onAttachView(view: AccountView) { super.onAttachView(view) + Timber.i("Account dialog is attached") view.initView() loadData() } fun onAddSelected() { + Timber.i("Select add account") view?.openLoginView() } fun onRemoveSelected() { + Timber.i("Select remove account") view?.showConfirmDialog() } fun onLogoutConfirm() { + Timber.i("Attempt to logout current user ") disposable.add(studentRepository.getCurrentStudent() .flatMapCompletable { studentRepository.logoutStudent(it) } .andThen(studentRepository.getSavedStudents(false)) @@ -41,31 +46,54 @@ class AccountPresenter @Inject constructor( .doFinally { view?.dismissView() } .subscribe({ view?.apply { - if (it.isEmpty()) openClearLoginView() - else recreateView() + if (it.isEmpty()) { + Timber.i("Logout result: Open login view") + openClearLoginView() + } else { + Timber.i("Logout result: Switch to another student") + recreateView() + } } - }, { errorHandler.dispatch(it) })) + }, { + Timber.i("Logout result: An exception occurred") + errorHandler.dispatch(it) + })) } fun onItemSelected(item: AbstractFlexibleItem<*>) { if (item is AccountItem) { + Timber.i("Select student item ${item.student.id}") if (item.student.isCurrent) { view?.dismissView() } else { + Timber.i("Attempt to change a student") disposable.add(studentRepository.switchStudent(item.student) .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) - .subscribe({ view?.recreateView() }, { errorHandler.dispatch(it) })) + .subscribe({ + Timber.i("Change a student result: Success") + view?.recreateView() + }, { + Timber.i("Change a student result: An exception occurred") + errorHandler.dispatch(it) + })) } } } private fun loadData() { + Timber.i("Loading account data started") disposable.add(studentRepository.getSavedStudents(false) .map { it.map { item -> AccountItem(item) } } .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) - .subscribe({ view?.updateData(it) }, { errorHandler.dispatch(it) })) + .subscribe({ + Timber.i("Loading account result: Success") + view?.updateData(it) + }, { + Timber.i("Loading account result: An exception occurred") + errorHandler.dispatch(it) + })) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt index 9be87b441..c2c5226c6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt @@ -18,6 +18,7 @@ import io.github.wulkanowy.utils.toFormattedString import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate.now import org.threeten.bp.LocalDate.ofEpochDay +import timber.log.Timber import java.util.concurrent.TimeUnit.MILLISECONDS import javax.inject.Inject @@ -36,6 +37,7 @@ class AttendancePresenter @Inject constructor( fun onAttachView(view: AttendanceView, date: Long?) { super.onAttachView(view) + Timber.i("Attendance view is attached") view.initView() loadData(ofEpochDay(date ?: now().previousOrSameSchoolDay.toEpochDay())) reloadView() @@ -52,10 +54,12 @@ class AttendancePresenter @Inject constructor( } fun onSwipeRefresh() { + Timber.i("Force refreshing the attendance") loadData(currentDate, true) } fun onViewReselected() { + Timber.i("Attendance view is reselected") view?.also { view -> if (view.currentStackSize == 1) { now().previousOrSameSchoolDay.also { @@ -69,7 +73,10 @@ class AttendancePresenter @Inject constructor( } fun onAttendanceItemSelected(item: AbstractFlexibleItem<*>?) { - if (item is AttendanceItem) view?.showAttendanceDialog(item.attendance) + if (item is AttendanceItem) { + Timber.i("Select attendance item ${item.attendance.id}") + view?.showAttendanceDialog(item.attendance) + } } fun onSummarySwitchSelected(): Boolean { @@ -78,6 +85,7 @@ class AttendancePresenter @Inject constructor( } private fun loadData(date: LocalDate, forceRefresh: Boolean = false) { + Timber.i("Loading attendance data started") currentDate = date disposable.apply { clear() @@ -100,6 +108,7 @@ class AttendancePresenter @Inject constructor( } } .subscribe({ + Timber.i("Loading attendance result: Success") view?.apply { updateData(it) showEmpty(it.isEmpty()) @@ -107,6 +116,7 @@ class AttendancePresenter @Inject constructor( } analytics.logEvent("load_attendance", mapOf("items" to it.size, "force_refresh" to forceRefresh, START_DATE to currentDate.toFormattedString("yyyy-MM-dd"))) }) { + Timber.i("Loading attendance result: An exception occurred") view?.run { showEmpty(isViewEmpty) } errorHandler.dispatch(it) } @@ -115,6 +125,7 @@ class AttendancePresenter @Inject constructor( } private fun reloadView() { + Timber.i("Reload attendance view with the date ${currentDate.toFormattedString()}") view?.apply { showProgress(true) showContent(false) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt index 5086e80cc..26741f3cc 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt @@ -12,6 +12,7 @@ import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.calculatePercentage import io.github.wulkanowy.utils.getFormattedName +import timber.log.Timber import java.lang.String.format import java.util.Locale.FRANCE import java.util.concurrent.TimeUnit.MILLISECONDS @@ -34,25 +35,31 @@ class AttendanceSummaryPresenter @Inject constructor( fun onAttachView(view: AttendanceSummaryView, subjectId: Int?) { super.onAttachView(view) + Timber.i("Attendance summary view is attached with subject id ${subjectId ?: -1}") view.initView() loadData(subjectId ?: -1) loadSubjects() } fun onSwipeRefresh() { + Timber.i("Force refreshing the attendance summary") loadData(currentSubjectId, true) } fun onSubjectSelected(name: String) { + Timber.i("Select attendance summary subject $name") view?.run { showContent(false) showProgress(true) clearView() } - loadData(subjects.singleOrNull { it.name == name }?.realId ?: -1) + (subjects.singleOrNull { it.name == name }?.realId ?: -1).let { + if (it != currentSubjectId) loadData(it) + } } private fun loadData(subjectId: Int, forceRefresh: Boolean = false) { + Timber.i("Loading attendance summary data started") currentSubjectId = subjectId disposable.apply { clear() @@ -70,6 +77,7 @@ class AttendanceSummaryPresenter @Inject constructor( } } .subscribe({ + Timber.i("Loading attendance summary result: Success") view?.apply { showEmpty(it.first.isEmpty()) showContent(it.first.isNotEmpty()) @@ -77,6 +85,7 @@ class AttendanceSummaryPresenter @Inject constructor( } analytics.logEvent("load_attendance_summary", mapOf("items" to it.first.size, "force_refresh" to forceRefresh, "item_id" to subjectId)) }) { + Timber.i("Loading attendance summary result: An exception occurred") view?.run { showEmpty(isViewEmpty) } errorHandler.dispatch(it) } @@ -85,6 +94,7 @@ class AttendanceSummaryPresenter @Inject constructor( } private fun loadSubjects() { + Timber.i("Loading attendance summary subjects started") disposable.add(studentRepository.getCurrentStudent() .flatMap { semesterRepository.getCurrentSemester(it) } .flatMap { subjectRepository.getSubjects(it) } @@ -93,11 +103,15 @@ class AttendanceSummaryPresenter @Inject constructor( .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) .subscribe({ + Timber.i("Loading attendance summary subjects result: Success") view?.run { view?.updateSubjects(it) showSubjects(true) } - }, { errorHandler.dispatch(it) }) + }, { + Timber.i("Loading attendance summary subjects result: An exception occurred") + errorHandler.dispatch(it) + }) ) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt index 88a0eaa53..150147ea1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt @@ -18,6 +18,7 @@ import io.github.wulkanowy.utils.toFormattedString import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate.now import org.threeten.bp.LocalDate.ofEpochDay +import timber.log.Timber import java.util.concurrent.TimeUnit.MILLISECONDS import javax.inject.Inject @@ -35,6 +36,7 @@ class ExamPresenter @Inject constructor( fun onAttachView(view: ExamView, date: Long?) { super.onAttachView(view) + Timber.i("Exam view is attached") view.initView() loadData(ofEpochDay(date ?: now().nextOrSameSchoolDay.toEpochDay())) reloadView() @@ -51,14 +53,19 @@ class ExamPresenter @Inject constructor( } fun onSwipeRefresh() { + Timber.i("Force refreshing the exam") loadData(currentDate, true) } fun onExamItemSelected(item: AbstractFlexibleItem<*>?) { - if (item is ExamItem) view?.showExamDialog(item.exam) + if (item is ExamItem) { + Timber.i("Select exam item ${item.exam.id}") + view?.showExamDialog(item.exam) + } } fun onViewReselected() { + Timber.i("Exam view is reselected") now().nextOrSameSchoolDay.also { if (currentDate != it) { loadData(it) @@ -68,6 +75,7 @@ class ExamPresenter @Inject constructor( } private fun loadData(date: LocalDate, forceRefresh: Boolean = false) { + Timber.i("Loading exam data started") currentDate = date disposable.apply { clear() @@ -87,6 +95,7 @@ class ExamPresenter @Inject constructor( } } .subscribe({ + Timber.i("Loading exam result: Success") view?.apply { updateData(it) showEmpty(it.isEmpty()) @@ -94,6 +103,7 @@ class ExamPresenter @Inject constructor( } analytics.logEvent("load_exam", mapOf("items" to it.size, "force_refresh" to forceRefresh, START_DATE to currentDate.toFormattedString("yyyy-MM-dd"))) }) { + Timber.i("Loading exam result: An exception occurred") view?.run { showEmpty(isViewEmpty) } errorHandler.dispatch(it) }) @@ -109,6 +119,7 @@ class ExamPresenter @Inject constructor( } private fun reloadView() { + Timber.i("Reload exam view with the date ${currentDate.toFormattedString()}") view?.apply { showProgress(true) showContent(false) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt index d2dd8031e..ca6822654 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt @@ -8,6 +8,7 @@ import io.github.wulkanowy.ui.base.session.SessionErrorHandler import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider import io.reactivex.Completable +import timber.log.Timber import java.util.concurrent.TimeUnit.MILLISECONDS import javax.inject.Inject @@ -28,6 +29,7 @@ class GradePresenter @Inject constructor( fun onAttachView(view: GradeView, savedIndex: Int?) { super.onAttachView(view) + Timber.i("Grade view is attached") disposable.add(Completable.timer(150, MILLISECONDS, schedulers.mainThread) .subscribe { selectedIndex = savedIndex ?: 0 @@ -37,6 +39,7 @@ class GradePresenter @Inject constructor( } fun onViewReselected() { + Timber.i("Grade view is reselected") view?.run { notifyChildParentReselected(currentPageIndex) } } @@ -47,6 +50,7 @@ class GradePresenter @Inject constructor( fun onSemesterSelected(index: Int) { if (selectedIndex != index - 1) { + Timber.i("Change semester in grade view to ${index + 1}") selectedIndex = index + 1 loadedSemesterId.clear() view?.let { @@ -74,6 +78,7 @@ class GradePresenter @Inject constructor( } private fun loadData() { + Timber.i("Loading grade data started") disposable.add(studentRepository.getCurrentStudent() .flatMap { semesterRepository.getSemesters(it) } .doOnSuccess { @@ -84,7 +89,13 @@ class GradePresenter @Inject constructor( } .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) - .subscribe({ view?.run { loadChild(currentPageIndex) } }) { + .subscribe({ + view?.run { + Timber.i("Loading grade result: Attempt load index $currentPageIndex") + loadChild(currentPageIndex) + } + }) { + Timber.i("Loading grade result: An exception occurred") errorHandler.dispatch(it) view?.run { showProgress(false) @@ -96,6 +107,7 @@ class GradePresenter @Inject constructor( private fun loadChild(index: Int, forceRefresh: Boolean = false) { semesters.first { it.semesterName == selectedIndex }.semesterId.also { if (forceRefresh || loadedSemesterId[index] != it) { + Timber.i("Load grade child view index: $index") view?.notifyChildLoadData(index, it, forceRefresh) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt index bc3b6c1e9..16cc0fa07 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt @@ -40,6 +40,7 @@ class GradeDetailsPresenter @Inject constructor( fun onGradeItemSelected(item: AbstractFlexibleItem<*>?) { if (item is GradeDetailsItem) { + Timber.i("Select grade item ${item.grade.id}") view?.apply { showGradeDialog(item.grade) if (!item.grade.isRead) { @@ -58,18 +59,29 @@ class GradeDetailsPresenter @Inject constructor( } fun onMarkAsReadSelected(): Boolean { + Timber.i("Select mark grades as read") disposable.add(studentRepository.getCurrentStudent() .flatMap { semesterRepository.getSemesters(it) } .flatMap { gradeRepository.getNewGrades(it.first { item -> item.semesterId == currentSemesterId }) } .map { it.map { grade -> grade.apply { isRead = true } } } - .flatMapCompletable { gradeRepository.updateGrades(it) } + .flatMapCompletable { + Timber.i("Mark as read ${it.size} grades") + gradeRepository.updateGrades(it) + } .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) - .subscribe({ loadData(currentSemesterId, false) }, { errorHandler.dispatch(it) })) + .subscribe({ + Timber.i("Mark as read result: Success") + loadData(currentSemesterId, false) + }, { + Timber.i("Mark as read result: An exception occurred") + errorHandler.dispatch(it) + })) return true } fun onSwipeRefresh() { + Timber.i("Force refreshing the grade details") view?.notifyParentRefresh() } @@ -94,6 +106,7 @@ class GradeDetailsPresenter @Inject constructor( } private fun loadData(semesterId: Int, forceRefresh: Boolean) { + Timber.i("Loading grade details data started") disposable.add(studentRepository.getCurrentStudent() .flatMap { semesterRepository.getSemesters(it) } .flatMap { gradeRepository.getGrades(it.first { item -> item.semesterId == semesterId }, forceRefresh) } @@ -110,6 +123,7 @@ class GradeDetailsPresenter @Inject constructor( } } .subscribe({ + Timber.i("Loading grade details result: Success") view?.run { showEmpty(it.isEmpty()) showContent(it.isNotEmpty()) @@ -117,6 +131,7 @@ class GradeDetailsPresenter @Inject constructor( } analytics.logEvent("load_grade_details", mapOf("items" to it.size, "force_refresh" to forceRefresh)) }) { + Timber.i("Loading grade details result: An exception occurred") view?.run { showEmpty(isViewEmpty) } errorHandler.dispatch(it) }) @@ -152,10 +167,14 @@ class GradeDetailsPresenter @Inject constructor( } private fun updateGrade(grade: Grade) { + Timber.i("Attempt to update grade ${grade.id}") disposable.add(gradeRepository.updateGrade(grade) .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) - .subscribe({}) { error -> errorHandler.dispatch(error) }) - Timber.d("Grade ${grade.id} updated") + .subscribe({ Timber.i("Update grade result: Success") }) + { error -> + Timber.i("Update grade result: An exception occurred") + errorHandler.dispatch(error) + }) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt index 7920ca288..08db37d2c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt @@ -12,6 +12,7 @@ import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.calcAverage import io.github.wulkanowy.utils.changeModifier +import timber.log.Timber import java.lang.String.format import java.util.Locale.FRANCE import javax.inject.Inject @@ -33,6 +34,7 @@ class GradeSummaryPresenter @Inject constructor( } fun onParentViewLoadData(semesterId: Int, forceRefresh: Boolean) { + Timber.i("Loading grade summary data started") disposable.add(studentRepository.getCurrentStudent() .flatMap { semesterRepository.getSemesters(it) } .map { semester -> semester.first { it.semesterId == semesterId } } @@ -64,6 +66,7 @@ class GradeSummaryPresenter @Inject constructor( notifyParentDataLoaded(semesterId) } }.subscribe({ + Timber.i("Loading grade summary result: Success") view?.run { showEmpty(it.first.isEmpty()) showContent(it.first.isNotEmpty()) @@ -71,12 +74,14 @@ class GradeSummaryPresenter @Inject constructor( } analytics.logEvent("load_grade_summary", mapOf("items" to it.first.size, "force_refresh" to forceRefresh)) }) { + Timber.i("Loading grade summary result: An exception occurred") view?.run { showEmpty(isViewEmpty) } errorHandler.dispatch(it) }) } fun onSwipeRefresh() { + Timber.i("Force refreshing the grade summary") view?.notifyParentRefresh() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt index e4d427450..e8c40ba89 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt @@ -15,6 +15,7 @@ import io.github.wulkanowy.utils.nextSchoolDay import io.github.wulkanowy.utils.previousSchoolDay import io.github.wulkanowy.utils.toFormattedString import org.threeten.bp.LocalDate +import timber.log.Timber import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -32,6 +33,7 @@ class HomeworkPresenter @Inject constructor( fun onAttachView(view: HomeworkView, date: Long?) { super.onAttachView(view) + Timber.i("Homework view is attached") view.initView() loadData(LocalDate.ofEpochDay(date ?: LocalDate.now().nextOrSameSchoolDay.toEpochDay())) reloadView() @@ -48,14 +50,19 @@ class HomeworkPresenter @Inject constructor( } fun onSwipeRefresh() { + Timber.i("Force refreshing the homework") loadData(currentDate, true) } fun onHomeworkItemSelected(item: AbstractFlexibleItem<*>?) { - if (item is HomeworkItem) view?.showTimetableDialog(item.homework) + if (item is HomeworkItem) { + Timber.i("Select homework item ${item.homework.id}") + view?.showTimetableDialog(item.homework) + } } private fun loadData(date: LocalDate, forceRefresh: Boolean = false) { + Timber.i("Loading homework data started") currentDate = date disposable.apply { clear() @@ -73,6 +80,7 @@ class HomeworkPresenter @Inject constructor( } } .subscribe({ + Timber.i("Loading homework result: Success") view?.apply { updateData(it) showEmpty(it.isEmpty()) @@ -80,6 +88,7 @@ class HomeworkPresenter @Inject constructor( } analytics.logEvent("load_homework", mapOf("items" to it.size, "force_refresh" to forceRefresh, START_DATE to currentDate.toFormattedString("yyyy-MM-dd"))) }) { + Timber.i("Loading homework result: An exception occurred") view?.run { showEmpty(isViewEmpty()) } errorHandler.dispatch(it) }) @@ -87,6 +96,7 @@ class HomeworkPresenter @Inject constructor( } private fun reloadView() { + Timber.i("Reload homework view with the date ${currentDate.toFormattedString()}") view?.apply { showProgress(true) showContent(false) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginPresenter.kt index 0687e2f21..d4308959a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginPresenter.kt @@ -2,6 +2,7 @@ package io.github.wulkanowy.ui.modules.login import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler +import timber.log.Timber import javax.inject.Inject class LoginPresenter @Inject constructor(errorHandler: ErrorHandler) : BasePresenter(errorHandler) { @@ -12,6 +13,7 @@ class LoginPresenter @Inject constructor(errorHandler: ErrorHandler) : BasePrese initAdapter() hideActionBar() } + Timber.i("Login view is attached") } fun onPageSelected(index: Int) { @@ -23,6 +25,7 @@ class LoginPresenter @Inject constructor(errorHandler: ErrorHandler) : BasePrese } fun onBackPressed(default: () -> Unit) { + Timber.i("Back pressed in login view") view?.run { if (currentViewIndex == 1) { switchView(0) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt index a1bbc1f05..7f963527b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt @@ -45,6 +45,7 @@ class LoginFormPresenter @Inject constructor( showProgress(true) showContent(false) } + Timber.i("Login started") } .doFinally { view?.apply { @@ -58,17 +59,21 @@ class LoginFormPresenter @Inject constructor( showSymbolInput() wasEmpty = true analytics.logEvent("sign_up_send", mapOf(SUCCESS to false, "students" to 0, "endpoint" to endpoint, GROUP_ID to symbol.ifEmpty { "null" })) + Timber.i("Login result: Empty student list") } else if (it.isEmpty() && wasEmpty) { showSymbolInput() setErrorSymbolIncorrect() - analytics.logEvent("sign_up_send", mapOf(SUCCESS to false, "students" to it.size, "endpoint" to endpoint, GROUP_ID to symbol.ifEmpty { "nil" })) + analytics.logEvent("sign_up_send", mapOf(SUCCESS to false, "students" to it.size, "endpoint" to endpoint, GROUP_ID to symbol.ifEmpty { "null" })) + Timber.i("Login result: Wrong symbol") } else { analytics.logEvent("sign_up_send", mapOf(SUCCESS to true, "students" to it.size, "endpoint" to endpoint, GROUP_ID to symbol)) + Timber.i("Login result: Success") switchOptionsView() } } }, { - analytics.logEvent(SIGN_UP, mapOf(SUCCESS to false, "endpoint" to endpoint, "message" to it.localizedMessage, GROUP_ID to symbol.ifEmpty { "nil" })) + analytics.logEvent(SIGN_UP, mapOf(SUCCESS to false, "endpoint" to endpoint, "message" to it.localizedMessage, GROUP_ID to symbol.ifEmpty { "null" })) + Timber.i("Login result: An exception occurred") errorHandler.dispatch(it) })) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/options/LoginOptionsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/options/LoginOptionsPresenter.kt index 6740d0582..1e2c39334 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/options/LoginOptionsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/options/LoginOptionsPresenter.kt @@ -12,6 +12,7 @@ import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider import io.reactivex.Single +import timber.log.Timber import javax.inject.Inject class LoginOptionsPresenter @Inject constructor( @@ -26,7 +27,10 @@ class LoginOptionsPresenter @Inject constructor( super.onAttachView(view) view.run { initView() - errorHandler.onStudentDuplicate = { showMessage(it) } + errorHandler.onStudentDuplicate = { + showMessage(it) + Timber.i("The student already registered in the app was selected") + } } } @@ -58,11 +62,14 @@ class LoginOptionsPresenter @Inject constructor( showContent(false) showActionBar(false) } + Timber.i("Registration started") } .subscribe({ analytics.logEvent(SIGN_UP, mapOf(SUCCESS to true, "endpoint" to student.endpoint, "message" to "Success", GROUP_ID to student.symbol)) + Timber.i("Registration result: Success") view?.openMainView() }, { + Timber.i("Registration result: An exception occurred ") errorHandler.dispatch(it) view?.apply { showProgress(false) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt index 58517302a..efe5cc3a4 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt @@ -71,7 +71,7 @@ class MainActivity : BaseActivity(), MainView { override fun onStart() { super.onStart() - presenter.onViewStart() + presenter.onViewChange() } override fun initView() { @@ -98,7 +98,7 @@ class MainActivity : BaseActivity(), MainView { } navController.run { - setOnViewChangeListener { presenter.onViewStart() } + setOnViewChangeListener { presenter.onViewChange() } fragmentHideStrategy = HIDE rootFragments = listOf( GradeFragment.newInstance(), diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt index f18bd9eb0..0ef59c2b1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt @@ -10,6 +10,7 @@ import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider import io.reactivex.Completable +import timber.log.Timber import javax.inject.Inject class MainPresenter @Inject constructor( @@ -23,6 +24,7 @@ class MainPresenter @Inject constructor( fun onAttachView(view: MainView, initMenuIndex: Int) { super.onAttachView(view) + Timber.i("Main view is attached with $initMenuIndex menu index") view.run { cancelNotifications() startMenuIndex = if (initMenuIndex != -1) initMenuIndex else prefRepository.startMenuIndex @@ -38,7 +40,7 @@ class MainPresenter @Inject constructor( })) } - fun onViewStart() { + fun onViewChange() { view?.apply { currentViewTitle?.let { setViewTitle(it) } currentStackSize?.let { @@ -49,16 +51,19 @@ class MainPresenter @Inject constructor( } fun onAccountManagerSelected(): Boolean { + Timber.i("Select account manager") view?.showAccountPicker() return true } fun onUpNavigate(): Boolean { + Timber.i("Up navigate pressed") view?.popView() return true } fun onBackPressed(default: () -> Unit) { + Timber.i("Back pressed in main view") view?.run { if (isRootView) default() else popView() @@ -67,6 +72,7 @@ class MainPresenter @Inject constructor( fun onTabSelected(index: Int, wasSelected: Boolean): Boolean { return view?.run { + Timber.i("Switch main tab index: $index, reselected: $wasSelected") if (wasSelected) { notifyMenuViewReselected() false @@ -78,15 +84,25 @@ class MainPresenter @Inject constructor( } fun onLoginSelected() { + Timber.i("Attempt to switch the student after the session expires") disposable.add(studentRepository.getCurrentStudent(false) .flatMapCompletable { studentRepository.logoutStudent(it) } .andThen(studentRepository.getSavedStudents(false)) .flatMapCompletable { - if (it.isNotEmpty()) studentRepository.switchStudent(it[0]) + if (it.isNotEmpty()) { + Timber.i("Switching current student") + studentRepository.switchStudent(it[0]) + } else Completable.complete() } .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) - .subscribe({ view?.openLoginView() }, { errorHandler.dispatch(it) })) + .subscribe({ + Timber.i("Switch student result: Open login view") + view?.openLoginView() + }, { + Timber.i("Switch student result: An exception occurred") + errorHandler.dispatch(it) + })) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessagePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessagePresenter.kt index 360b9bfc1..11ec94d8e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessagePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessagePresenter.kt @@ -4,6 +4,7 @@ import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.SchedulersProvider import io.reactivex.Completable +import timber.log.Timber import java.util.concurrent.TimeUnit.MILLISECONDS import javax.inject.Inject @@ -14,6 +15,7 @@ class MessagePresenter @Inject constructor( override fun onAttachView(view: MessageView) { super.onAttachView(view) + Timber.i("Message view is attached") disposable.add(Completable.timer(150, MILLISECONDS, schedulers.mainThread) .subscribe { view.initView() @@ -30,6 +32,7 @@ class MessagePresenter @Inject constructor( } private fun loadChild(index: Int, forceRefresh: Boolean = false) { + Timber.i("Load message child view index: $index") view?.notifyChildLoadData(index, forceRefresh) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt index 3197e9914..aae11b77f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt @@ -8,6 +8,7 @@ import io.github.wulkanowy.ui.base.session.SessionErrorHandler import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.toFormattedString +import timber.log.Timber import javax.inject.Inject class MessagePreviewPresenter @Inject constructor( @@ -26,6 +27,7 @@ class MessagePreviewPresenter @Inject constructor( } private fun loadData(id: Int) { + Timber.i("Loading message $id preview started") messageId = id disposable.apply { clear() @@ -35,6 +37,7 @@ class MessagePreviewPresenter @Inject constructor( .observeOn(schedulers.mainThread) .doFinally { view?.showProgress(false) } .subscribe({ message -> + Timber.i("Loading message $id preview result: Success ") view?.run { message.let { setSubject(if (it.subject.isNotBlank()) it.subject else noSubjectString) @@ -45,8 +48,9 @@ class MessagePreviewPresenter @Inject constructor( else setSender(it.sender) } } - analytics.logEvent("load_attendance", mapOf(START_DATE to message.date?.toFormattedString("yyyy.MM.dd"), "lenght" to message.content?.length)) + analytics.logEvent("load_message_preview", mapOf(START_DATE to message.date?.toFormattedString("yyyy.MM.dd"), "lenght" to message.content?.length)) }) { + Timber.i("Loading message $id preview result: An excception occurred ") view?.showMessageError() errorHandler.dispatch(it) }) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt index 0e1a9d84f..0d413b99c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt @@ -29,10 +29,12 @@ class MessageTabPresenter @Inject constructor( } fun onSwipeRefresh() { + Timber.i("Force refreshing the $folder message") onParentViewLoadData(true) } fun onParentViewLoadData(forceRefresh: Boolean) { + Timber.i("Loading $folder message data started") disposable.apply { clear() add(studentRepository.getCurrentStudent() @@ -48,6 +50,7 @@ class MessageTabPresenter @Inject constructor( } } .subscribe({ + Timber.i("Loading $folder message result: Success") view?.run { showEmpty(it.isEmpty()) showContent(it.isNotEmpty()) @@ -55,6 +58,7 @@ class MessageTabPresenter @Inject constructor( } analytics.logEvent("load_messages", mapOf("items" to it.size, "folder" to folder.name)) }) { + Timber.i("Loading $folder message result: An exception occurred") view?.run { showEmpty(isViewEmpty) } errorHandler.dispatch(it) }) @@ -63,6 +67,7 @@ class MessageTabPresenter @Inject constructor( fun onMessageItemSelected(item: AbstractFlexibleItem<*>) { if (item is MessageItem) { + Timber.i("Select message ${item.message.realId} item") view?.run { openMessage(item.message.realId) if (item.message.unread == true) { @@ -75,12 +80,14 @@ class MessageTabPresenter @Inject constructor( } private fun updateMessage(message: Message) { + Timber.i("Attempt to update message ${message.realId}") disposable.add(messagesRepository.updateMessage(message) .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) - .subscribe({ - Timber.d("Message ${message.realId} updated") - }) { error -> errorHandler.dispatch(error) } - ) + .subscribe({ Timber.d("Update message ${message.realId} result: Success") }) + { error -> + Timber.i("Update message ${message.realId} result: An exception occurred") + errorHandler.dispatch(error) + }) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MorePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MorePresenter.kt index 61b187f1b..6becc8ad1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MorePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MorePresenter.kt @@ -3,18 +3,21 @@ package io.github.wulkanowy.ui.modules.more import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler +import timber.log.Timber import javax.inject.Inject class MorePresenter @Inject constructor(errorHandler: ErrorHandler) : BasePresenter(errorHandler) { override fun onAttachView(view: MoreView) { super.onAttachView(view) + Timber.i("More view is attached") view.initView() loadData() } fun onItemSelected(item: AbstractFlexibleItem<*>?) { if (item is MoreItem) { + Timber.i("Select more item \"${item.title}\"") view?.run { when (item.title) { messagesRes?.first -> openMessagesView() @@ -28,10 +31,12 @@ class MorePresenter @Inject constructor(errorHandler: ErrorHandler) : BasePresen } fun onViewReselected() { + Timber.i("More view is reselected") view?.popView() } private fun loadData() { + Timber.i("Load items for more view") view?.run { updateData(listOfNotNull( messagesRes?.let { MoreItem(it.first, it.second) }, diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt index c388a3f78..4bf3de3de 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt @@ -23,15 +23,18 @@ class NotePresenter @Inject constructor( override fun onAttachView(view: NoteView) { super.onAttachView(view) + Timber.i("Note view is attached") view.initView() loadData() } fun onSwipeRefresh() { + Timber.i("Force refreshing the note") loadData(true) } private fun loadData(forceRefresh: Boolean = false) { + Timber.i("Loading note data started") disposable.add(studentRepository.getCurrentStudent() .flatMap { semesterRepository.getCurrentSemester(it) } .flatMap { noteRepository.getNotes(it, forceRefresh) } @@ -45,6 +48,7 @@ class NotePresenter @Inject constructor( showProgress(false) } }.subscribe({ + Timber.i("Loading note result: Success") view?.apply { updateData(it) showEmpty(it.isEmpty()) @@ -52,6 +56,7 @@ class NotePresenter @Inject constructor( } analytics.logEvent("load_note", mapOf("items" to it.size, "force_refresh" to forceRefresh)) }, { + Timber.i("Loading note result: An exception occurred") view?.run { showEmpty(isViewEmpty) } errorHandler.dispatch(it) }) @@ -60,6 +65,7 @@ class NotePresenter @Inject constructor( fun onNoteItemSelected(item: AbstractFlexibleItem<*>?) { if (item is NoteItem) { + Timber.i("Select note item ${item.note.id}") view?.run { showNoteDialog(item.note) if (!item.note.isRead) { @@ -72,12 +78,14 @@ class NotePresenter @Inject constructor( } private fun updateNote(note: Note) { + Timber.i("Attempt to update note ${note.id}") disposable.add(noteRepository.updateNote(note) .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) - .subscribe({ - Timber.d("Note ${note.id} updated") - }) { error -> errorHandler.dispatch(error) } - ) + .subscribe({ Timber.i("Update note result: Success") }) + { error -> + Timber.i("Update note result: An exception occurred") + errorHandler.dispatch(error) + }) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt index 6a2087558..dff39d202 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt @@ -1,7 +1,6 @@ package io.github.wulkanowy.ui.modules.settings import com.readystatesoftware.chuck.api.ChuckCollector -import io.github.wulkanowy.data.RepositoryModule_ProvideChuckCollectorFactory import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.services.job.ServiceHelper import io.github.wulkanowy.ui.base.BasePresenter @@ -9,6 +8,7 @@ import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.isHolidays import org.threeten.bp.LocalDate.now +import timber.log.Timber import javax.inject.Inject class SettingsPresenter @Inject constructor( @@ -21,6 +21,7 @@ class SettingsPresenter @Inject constructor( override fun onAttachView(view: SettingsView) { super.onAttachView(view) + Timber.i("Settings view is attached") view.run { setServicesSuspended(preferencesRepository.serviceEnablesKey, now().isHolidays) @@ -28,6 +29,7 @@ class SettingsPresenter @Inject constructor( } fun onSharedPreferenceChanged(key: String) { + Timber.i("Change settings $key") when (key) { preferencesRepository.serviceEnablesKey -> { if (preferencesRepository.isServiceEnabled) serviceHelper.startFullSyncService() @@ -44,7 +46,6 @@ class SettingsPresenter @Inject constructor( chuckCollector.showNotification(preferencesRepository.isShowChuckerNotification) } } - analytics.logEvent("setting_changed", mapOf("name" to key)) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt index 27a9db998..ac0dcca4c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt @@ -17,6 +17,7 @@ import io.github.wulkanowy.utils.toFormattedString import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate.now import org.threeten.bp.LocalDate.ofEpochDay +import timber.log.Timber import java.util.concurrent.TimeUnit.MILLISECONDS import javax.inject.Inject @@ -34,6 +35,7 @@ class TimetablePresenter @Inject constructor( fun onAttachView(view: TimetableView, date: Long?) { super.onAttachView(view) + Timber.i("Timetable is attached") view.initView() loadData(ofEpochDay(date ?: now().nextOrSameSchoolDay.toEpochDay())) reloadView() @@ -50,10 +52,12 @@ class TimetablePresenter @Inject constructor( } fun onSwipeRefresh() { + Timber.i("Force refreshing the timetable") loadData(currentDate, true) } fun onViewReselected() { + Timber.i("Exam view is reselected") now().nextOrSameSchoolDay.also { if (currentDate != it) { loadData(it) @@ -63,10 +67,14 @@ class TimetablePresenter @Inject constructor( } fun onTimetableItemSelected(item: AbstractFlexibleItem<*>?) { - if (item is TimetableItem) view?.showTimetableDialog(item.lesson) + if (item is TimetableItem) { + Timber.i("Select exam item ${item.lesson.id}") + view?.showTimetableDialog(item.lesson) + } } private fun loadData(date: LocalDate, forceRefresh: Boolean = false) { + Timber.i("Loading timetable data started") currentDate = date disposable.apply { clear() @@ -85,13 +93,15 @@ class TimetablePresenter @Inject constructor( } } .subscribe({ + Timber.i("Loading timetable result: Success") view?.apply { updateData(it) showEmpty(it.isEmpty()) showContent(it.isNotEmpty()) } - analytics.logEvent("load_attendance", mapOf("items" to it.size, "force_refresh" to forceRefresh, START_DATE to currentDate.toFormattedString("yyyy-MM-dd"))) + analytics.logEvent("load_timetable", mapOf("items" to it.size, "force_refresh" to forceRefresh, START_DATE to currentDate.toFormattedString("yyyy-MM-dd"))) }) { + Timber.i("Loading timetable result: An exception occurred") view?.run { showEmpty(isViewEmpty) } errorHandler.dispatch(it) }) @@ -99,6 +109,7 @@ class TimetablePresenter @Inject constructor( } private fun reloadView() { + Timber.i("Reload timetable view with the date ${currentDate.toFormattedString()}") view?.apply { showProgress(true) showContent(false) From a174ae998d028ff765e9ce9dc8f4bdb0366b88ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sun, 20 Jan 2019 14:16:24 +0100 Subject: [PATCH 006/121] Add api initialization to the message repository (#220) --- .../data/repositories/MessagesRepository.kt | 82 ++++++++++--------- .../wulkanowy/services/job/SyncWorker.kt | 22 ++--- .../preview/MessagePreviewPresenter.kt | 2 +- .../message/tab/MessageTabPresenter.kt | 2 +- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 3 +- 6 files changed, 61 insertions(+), 52 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/MessagesRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/MessagesRepository.kt index 06a0f6f95..85a1e076c 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/MessagesRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/MessagesRepository.kt @@ -2,6 +2,7 @@ package io.github.wulkanowy.data.repositories import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings +import io.github.wulkanowy.data.ApiHelper import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.local.MessagesLocal @@ -16,7 +17,8 @@ import javax.inject.Singleton class MessagesRepository @Inject constructor( private val settings: InternetObservingSettings, private val local: MessagesLocal, - private val remote: MessagesRemote + private val remote: MessagesRemote, + private val apiHelper: ApiHelper ) { enum class MessageFolder(val id: Int = 1) { @@ -25,44 +27,50 @@ class MessagesRepository @Inject constructor( TRASHED(3) } - fun getMessages(studentId: Int, folder: MessageFolder, forceRefresh: Boolean = false, notify: Boolean = false): Single> { - return local.getMessages(studentId, folder).filter { !forceRefresh } - .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) - .flatMap { - if (it) remote.getMessages(studentId, folder) - else Single.error(UnknownHostException()) - }.flatMap { new -> - local.getMessages(studentId, folder).toSingle(emptyList()) - .doOnSuccess { old -> - local.deleteMessages(old - new) - local.saveMessages((new - old) - .onEach { - it.isNotified = !notify - }) - } - }.flatMap { local.getMessages(studentId, folder).toSingle(emptyList()) } - ) + fun getMessages(student: Student, folder: MessageFolder, forceRefresh: Boolean = false, notify: Boolean = false): Single> { + return Single.just(apiHelper.initApi(student)) + .flatMap { _ -> + local.getMessages(student.studentId, folder).filter { !forceRefresh } + .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) + .flatMap { + if (it) remote.getMessages(student.studentId, folder) + else Single.error(UnknownHostException()) + }.flatMap { new -> + local.getMessages(student.studentId, folder).toSingle(emptyList()) + .doOnSuccess { old -> + local.deleteMessages(old - new) + local.saveMessages((new - old) + .onEach { + it.isNotified = !notify + }) + } + }.flatMap { local.getMessages(student.studentId, folder).toSingle(emptyList()) } + ) + } } - fun getMessage(studentId: Int, messageId: Int, markAsRead: Boolean = false): Single { - return local.getMessage(studentId, messageId) - .filter { !it.content.isNullOrEmpty() } - .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) - .flatMap { - if (it) local.getMessage(studentId, messageId).toSingle() - else Single.error(UnknownHostException()) - } - .flatMap { dbMessage -> - remote.getMessagesContent(dbMessage, markAsRead).doOnSuccess { - local.updateMessage(dbMessage.copy(unread = false).apply { - id = dbMessage.id - content = it - }) - } - }.flatMap { - local.getMessage(studentId, messageId).toSingle() - } - ) + fun getMessage(student: Student, messageId: Int, markAsRead: Boolean = false): Single { + return Single.just(apiHelper.initApi(student)) + .flatMap { _ -> + local.getMessage(student.studentId, messageId) + .filter { !it.content.isNullOrEmpty() } + .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) + .flatMap { + if (it) local.getMessage(student.studentId, messageId).toSingle() + else Single.error(UnknownHostException()) + } + .flatMap { dbMessage -> + remote.getMessagesContent(dbMessage, markAsRead).doOnSuccess { + local.updateMessage(dbMessage.copy(unread = false).apply { + id = dbMessage.id + content = it + }) + } + }.flatMap { + local.getMessage(student.studentId, messageId).toSingle() + } + ) + } } fun getNewMessages(student: Student): Single> { diff --git a/app/src/main/java/io/github/wulkanowy/services/job/SyncWorker.kt b/app/src/main/java/io/github/wulkanowy/services/job/SyncWorker.kt index 65fb3abcf..c958c22d0 100644 --- a/app/src/main/java/io/github/wulkanowy/services/job/SyncWorker.kt +++ b/app/src/main/java/io/github/wulkanowy/services/job/SyncWorker.kt @@ -84,19 +84,19 @@ class SyncWorker : SimpleJobService() { var error: Throwable? = null disposable.add(student.getCurrentStudent() - .flatMap { semester.getCurrentSemester(it, true) } + .flatMap { semester.getCurrentSemester(it, true).map { semester -> semester to it } } .flatMapPublisher { Single.merge( listOf( - gradesDetails.getGrades(it, true, true), - gradesSummary.getGradesSummary(it, true), - attendance.getAttendance(it, start, end, true), - exam.getExams(it, start, end, true), - timetable.getTimetable(it, start, end, true), - message.getMessages(it.studentId, RECEIVED, true, true), - note.getNotes(it, true, true), - homework.getHomework(it, LocalDate.now(), true), - homework.getHomework(it, LocalDate.now().plusDays(1), true) + gradesDetails.getGrades(it.first, true, true), + gradesSummary.getGradesSummary(it.first, true), + attendance.getAttendance(it.first, start, end, true), + exam.getExams(it.first, start, end, true), + timetable.getTimetable(it.first, start, end, true), + message.getMessages(it.second, RECEIVED, true, true), + note.getNotes(it.first, true, true), + homework.getHomework(it.first, LocalDate.now(), true), + homework.getHomework(it.first, LocalDate.now().plusDays(1), true) ) ) } @@ -138,7 +138,7 @@ class SyncWorker : SimpleJobService() { disposable.add(student.getCurrentStudent() .flatMap { message.getNewMessages(it) } .map { it.filter { message -> !message.isNotified } } - .doOnSuccess{ + .doOnSuccess { if (it.isNotEmpty()) { Timber.d("Found ${it.size} unread messages") MessageNotification(applicationContext).sendNotification(it) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt index aae11b77f..d0ce92504 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt @@ -32,7 +32,7 @@ class MessagePreviewPresenter @Inject constructor( disposable.apply { clear() add(studentRepository.getCurrentStudent() - .flatMap { messagesRepository.getMessage(it.studentId, messageId, true) } + .flatMap { messagesRepository.getMessage(it, messageId, true) } .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) .doFinally { view?.showProgress(false) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt index 0d413b99c..9636e4df3 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt @@ -38,7 +38,7 @@ class MessageTabPresenter @Inject constructor( disposable.apply { clear() add(studentRepository.getCurrentStudent() - .flatMap { messagesRepository.getMessages(it.studentId, folder, forceRefresh) } + .flatMap { messagesRepository.getMessages(it, folder, forceRefresh) } .map { items -> items.map { MessageItem(it, view?.noSubjectString.orEmpty()) } } .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) diff --git a/build.gradle b/build.gradle index 629b4a48e..fdd750f74 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ buildscript { } dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'com.android.tools.build:gradle:3.2.1' + classpath 'com.android.tools.build:gradle:3.3.0' classpath 'com.google.gms:google-services:4.2.0' classpath "io.fabric.tools:gradle:1.26.1" classpath "com.github.triplet.gradle:play-publisher:1.2.2" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3f0f9be63..acee7e1ba 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Sun Jan 20 00:47:56 CET 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip From 8476f0e62e307c834db8b5a67b3fa04cfd4835fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sun, 20 Jan 2019 15:00:33 +0100 Subject: [PATCH 007/121] Update dependencies (#221) --- app/build.gradle | 18 +++++++++--------- build.gradle | 4 ++-- gradle/wrapper/gradle-wrapper.properties | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index f60cbb008..dd6258e1e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -92,9 +92,9 @@ dependencies { implementation "com.mikepenz:aboutlibraries:6.2.0" implementation "com.firebase:firebase-jobdispatcher:0.8.5" - implementation "com.google.dagger:dagger-android-support:2.19" - kapt "com.google.dagger:dagger-compiler:2.19" - kapt "com.google.dagger:dagger-android-processor:2.19" + implementation "com.google.dagger:dagger-android-support:2.21" + kapt "com.google.dagger:dagger-compiler:2.21" + kapt "com.google.dagger:dagger-android-processor:2.21" implementation "androidx.room:room-runtime:2.1.0-alpha03" implementation "androidx.room:room-rxjava2:2.1.0-alpha03" @@ -103,14 +103,14 @@ dependencies { implementation "eu.davidea:flexible-adapter:5.1.0" implementation "eu.davidea:flexible-adapter-ui:1.0.0" - implementation "com.aurelhubert:ahbottomnavigation:2.2.0" - implementation 'com.ncapdevi:frag-nav:3.0.0' + implementation "com.aurelhubert:ahbottomnavigation:2.3.4" + implementation 'com.ncapdevi:frag-nav:3.1.0' - implementation 'com.github.pwittchen:reactivenetwork-rx2:3.0.1' + implementation 'com.github.pwittchen:reactivenetwork-rx2:3.0.2' implementation 'io.reactivex.rxjava2:rxandroid:2.1.0' - implementation "io.reactivex.rxjava2:rxjava:2.2.4" + implementation "io.reactivex.rxjava2:rxjava:2.2.5" - implementation "com.jakewharton.threetenabp:threetenabp:1.1.0" + implementation "com.jakewharton.threetenabp:threetenabp:1.1.1" implementation "com.jakewharton.timber:timber:4.7.1" implementation "at.favre.lib:slf4j-timber:1.0.1" @@ -124,7 +124,7 @@ dependencies { debugImplementation "com.amitshekhar.android:debug-db:1.0.4" testImplementation "junit:junit:4.12" - testImplementation "io.mockk:mockk:1.8.13.kotlin13" + testImplementation "io.mockk:mockk:1.9" testImplementation "org.mockito:mockito-inline:2.23.4" testImplementation 'org.threeten:threetenbp:1.3.8' diff --git a/build.gradle b/build.gradle index fdd750f74..d2f68da07 100644 --- a/build.gradle +++ b/build.gradle @@ -11,9 +11,9 @@ buildscript { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.android.tools.build:gradle:3.3.0' classpath 'com.google.gms:google-services:4.2.0' - classpath "io.fabric.tools:gradle:1.26.1" + classpath "io.fabric.tools:gradle:1.27.0" classpath "com.github.triplet.gradle:play-publisher:1.2.2" - classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.6.2" + classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7" classpath 'com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta02' } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index acee7e1ba..f95599f57 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -#Sun Jan 20 00:47:56 CET 2019 +#Sun Jan 20 13:58:58 CET 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME From 20d9313257361100223d6afeeb04f80b4b137c69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 20 Jan 2019 15:39:11 +0100 Subject: [PATCH 008/121] Version 0.6.4 --- app/build.gradle | 6 +++--- app/src/main/play/pl-PL/whatsnew | 13 ++++++++----- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index dd6258e1e..46349f1d3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -23,8 +23,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 15 targetSdkVersion 28 - versionCode 22 - versionName "0.6.3" + versionCode 23 + versionName "0.6.4" multiDexEnabled true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true @@ -80,7 +80,7 @@ play { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" - implementation('io.github.wulkanowy:api:0.6.3') { exclude module: "threetenbp" } + implementation('io.github.wulkanowy:api:0.6.4') { exclude module: "threetenbp" } implementation "androidx.legacy:legacy-support-v4:1.0.0" implementation "androidx.appcompat:appcompat:1.0.2" diff --git a/app/src/main/play/pl-PL/whatsnew b/app/src/main/play/pl-PL/whatsnew index 81ff753b0..ab5ec9776 100644 --- a/app/src/main/play/pl-PL/whatsnew +++ b/app/src/main/play/pl-PL/whatsnew @@ -1,7 +1,10 @@ -Wersja 0.6.3 +Wersja 0.6.4 -- naprawiono problem ze stabilnością przy odczytywaniu ocen -- poprawiono komunikat o błędzie podczas przerwy technicznej dziennika -- (ponownie) naprawiono logowanie w systemach Resman Rzeszów i podobnych +- naprawiono problemy ze stabilnością podczas logowania na urządzeniach marki Meizu +- naprawiono problemy ze stabilnością w uwagach +- naprawiono błąd pobierania podglądu wiadomości, gdy otworzono aplikację z powiadomienia +- zoptymalizowano pobieranie wiadomości wysłanych +- poprawiono wyświetlanie odbiorców wiadomości wysłanych +- naprawiono problem z liczeniem średniej z ocen zawierających minus -Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases/tag/0.6.3 +Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases/tag/0.6.4 From e29886560e8fcbd4e3179c6d9d46ad1f668b3c6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sun, 20 Jan 2019 22:43:50 +0100 Subject: [PATCH 009/121] Disable obfuscate in R8 (#222) --- app/build.gradle | 1 - app/proguard-rules.pro | 1 + gradle.properties | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 46349f1d3..e5bb803c0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -48,7 +48,6 @@ android { release { buildConfigField "boolean", "CRASHLYTICS_ENABLED", "true" minifyEnabled true - useProguard false shrinkResources true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' signingConfig signingConfigs.release diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index cebd3d94d..589b5f34b 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -5,6 +5,7 @@ -dontskipnonpubliclibraryclasses -dontskipnonpubliclibraryclassmembers -dontpreverify +-dontobfuscate -allowaccessmodification -repackageclasses '' -verbose diff --git a/gradle.properties b/gradle.properties index 9e6fce102..24e69b6ac 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,6 +11,7 @@ # The setting is particularly useful for tweaking memory settings. android.enableJetifier=true android.useAndroidX=true +android.enableR8=true org.gradle.jvmargs=-Xmx1536m # When configured, Gradle will run in incubating parallel mode. From 2061d6408fee725e5097ec924e14408b3fc422cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 20 Jan 2019 23:36:16 +0100 Subject: [PATCH 010/121] Change icon of semester switch (#223) --- app/src/main/res/drawable/ic_menu_grade_semester_24dp.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/drawable/ic_menu_grade_semester_24dp.xml b/app/src/main/res/drawable/ic_menu_grade_semester_24dp.xml index dbc32c27d..2a03f6300 100644 --- a/app/src/main/res/drawable/ic_menu_grade_semester_24dp.xml +++ b/app/src/main/res/drawable/ic_menu_grade_semester_24dp.xml @@ -1,9 +1,9 @@ + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M21,17V8H7V17H21M21,3A2,2 0,0 1,23 5V17A2,2 0,0 1,21 19H7C5.89,19 5,18.1 5,17V5A2,2 0,0 1,7 3H8V1H10V3H18V1H20V3H21M3,21H17V23H3C1.89,23 1,22.1 1,21V9H3V21M19,15H15V11H19V15Z" /> From 189830e0f494c25df2598f5a6310bccbd4c6ca85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 20 Jan 2019 23:38:02 +0100 Subject: [PATCH 011/121] Version 0.6.5 --- app/build.gradle | 4 ++-- app/src/main/play/pl-PL/whatsnew | 12 ++++-------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index e5bb803c0..4d199db2d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -23,8 +23,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 15 targetSdkVersion 28 - versionCode 23 - versionName "0.6.4" + versionCode 24 + versionName "0.6.5" multiDexEnabled true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true diff --git a/app/src/main/play/pl-PL/whatsnew b/app/src/main/play/pl-PL/whatsnew index ab5ec9776..70c0352ad 100644 --- a/app/src/main/play/pl-PL/whatsnew +++ b/app/src/main/play/pl-PL/whatsnew @@ -1,10 +1,6 @@ -Wersja 0.6.4 +Wersja 0.6.5 -- naprawiono problemy ze stabilnością podczas logowania na urządzeniach marki Meizu -- naprawiono problemy ze stabilnością w uwagach -- naprawiono błąd pobierania podglądu wiadomości, gdy otworzono aplikację z powiadomienia -- zoptymalizowano pobieranie wiadomości wysłanych -- poprawiono wyświetlanie odbiorców wiadomości wysłanych -- naprawiono problem z liczeniem średniej z ocen zawierających minus +- zmieniono ikonę zmiany semestru w ocenach +- naprawiono wyświetlanie szczegółowych informacji o błędach -Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases/tag/0.6.4 +Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases/tag/0.6.5 From 941765a3a353446f87a1eb0619268650321d3c28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Wed, 23 Jan 2019 18:28:51 +0100 Subject: [PATCH 012/121] Fix empty Maybe in student repository (#224) --- .../github/wulkanowy/data/repositories/StudentRepository.kt | 5 ++++- .../main/java/io/github/wulkanowy/services/job/SyncWorker.kt | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt index 97210da0b..a7a6f6eaa 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt @@ -7,6 +7,7 @@ import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.local.StudentLocal import io.github.wulkanowy.data.repositories.remote.StudentRemote import io.reactivex.Completable +import io.reactivex.Maybe import io.reactivex.Single import java.net.UnknownHostException import javax.inject.Inject @@ -41,7 +42,9 @@ class StudentRepository @Inject constructor( } fun getCurrentStudent(decryptPass: Boolean = true): Single { - return local.getCurrentStudent(decryptPass).toSingle() + return local.getCurrentStudent(decryptPass) + .switchIfEmpty(Maybe.error(NoSuchElementException("No current student"))) + .toSingle() } fun saveStudent(student: Student): Single { diff --git a/app/src/main/java/io/github/wulkanowy/services/job/SyncWorker.kt b/app/src/main/java/io/github/wulkanowy/services/job/SyncWorker.kt index c958c22d0..6e8566718 100644 --- a/app/src/main/java/io/github/wulkanowy/services/job/SyncWorker.kt +++ b/app/src/main/java/io/github/wulkanowy/services/job/SyncWorker.kt @@ -80,6 +80,7 @@ class SyncWorker : SimpleJobService() { val end = LocalDate.now().friday if (start.isHolidays) return RESULT_FAIL_NORETRY + if (!student.isStudentSaved) return RESULT_FAIL_RETRY var error: Throwable? = null From c78fb837748875b14f3080383656173d710eccd0 Mon Sep 17 00:00:00 2001 From: Kacper Ziubryniewicz Date: Thu, 24 Jan 2019 18:32:51 +0100 Subject: [PATCH 013/121] Fix receiving a lot of notifications after turning them off for a while (#225) --- .../io/github/wulkanowy/services/job/SyncWorker.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/services/job/SyncWorker.kt b/app/src/main/java/io/github/wulkanowy/services/job/SyncWorker.kt index 6e8566718..cf09728a6 100644 --- a/app/src/main/java/io/github/wulkanowy/services/job/SyncWorker.kt +++ b/app/src/main/java/io/github/wulkanowy/services/job/SyncWorker.kt @@ -84,18 +84,20 @@ class SyncWorker : SimpleJobService() { var error: Throwable? = null + val notify = prefRepository.isNotificationsEnable + disposable.add(student.getCurrentStudent() .flatMap { semester.getCurrentSemester(it, true).map { semester -> semester to it } } .flatMapPublisher { Single.merge( listOf( - gradesDetails.getGrades(it.first, true, true), + gradesDetails.getGrades(it.first, true, notify), gradesSummary.getGradesSummary(it.first, true), attendance.getAttendance(it.first, start, end, true), exam.getExams(it.first, start, end, true), timetable.getTimetable(it.first, start, end, true), - message.getMessages(it.second, RECEIVED, true, true), - note.getNotes(it.first, true, true), + message.getMessages(it.second, RECEIVED, true, notify), + note.getNotes(it.first, true, notify), homework.getHomework(it.first, LocalDate.now(), true), homework.getHomework(it.first, LocalDate.now().plusDays(1), true) ) @@ -104,7 +106,7 @@ class SyncWorker : SimpleJobService() { .subscribe({}, { error = it })) return if (null === error) { - if (prefRepository.isNotificationsEnable) sendNotifications() + if (notify) sendNotifications() Timber.d("Synchronization successful") RESULT_SUCCESS } else { From d3c13b8fc3d7863054f513ff79b373abb52795d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Thu, 24 Jan 2019 21:16:22 +0100 Subject: [PATCH 014/121] Fix empty fragment list in fragment manager (#226) --- .../ui/base/BaseFragmentPagerAdapter.kt | 36 +++++++++++++++++++ .../wulkanowy/ui/base/BasePagerAdapter.kt | 32 ----------------- .../ui/modules/grade/GradeFragment.kt | 17 ++++----- .../wulkanowy/ui/modules/grade/GradeModule.kt | 4 +-- .../ui/modules/login/LoginActivity.kt | 12 +++---- .../wulkanowy/ui/modules/login/LoginModule.kt | 4 +-- .../ui/modules/message/MessageFragment.kt | 15 ++++---- .../ui/modules/message/MessageModule.kt | 4 +-- 8 files changed, 65 insertions(+), 59 deletions(-) create mode 100644 app/src/main/java/io/github/wulkanowy/ui/base/BaseFragmentPagerAdapter.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/base/BasePagerAdapter.kt diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragmentPagerAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragmentPagerAdapter.kt new file mode 100644 index 000000000..bccb8f78c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragmentPagerAdapter.kt @@ -0,0 +1,36 @@ +package io.github.wulkanowy.ui.base + +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.FragmentPagerAdapter + +class BaseFragmentPagerAdapter(private val fragmentManager: FragmentManager) : FragmentPagerAdapter(fragmentManager) { + + private val pages = mutableMapOf() + + private var containerId = 0 + + fun getFragmentInstance(position: Int): Fragment? { + return fragmentManager.findFragmentByTag("android:switcher:$containerId:$position") + } + + fun addFragments(fragments: List) { + fragments.forEach { pages[it] = null } + } + + fun addFragmentsWithTitle(pages: Map) { + this.pages.putAll(pages) + } + + override fun instantiateItem(container: ViewGroup, position: Int): Any { + containerId = container.id + return super.instantiateItem(container, position) + } + + override fun getItem(position: Int) = pages.keys.elementAt(position) + + override fun getCount() = pages.size + + override fun getPageTitle(position: Int) = pages.values.elementAt(position) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BasePagerAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BasePagerAdapter.kt deleted file mode 100644 index 000efdcd3..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BasePagerAdapter.kt +++ /dev/null @@ -1,32 +0,0 @@ -package io.github.wulkanowy.ui.base - -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentManager -import androidx.fragment.app.FragmentStatePagerAdapter - -class BasePagerAdapter(fragmentManager: FragmentManager) : FragmentStatePagerAdapter(fragmentManager) { - - val fragments = mutableMapOf() - - val registeredFragments = mutableMapOf() - - override fun getItem(position: Int) = fragments.values.elementAt(position) - - override fun getCount() = fragments.size - - override fun getPageTitle(position: Int): CharSequence? { - return fragments.keys.elementAtOrNull(position) - } - - override fun instantiateItem(container: ViewGroup, position: Int): Any { - return super.instantiateItem(container, position).also { - registeredFragments[position] = it as Fragment - } - } - - override fun destroyItem(container: ViewGroup, position: Int, fragment: Any) { - registeredFragments.remove(position) - super.destroyItem(container, position, fragment) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt index a4d948a92..d56eca9a2 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt @@ -11,7 +11,7 @@ import android.view.View.VISIBLE import android.view.ViewGroup import androidx.appcompat.app.AlertDialog import io.github.wulkanowy.R -import io.github.wulkanowy.ui.base.BasePagerAdapter +import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter import io.github.wulkanowy.ui.base.session.BaseSessionFragment import io.github.wulkanowy.ui.modules.grade.details.GradeDetailsFragment import io.github.wulkanowy.ui.modules.grade.summary.GradeSummaryFragment @@ -26,7 +26,7 @@ class GradeFragment : BaseSessionFragment(), GradeView, MainView.MainChildView, lateinit var presenter: GradePresenter @Inject - lateinit var pagerAdapter: BasePagerAdapter + lateinit var pagerAdapter: BaseFragmentPagerAdapter companion object { private const val SAVED_SEMESTER_KEY = "CURRENT_SEMESTER" @@ -59,10 +59,11 @@ class GradeFragment : BaseSessionFragment(), GradeView, MainView.MainChildView, } override fun initView() { - pagerAdapter.fragments.putAll(mapOf( - getString(R.string.all_details) to GradeDetailsFragment.newInstance(), - getString(R.string.grade_menu_summary) to GradeSummaryFragment.newInstance() + pagerAdapter.addFragmentsWithTitle(mapOf( + GradeDetailsFragment.newInstance() to getString(R.string.all_details), + GradeSummaryFragment.newInstance() to getString(R.string.grade_menu_summary) )) + gradeViewPager.run { adapter = pagerAdapter setOnSelectPageListener { presenter.onPageSelected(it) } @@ -117,15 +118,15 @@ class GradeFragment : BaseSessionFragment(), GradeView, MainView.MainChildView, } override fun notifyChildLoadData(index: Int, semesterId: Int, forceRefresh: Boolean) { - (childFragmentManager.fragments[index] as GradeView.GradeChildView).onParentLoadData(semesterId, forceRefresh) + (pagerAdapter.getFragmentInstance(index) as? GradeView.GradeChildView)?.onParentLoadData(semesterId, forceRefresh) } override fun notifyChildParentReselected(index: Int) { - (pagerAdapter.registeredFragments[index] as? GradeView.GradeChildView)?.onParentReselected() + (pagerAdapter.getFragmentInstance(index) as? GradeView.GradeChildView)?.onParentReselected() } override fun notifyChildSemesterChange(index: Int) { - (pagerAdapter.registeredFragments[index] as? GradeView.GradeChildView)?.onParentChangeSemester() + (pagerAdapter.getFragmentInstance(index) as? GradeView.GradeChildView)?.onParentChangeSemester() } override fun onSaveInstanceState(outState: Bundle) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeModule.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeModule.kt index c47ddc574..b7b52f218 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeModule.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeModule.kt @@ -5,7 +5,7 @@ import dagger.Provides import dagger.android.ContributesAndroidInjector import io.github.wulkanowy.di.scopes.PerChildFragment import io.github.wulkanowy.di.scopes.PerFragment -import io.github.wulkanowy.ui.base.BasePagerAdapter +import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter import io.github.wulkanowy.ui.modules.grade.details.GradeDetailsFragment import io.github.wulkanowy.ui.modules.grade.summary.GradeSummaryFragment @@ -18,7 +18,7 @@ abstract class GradeModule { @JvmStatic @PerFragment @Provides - fun provideGradePagerAdapter(fragment: GradeFragment) = BasePagerAdapter(fragment.childFragmentManager) + fun provideGradeAdapter(fragment: GradeFragment) = BaseFragmentPagerAdapter(fragment.childFragmentManager) } @PerChildFragment diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt index c9c1aeb41..db3124e4a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt @@ -5,7 +5,7 @@ import android.content.Intent import android.os.Bundle import io.github.wulkanowy.R import io.github.wulkanowy.ui.base.BaseActivity -import io.github.wulkanowy.ui.base.BasePagerAdapter +import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter import io.github.wulkanowy.ui.modules.login.form.LoginFormFragment import io.github.wulkanowy.ui.modules.login.options.LoginOptionsFragment import io.github.wulkanowy.utils.setOnSelectPageListener @@ -18,7 +18,7 @@ class LoginActivity : BaseActivity(), LoginView { lateinit var presenter: LoginPresenter @Inject - lateinit var loginAdapter: BasePagerAdapter + lateinit var loginAdapter: BaseFragmentPagerAdapter companion object { fun getStartIntent(context: Context) = Intent(context, LoginActivity::class.java) @@ -36,9 +36,9 @@ class LoginActivity : BaseActivity(), LoginView { } override fun initAdapter() { - loginAdapter.fragments.putAll(mapOf( - "1" to LoginFormFragment.newInstance(), - "2" to LoginOptionsFragment.newInstance() + loginAdapter.addFragments(listOf( + LoginFormFragment.newInstance(), + LoginOptionsFragment.newInstance() )) loginViewpager.run { @@ -52,7 +52,7 @@ class LoginActivity : BaseActivity(), LoginView { } override fun notifyOptionsViewLoadData() { - (supportFragmentManager.fragments[1] as? LoginOptionsFragment)?.onParentLoadData() + (loginAdapter.getFragmentInstance(1) as? LoginOptionsFragment)?.onParentLoadData() } fun onChildFragmentSwitchOptions() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginModule.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginModule.kt index 3cdf83679..1758d59a2 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginModule.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginModule.kt @@ -5,7 +5,7 @@ import dagger.Provides import dagger.android.ContributesAndroidInjector import io.github.wulkanowy.di.scopes.PerActivity import io.github.wulkanowy.di.scopes.PerFragment -import io.github.wulkanowy.ui.base.BasePagerAdapter +import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter import io.github.wulkanowy.ui.modules.login.form.LoginFormFragment import io.github.wulkanowy.ui.modules.login.options.LoginOptionsFragment @@ -18,7 +18,7 @@ internal abstract class LoginModule { @JvmStatic @PerActivity @Provides - fun provideLoginAdapter(activity: LoginActivity) = BasePagerAdapter(activity.supportFragmentManager) + fun provideLoginAdapter(activity: LoginActivity) = BaseFragmentPagerAdapter(activity.supportFragmentManager) } @PerFragment diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageFragment.kt index f75c6131d..df6ceca7d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageFragment.kt @@ -11,7 +11,7 @@ import io.github.wulkanowy.data.repositories.MessagesRepository.MessageFolder.RE import io.github.wulkanowy.data.repositories.MessagesRepository.MessageFolder.SENT import io.github.wulkanowy.data.repositories.MessagesRepository.MessageFolder.TRASHED import io.github.wulkanowy.ui.base.BaseFragment -import io.github.wulkanowy.ui.base.BasePagerAdapter +import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.message.tab.MessageTabFragment import io.github.wulkanowy.utils.setOnSelectPageListener @@ -24,7 +24,7 @@ class MessageFragment : BaseFragment(), MessageView, MainView.TitledView { lateinit var presenter: MessagePresenter @Inject - lateinit var pagerAdapter: BasePagerAdapter + lateinit var pagerAdapter: BaseFragmentPagerAdapter companion object { fun newInstance() = MessageFragment() @@ -46,11 +46,12 @@ class MessageFragment : BaseFragment(), MessageView, MainView.TitledView { } override fun initView() { - pagerAdapter.fragments.putAll(mapOf( - getString(R.string.message_inbox) to MessageTabFragment.newInstance(RECEIVED), - getString(R.string.message_sent) to MessageTabFragment.newInstance(SENT), - getString(R.string.message_trash) to MessageTabFragment.newInstance(TRASHED) + pagerAdapter.addFragmentsWithTitle(mapOf( + MessageTabFragment.newInstance(RECEIVED) to getString(R.string.message_inbox), + MessageTabFragment.newInstance(SENT) to getString(R.string.message_sent), + MessageTabFragment.newInstance(TRASHED) to getString(R.string.message_trash) )) + messageViewPager.run { adapter = pagerAdapter offscreenPageLimit = 2 @@ -73,7 +74,7 @@ class MessageFragment : BaseFragment(), MessageView, MainView.TitledView { } override fun notifyChildLoadData(index: Int, forceRefresh: Boolean) { - (childFragmentManager.fragments[index] as MessageView.MessageChildView).onParentLoadData(forceRefresh) + (pagerAdapter.getFragmentInstance(index) as? MessageView.MessageChildView)?.onParentLoadData(forceRefresh) } override fun onDestroyView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageModule.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageModule.kt index dd7a22ae8..e4c202d9f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageModule.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageModule.kt @@ -5,7 +5,7 @@ import dagger.Provides import dagger.android.ContributesAndroidInjector import io.github.wulkanowy.di.scopes.PerChildFragment import io.github.wulkanowy.di.scopes.PerFragment -import io.github.wulkanowy.ui.base.BasePagerAdapter +import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter import io.github.wulkanowy.ui.modules.message.tab.MessageTabFragment @Module @@ -17,7 +17,7 @@ abstract class MessageModule { @JvmStatic @PerFragment @Provides - fun provideGradePagerAdapter(fragment: MessageFragment) = BasePagerAdapter(fragment.childFragmentManager) + fun provideMessageAdapter(fragment: MessageFragment) = BaseFragmentPagerAdapter(fragment.childFragmentManager) } @PerChildFragment From ffc2ef9a4e27bb925c1ac08569c4424b748b4bcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Fri, 25 Jan 2019 19:20:13 +0100 Subject: [PATCH 015/121] Remove renaming source file in R8 (#227) --- app/proguard-rules.pro | 1 - build.gradle | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 589b5f34b..12360845c 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -14,7 +14,6 @@ #Config for anallitycs -keepattributes *Annotation* -keepattributes SourceFile,LineNumberTable --renamesourcefileattribute SourceFile -keep class com.crashlytics.** {*;} -keep public class * extends java.lang.Exception -dontwarn com.crashlytics.** diff --git a/build.gradle b/build.gradle index d2f68da07..26becfc7c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.3.11' + ext.kotlin_version = '1.3.20' repositories { mavenCentral() google() From 43f6048c27b0e895168350a7b9bf683042e94212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Fri, 25 Jan 2019 20:41:03 +0100 Subject: [PATCH 016/121] Upgrade to Gradle Play Publisher v2 (#228) --- .circleci/config.yml | 2 +- .travis.yml | 6 +++++- app/build.gradle | 14 ++++---------- app/src/main/ic_launcher-web.png | Bin 19242 -> 0 bytes .../play/{contactEmail => contact-email.txt} | 0 .../play/{contactPhone => contact-phone.txt} | 0 .../{contactWebsite => contact-website.txt} | 0 .../{defaultLanguage => default-language.txt} | 0 .../pl-PL/full-description.txt} | 0 .../pl-PL/graphics/feature-graphic}/feature.png | Bin .../play/listings/pl-PL/graphics/icon/icon.png | Bin 0 -> 38676 bytes .../phone-screenshots}/account-switcher.png | Bin .../phone-screenshots}/attendance-dialog.png | Bin .../attendance-statistics.png | Bin .../graphics/phone-screenshots}/grades.png | Bin .../pl-PL/graphics/phone-screenshots}/more.png | Bin .../phone-screenshots}/timetable-dialog.png | Bin .../phone-screenshots}/timetable-widget.png | Bin .../pl-PL/short-description.txt} | 0 .../listing/title => listings/pl-PL/title.txt} | 0 app/src/main/play/pl-PL/listing/icon/icon.png | Bin 19242 -> 0 bytes app/src/main/play/pl-PL/listing/video | 0 .../pl-PL/default.txt} | 0 build.gradle | 2 +- 24 files changed, 11 insertions(+), 13 deletions(-) delete mode 100644 app/src/main/ic_launcher-web.png rename app/src/main/play/{contactEmail => contact-email.txt} (100%) rename app/src/main/play/{contactPhone => contact-phone.txt} (100%) rename app/src/main/play/{contactWebsite => contact-website.txt} (100%) rename app/src/main/play/{defaultLanguage => default-language.txt} (100%) rename app/src/main/play/{pl-PL/listing/fulldescription => listings/pl-PL/full-description.txt} (100%) rename app/src/main/play/{pl-PL/listing/featureGraphic => listings/pl-PL/graphics/feature-graphic}/feature.png (100%) create mode 100644 app/src/main/play/listings/pl-PL/graphics/icon/icon.png rename app/src/main/play/{pl-PL/listing/phoneScreenshots => listings/pl-PL/graphics/phone-screenshots}/account-switcher.png (100%) rename app/src/main/play/{pl-PL/listing/phoneScreenshots => listings/pl-PL/graphics/phone-screenshots}/attendance-dialog.png (100%) rename app/src/main/play/{pl-PL/listing/phoneScreenshots => listings/pl-PL/graphics/phone-screenshots}/attendance-statistics.png (100%) rename app/src/main/play/{pl-PL/listing/phoneScreenshots => listings/pl-PL/graphics/phone-screenshots}/grades.png (100%) rename app/src/main/play/{pl-PL/listing/phoneScreenshots => listings/pl-PL/graphics/phone-screenshots}/more.png (100%) rename app/src/main/play/{pl-PL/listing/phoneScreenshots => listings/pl-PL/graphics/phone-screenshots}/timetable-dialog.png (100%) rename app/src/main/play/{pl-PL/listing/phoneScreenshots => listings/pl-PL/graphics/phone-screenshots}/timetable-widget.png (100%) rename app/src/main/play/{pl-PL/listing/shortdescription => listings/pl-PL/short-description.txt} (100%) rename app/src/main/play/{pl-PL/listing/title => listings/pl-PL/title.txt} (100%) delete mode 100644 app/src/main/play/pl-PL/listing/icon/icon.png delete mode 100644 app/src/main/play/pl-PL/listing/video rename app/src/main/play/{pl-PL/whatsnew => release-notes/pl-PL/default.txt} (100%) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1ae402f53..d4e59be19 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -159,7 +159,7 @@ jobs: openssl aes-256-cbc -d -in ./app/upload-key-encrypted.jks -k $ENCRYPT_KEY >> ./app/upload-key.jks - run: name: Publish release - command: ./gradlew publishRelease --no-daemon --stacktrace --console=plain -PenableCrashlytics -PdisablePreDex + command: ./gradlew publish --no-daemon --stacktrace --console=plain -PenableCrashlytics -PdisablePreDex workflows: version: 2 diff --git a/.travis.yml b/.travis.yml index a430c90ef..e2f4c9511 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,10 @@ cache: - $HOME/.gradle/caches/ - $HOME/.gradle/wrapper/ +branches: + only: + - master + android: licenses: - android-sdk-preview-license-.+ @@ -56,7 +60,7 @@ script: gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/google-services.json.gpg; gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/key.p12.gpg; gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg; - ./gradlew publishRelease -PenableCrashlytics --stacktrace; + ./gradlew publish -PenableCrashlytics --stacktrace; fi after_success: diff --git a/app/build.gradle b/app/build.gradle index 4d199db2d..ef33e2343 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -11,13 +11,6 @@ android { compileSdkVersion 28 buildToolsVersion '28.0.3' - playAccountConfigs { - defaultAccountConfig { - serviceAccountEmail = System.getenv("PLAY_SERVICE_ACCOUNT_EMAIL") - pk12File = file('key.p12') - } - } - defaultConfig { applicationId "io.github.wulkanowy" testApplicationId "io.github.tests.wulkanowy" @@ -28,7 +21,6 @@ android { multiDexEnabled true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true - playAccountConfig = playAccountConfigs.defaultAccountConfig manifestPlaceholders = [ fabric_api_key: System.getenv("FABRIC_API_KEY") ?: "null", crashlytics_enabled: project.hasProperty("enableCrashlytics") @@ -72,14 +64,16 @@ androidExtensions { } play { + serviceAccountEmail = System.getenv("PLAY_SERVICE_ACCOUNT_EMAIL") ?: "jan@fakelog.cf" + serviceAccountCredentials = file('key.p12') + defaultToAppBundles = true track = 'alpha' - uploadImages = false } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" - implementation('io.github.wulkanowy:api:0.6.4') { exclude module: "threetenbp" } + implementation('com.github.wulkanowy:api:0fe0fbc69d') { exclude module: "threetenbp" } implementation "androidx.legacy:legacy-support-v4:1.0.0" implementation "androidx.appcompat:appcompat:1.0.2" diff --git a/app/src/main/ic_launcher-web.png b/app/src/main/ic_launcher-web.png deleted file mode 100644 index de216d36b258956f80fca256b2f0e7dca99ed3e7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19242 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 N;pyt{!{3*-O* diff --git a/app/src/main/play/contactEmail b/app/src/main/play/contact-email.txt similarity index 100% rename from app/src/main/play/contactEmail rename to app/src/main/play/contact-email.txt diff --git a/app/src/main/play/contactPhone b/app/src/main/play/contact-phone.txt similarity index 100% rename from app/src/main/play/contactPhone rename to app/src/main/play/contact-phone.txt diff --git a/app/src/main/play/contactWebsite b/app/src/main/play/contact-website.txt similarity index 100% rename from app/src/main/play/contactWebsite rename to app/src/main/play/contact-website.txt diff --git a/app/src/main/play/defaultLanguage b/app/src/main/play/default-language.txt similarity index 100% rename from app/src/main/play/defaultLanguage rename to app/src/main/play/default-language.txt diff --git a/app/src/main/play/pl-PL/listing/fulldescription b/app/src/main/play/listings/pl-PL/full-description.txt similarity index 100% rename from app/src/main/play/pl-PL/listing/fulldescription rename to app/src/main/play/listings/pl-PL/full-description.txt diff --git a/app/src/main/play/pl-PL/listing/featureGraphic/feature.png b/app/src/main/play/listings/pl-PL/graphics/feature-graphic/feature.png similarity index 100% rename from app/src/main/play/pl-PL/listing/featureGraphic/feature.png rename to app/src/main/play/listings/pl-PL/graphics/feature-graphic/feature.png diff --git a/app/src/main/play/listings/pl-PL/graphics/icon/icon.png b/app/src/main/play/listings/pl-PL/graphics/icon/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8c33c2f929170781210ac8b2bb25aeceed0d12a0 GIT binary patch literal 38676 zcmeAS@N?(olHy`uVBq!ia0y~yU}6Aa4mJh`hA$OYelajGa29w(7Bet#3xhBt!>lJ4}r;Fna-R}9C&vQ4QXFtHfGtI*|(6oVPrIVcCgXZH$jy-&$=g`Qd zQotaExux3<0_f3F`IFE;BUD{V}teks*4DqA25n zi3|$DsU0p18P*JI&WClmGDH|Kh@^QRv1GWR%V6Lcp60=@VLQWta{}C(7&v$s3<5h^ zH5pi{84^w?D_1bI%w!O8`xI{TQG1=hb1epjikX{icB)vqHb!&k7P5zj>&a=H^Ay(> zF-v4hoaLZ*sB)&HQKJd}gU@Fe7#2(v6@1YA`ESKJzIEr$ne}bsi`RX%pY6X=Qqtpp zPtUI|aA06~SW!-AK$!ao;)!p3#s zgq=5UK7I1!hSw3N=)_auwg1u&#UBX&oAa~I=Fj)95B5D$X*7y$bZ7bT;Lw>bK1*fJ zPvUGzy7R2x{m*vp|9@mJD;@Dn+8LoG@;Feau+L>y&u62hN}W!VG>@uY`k(UXzRquE zyZfdL8?x*et0(HNh)rhx=B+LeQF7qXcLs(}yYmlT(coZi$Y_3ezWo1t`@ilBR2dr5 zJSP=0FvPj2=!KlxY=4}Cfx%@#qt*sT=`RN*bPlk|9Av$7ko!-9S&Ng(kwZ=bPFyJs zGDQiRYnq%i+Rko})k@I6;vj2sATXnCasi*-q0k+CwgvpZ5;^7_)Y#L=$>Ml|LpV|K zRtIBJSA=pOhxp9aABqP(ScJM&6i#-C1S)w?H1#kH>IzW^^^pB!Tf}799^kJ-ZK7glHR-|Dw> zK5lT}g#^3tksJfPV~3qKTIg_2PCmU+X${ZnV^SN!)-dnx{F;1zV>ydZqw4`qGnR5E zg+w_=j|iU#zZ)Ja6l7FP1aEPgb27IC9#U7Dyuy2n+AhJLME}D8-DnmTud*=EVCA$RgPL=bUej)tQ>6h*=roUkR zQqIHMCfaNQnusv-zs_doG3wOJe@6TKQZpOTQaSii)@|WMM-242OOs)RkkiWUVmVb5UiD28z zqr!HZ=bVJiQPaYF8(ID1w?76t#~yZYUYa;Har(oSi90XaEWYa2>&D-cc`W9!%H!N) zxyKf(v8gGldHcTdeK#w}cjq$AW!65{KF80VIlJiWwV9@~Z4Iv*J)ix2_VwBKwv291H=cXr7%Oc-x+mqKCWf{3NA~Y&?v)kIaYgMo9Ub}m_;PRb*ll@lDyOw@< z!^dqWx2@bRx#8&zw%hBrnRA0@Uv^IyPv2SG{JEpM)4S=k z-gG;?+0w`Ep4j`utK@58?8B-{U+2!=Jp0hw6}Q*FGkcf$uKmvZGbZyj=S3P9UpRci z`9a{*i}u{}ry6A6UsLDr?BAHa^|`Wsvi`*IwbwUnUs}HEd*XX>yKiziwlTkF zbT2agjQN4>C$^vVKb$`=zV*Iyz4HH*|5g7lGAwUUWlU%8Y;S?Vr%gfn!|Igdc?T_X@;(lB$K12URY(eG1bBDgPE*D#=C!}LF z&B3R`&tl34M**!BVm+##gv(X?xGE3H9!gizzi4)`CP7Ql@s>(@cP`HZ8cA9R+&Psk*(EU!{$j7ClorZQuLj?E&Wycv$V3)Yf@V` z{X3f1yPFp=s`|R5@n@`=I zU_R&l{Qqx-4hcP+v~6jdcet)>#IwC?vMe{*6jt8mdMwJS-4@2S#%4#?Hm%&N{;S`H zPhVHJSFOr7yD{$IsiuQl8M9w=hjKk_t$lsx`rBCl2>rmX)iI>`XbIb}8-UvwOPpqWPw+n*MA$UmowxTU&VVhi!TL z^_%N${pn{lZtrg1=Dc(9ew!V8QkS1Rch=fIWJ$=EkY6EY?=Iclnt1(3-oR2DUt<2tuFSXX z`pzp?WiQ!=1odCje(lb8Tz=obx$nz$dCho3d*>W`q+H-q`Q+ty&imixc&%=g^;EBU z$bMPAOg3FsKg(&>r&(^ZgJ=DYPKw^;f5z_OwO9A5{1XyJxN28I!&9UDe*L)oI^fO3yPtclXQQ%8z3IuU(yI zX?u6?=c@egN56l)FLt-CNd51*)4Ok$tG`>j$A3@!7l*fwH=XacU$u|^cjw0YJM~!y z$`^dTc*p%aTWj03=E|P3=FRl7#OT(FffScPl`Y4#=y{H@9E+gQgQ3e-^vNG;nUW)*H+Ij zerNOKtfjDn+OtYFk0ynKGhH{ig^5<@y^PGeIqmkQwhcM$B77(LTAilJ$cb!Q^F8I{ z`_?Oc%ft4|t=b(QUOF*$O=HSN3t^RKm25@lW|!}KKIg6dxt*OX90Hcl&z||}s=&Z> z@=W!4+w}GKqpIKD4>~5n;wZr47!jMXlch<4qe+3uK>#Eo$N=VbFo1c9t_k{A%>TXl zcsx^5I{EL;;yJfAx0g=We|*bqx_R!QRo7SEG!s0p9JSi;%B@pp-{qxWZspDuJpbA} zD*y0|xu<5V-4|5LRn;IPz&xcUr%8dsNjQj^jrY#AV=<;)b6r-4zqPf1d2|{^H8lVb|u{?%u<4;3%*3isO$b zc-6kW-kQ#DG%wY5zQ)wJ*z(86#BR^%2-Ipc)Ktiw;-%Ta+;=A&Wa(sK#%mfcOuTX# z=N*{Axj5vY*|II~j`c=uiL!kjdCk9JJMR@BRIN05p46g~C5JZoLT@4D@oAxu-8n)WMjG;Nr~`FIW+9}|1-{o@C6pZ=~7^jLcD^07Ig z`~Gu2Sg9#zD{h{bqBHZ!1M&DXuV2qB7G4m4J_xdZc>1)@QzWKM_zW77JXO4#T z-2bM%4)RmkSD1Ia&pdnLiTSs_{)&71`%@=(lLE(~zXxx8-}w6L-mM-_{LjC=@iEPE z;*Vp^+gSG9y=9eeUwkc^fuH-|sqcZu-Z1^UZnx)6^tEF@dHy_GmnFpFDA2OZ+3@w_ zEU^hiy4kzUp8e;m36}i)Kjhe)ihw_JS5N)FhULKFru~euzAFE=AKMlmaLlILFwnML zJY(6qGOiJ!No2IQk>Rasek2MUvKg&ep|$~ znTF?Apa1%G#Y~O%-z*JGXZG(;J3Fs__3q#HQbl*)8y{Zgbo0D8i=#l8_2rk61`b!h z|6P;%_pANB;^+T=)bB2R{n<^1uZz{Vf7#`A3^oeC&HoqH|6h2$c;|oTi{InT7A)Vr zU$HSBloR(!3CpXoi3kLGh4=nE#((S9mc>`zkTcde{xsp|6?0o|J(57_hr6+lYW9z%^595 zek<8@?bduA)%*Y7{ol9!d;k8~r*lt6i5;7<-t@)nK-L8tmoJzS#C2x>wR`LS@4a{L z{=Sgae`kN!o_uqPF{o^EtSB*cxI2BP`<7!r?96T6*MHmp=GCq&?XUB*mu4BSWT;uo zkik;lP_I4tVf)K3|Ch1+pEvVa_p}5098C%wpM!a4+)`L^$jiYe`_-{O*7~YKrsflW zoSzzW$|sohg?k8tlsnIvcRAl4{0ezht$IIy@&m9wZ)Fy#k5`$L8YMDb`>WLLP@HSW zHREL-^9y&DdY!eEd)NBrpGf)lJalQuU9>zv5+1rzLx3 zFI~2fY2P-cgvOg|O5gmybN~O_-it3@3(uQA_guFy$a{&6fm~A#F=g$WA0ywp@7Hl# z+xz!dgq)e@W^pZk`rqwZN3*A0jV!wRe@&Lp^^!SVAT5p&KfDfv%yHl-k-Gl3KmOvb z&9zdszxY=(XdGDMzf|de#_MZ)w*_V2b+7p2{SurI#DZB_0>iai7mM&s*s^Axa&4{u zHWi^x_qR+h+Ef3VZ8$o^cJ9X?^@|js31=3^(tt#T?Nbl2$*g@m)8mQ#ouo5;N2biM zo6OZy3wI z4NIrIDxA}stvr#J-}{^VkLj9A`;Jbz_160vI1QX#&v>Z8u2lL!VAgtGec##bD@ZpjVQwc zg~u9Ov!j?4xNe&+d8)}8xtEOwY8qAhOv)V!$4LXM(RC!d1Sa!aZX1B3TUH?#TkrZF%qD*6dZtcgDtz32UKu7vdfvo7;e z#sF}@i7%|Y|K&KN{hQgKOxm>J49fxMp1)gSqdJ*o4-!|MB5_ zp~+#j0JO1!wdvx>(g0305S@+!kR;T^!2mK?z=Z*99rjiaDO#y!5DU~Z(3TfbZlJbj zNOA+#0NrqC!&4!SrVUDlY>$6Gs66=op+2+5+Q4b7(^@Y{P2sBJ1V=zis>}yl{vOBU zk0WmCSjoA&E`CuV!>~W**y-mV)zm(CtM)u+UF49x;jM#AhXN$7bm|QDKmYD#P!|wj zruJj0h?SVLx=_SRlQO;b%a@xk-|d}ycG2a`HPghV1npS7_)7lWMRm{l-hD8LTiCEN zQ^AR!cgc8{+z^Zx()-}he*{(ft#n{C4VyA^WB zB-XevE#E6U*Q@dUBC+s9cv<;f6Ywy zu>Z{SQPz6H?OPYT5)U27ulfJ_{^coJO=U7$9w)rL;kab?>>GWuQ}+L8I#m7M@Z;-v z_N0Z1Ni1MTY8yV7ZBX#|x1+Oc!qOlYv%DiO-yM7T#wFdz@j`tA!-h#GKSZ~NHde{5 z4>*v|(Qq;F&clZfzHc;qWuDhyxMW_+5?pMVhXwC^>-g|Di4=o_V(l12#&8X|+y0oAkWM<=R^1ec9q4-`?*0_I069=gDW* zQ=D2Ee zzPU_y=leQw>)E{VyX^I+A82uBy(ncpS+VqshStm_?{-bG`y=3`Iz`ue`G!A%&-U%z zrT_hXeZuly{p(%=VgH&Ih*-^4yL8!k%l`Q%rs=ADd!x9o*4oeebV`C*;5KIm~HB2|7Swu|9|_J|C)V2p*n7ZtOAE`mtotY z_r;6D9=okQw!ir0q)A*23LNpVI){osOyrgJkhOGr_3?P;`x}OlZQ>zt_n z+x2DYe=~j|w*FsoQHzB*Lw2}sln52fy`A#?{cg+M#!ce`!8`zt7uB=wRlb&3D4U1<5R{AHVH1Tuc5|Ma5ONPRp;GeB#&F z^8WLt#Sh|x&L#$UO}x8j=AFy_#jnEmGu>BTYuXpW2+qPLwqAlS->QDU`+m~zf8RT| zY<(zgoFDr|JBo#2V#%yG*VfK(Y1$aYD}N$FOmHO=$7!C-WYLzZlMgi+M!Yo9UBB1u zwD$T3Teh(^#vA?13}<9m|; z1M%*Pqe@m27rbS+SNQ%mUpR8Jo79c8-33;B6Q_wyxpt*ye$9{5;(`h~$|>9q0%1(N z$M>}v_FvODe$Y#;S+ioJL|MA}N$2(x`~DbRa7V17X(#G8wwilOH9d4R-J5d_pZzJwTEB5a8MVzF_S0E+dJX$?aR(Nx6Mjy>|P#I zKg0KU*N&ey62>z>hW93(k*Hbfk?q@}d9EGeg`eF{jhcOP{T{E0^)C5aHHn>nQq`M{ z3s(RCr=R~vud)7Jg_SX+C_TRMTV>-!sx zmoEzPtmgc_xV81*$+XOyTf0v^P*@h_8hP!>{+-7rSD_Y-#CWhUN>D>3T46m{x1x9*-PN*6C5K6d|atkB-QcWPeietLec@qMzYkjBy# zny&Z02RgX9G=OUN7E58XUq{Q{E&2Mre*PWveT{c73La5gvS+`A)PV%m$y;P?Jd)l< zSk+#MgVr-=1>>B{!03{xf@a{w2(@%ju?a+9}j6}-EJbNOoS<%^lM zryqX#LSb8`uwVM}H($*K>m(ioOPg={SzbTAqS|`;|3Bv??Tguz1Y?pVrb7+k=lXT* z`^RoqP*$w`S9$4p-@}r!q@_VGT$qv!OH`V=T$$)$_Dzd1&$e@|YBQi}u zl1+ufTT;h{f3D9Yi;4-`?*Geu6um8h{eSFp!LHpW?f%E z`Sq(K#i-Hq@|2s5UpE*Q9QvL*WdkV1g~d+zQ2XXu*dunn1<4zCKdnnl)!ESJyk%)? zMz;2?ZTm0n4m5Au^W5;^?;pWO8WlPi1X@I8|MObOsc$ONz4$KgLX_8E?U$cfr{oHs zeA>}wEF~9mH?v1b&bVz?j9H@KL^TJ;h(lZrn{rER>aHJ-XE|`{@ONf+_lDnoB_^}} zYXwU&A2ke^z}29z=ErjX$KhH#4j-AxizvuhxsQ5{n&pdwV=>jrK*b`Dy!n_{-wp$!ub8zkU`~Pme z`R`~V#oQZ|a>OtIRC%Aa`EmXAii{~Yrs=Aby;uACWU@-X%t6nk@BF?BGj2%Q^nc=c z`{%Fv>y8|`xmn}*%x~ZP7XQo-VqwT!Te$4cY;Z6?75bBHT4Lk(=m2BXzR$AYmfm}Q zsiRAN<{xFL6D@l`{oS31ukQcT-G09^Ju3BoduPKe!=83^j!RL(6IbhiV(O{LU)~Jg zn)h+H{y*UNu>V_9peA$VYkq6$-?*(_wK+Z7^&iFW{CleZ@l@)XjFPhZMt}D5_58Gx zx)7D@EefviPAmVIT{So5#n<^&pF9P2-=ErgE#tu2FiG3OrqlHw#d-exx*n2u|5(wS zo!Q(6Oxq04AC_=FwrPnvM6=>&|95x2UrXmtuqyL;Qy1=aX#Llew!DTzz0!B8e&??H zfA{{x{S^~`{rK_Z`dM*Ph4;S|dK*CnSVoc!|DwPoX|oBxAM$(Je~-EFMt$<%X_cGS zt2ftWCNz9Hz;JYRxa6-F%J264))N-jTd>6~`r4i*F81gB(MMb zmJf1G*5buGTAAYZ_Fa;)o)}RfDD*E*IfEyJWjS+-qPPnKhm$c&SJu=QZM-S-YQ?Ol zAJ$N{`+wTz^Y_^4e|8qf{QPYH{Mq?Bu`_ub#*)fnI`u(D%dg}S|gyH;5fs* z=RLdozp^(9U%y;7dQ`r&X5G5eM~-&)%(wL}t+4npXSTzYs~l&VKJC-rcCaagfhEy% z<@L^Q-zVGD%y{wZ>!hFhzrWaTNH05`sbkba?>SY1rJsS z0Ws^i2(gc`yFK3A6fSAvK3&}y!j;Ukb)(^@=jR@Zad`)Qs(f4sAvcQ*cao zcTYVlbbE*3^5yPE8>UY9%e=K_-a~)8#6MRw?<5JPnuk9OY27c?*T{PR_*8%Y8El(a z6*xXS?l_;n^zx0hYhSp&;1CzH`qwV}*Y5uu$(4b+R{88<$3M>bNE2z z^r1zI9v7CFb$1UaMh^P6|1;@&2Xf4pka%u|)D zQ_C9eOa^P4@FhMub@PpxJ{MYbkN=;>nzz&E=Q&xe?fJoyQi}?H8ZB1$GpKsRGS#}= z;FwA1uVud4VdtGv5zMcP4D#x^U9TlU)rL&d5g;99duR zRV)18Cw<8uPxV(m{k&tTP~6TQPVaxGH9!CF;$U#Am0x_tb~7_L{hnc8sK2{NW{nS< zXy*yjNIm(vuZ;@-*)+f3fBwbv_@w2R58CToK2X1(qv3+#gab>olzSTwi9C^5pm1h7 z$A`ZOTnlFW*O~q1{_3k5cXl}D*2w5&9}UZWb&A@<^W)dvxe%ech1ZNt-jGWO)YyBrp5sAfg|P69s#ra@ z&D;zJzT2?b|7iGld3j;C>z3U0FTQ6tbQwzAcV}Mtp+mtVc|iyRi{lLGb=~G>ZRUoG z*B8xfPPDk2|LBgT-oKaggLV|Dp7?Y1)VWS|S?1ovE1ysNwo?jqW#DKs@UKu-zLJ0U z(E97mo()cuT^(CL)_xM#FM7KEU)RHayF=R3!%Mz_J#ThH;?q8V-*;S~k%=?5J9aEy z9Fl+M!0G3y{aZPW>z9foFZVD0dH(;L*Y*Fu|5$hX%C&vmo_8fa^&WHBX&a_`iCKZ; zv&bLDI~xsOZO?o7^z)6}+@jc7il;fX4cT`5T^(QeHveB+3_J8CSm-zR=Id9KbvBmT2 z{c4jIyOa@+yz7$=4u zix-R7|DE#a`TWV1|9&nwqQjKn*ld67LABhz*Foo>ujJvKXkXqJZU0-6dH+Attrr8Q z*zo*^@n@uca&yPy{uYS~5czS6tVu{i&*4 z|1F(i37@>1x{&rVp#@V8=}wpY`^md2eBFW^Y3r$ny4*G{3f%ITW$AY}gEE368B31y>zDJSFcgs2;jHQSi)^hSs1L zAD%1x{_|OxsVDKwjD#s0OxVEQZJXLqHTTf&_iT*o!jvnYaD*A{bhL7YawuFB#d6(6=2znM!P z@0Bh#Um;=q^Rz^>Y(M*dUB|kUEe>}jj@k0_Mnolda)1-bmyg<_UGX)7DSMU*-OR6H zUcOfMM4Pl@{!EsCZW?+qdrw`J|J!lu?QMZ?j1PVv{Jk^&jt}pr;LT@#GyfLZXz=sB z(4XmZtP=ehJdziH#v9XuI0L4&t_;zbZ_ya96STjy8`SN-u`PGl$JKiV1%LX?)n?7g=KCpOD8FFs! z;=12vU!%8s*#0&7xHh_Q*RBhlELvjDtw{|AkE2SQf@L57KDhqF`MiY(j!e}S(T{)j zO~3Bo19$mAY3ajRIq4j$omuW&msqhx3X-N0A~jAQ(iV~U{d)hh4-eZ_HvG8&d26xB{Dq>ArJV20@Z7AB$s@w3 zz;VdOkZph1;hw%nqFf<*@yq^vJbtA7t5@UeEwwTO)pt*@_Z z#_cq!blZQicl{DW{|D=m7pP_MOj*kSE?{(?@-eM4{Bc!l%IS*3hp(`+7k;jM6Vvx& zUv0$38*A2-rgw{eQ~L4sSFClc?zs<#_J;J%{KDyxsmqu;Q_+8mf=Rc)2AwI77s#;Z z*UUL}a`F`axrg+pihWGg=y@M3D|c*tNK{_mq&2gBRjC2b3t?tfNWE!^~Bg_ncv7fy{IcJrXwg`M%i?-@ms z!K-z>m>2M|9y`*?-6Ln;_kP!=_xfi#tPJGx*nb@B;K(RilI|=pOYal=-EU<^o;kP8 z=JfR?$}_xEbN zVbLFpeTEUi$KP@t(s6M}?pkm$N`@I^fI)1DQRn5my`uf?7pwY;8J(2pA1gOp=l$;x zE8G40`3wE+Qy-m?^xB@6sPcpRvkH^K3r-2{vr9L5ofL5dC4`#X1#BTIO@1nAR~qe0 zL*D=WG0(lUV#cQf3`?!cC4PNycH94>>C)TVJ03r-xTrWkN<(_=FYEPBzj2Gc+7fIzcM=e$s+>*=g{(s5k_y`YQ>9jwh zhZe78*>(`>!0-Aqq8^8@4p|?&xagfs=ilEOl?4B=b1!i_PrxnyXt<9q&qfns>bAd1vki zA*E=BrVC4y7_J}PTRVH>|A+MpDn2TGRCe$9{#DR(`A03$W9R2QJh#5?=!NC;gQO*A zmI%aT^}J7x5l{9hFj9PDEzq(ux|oKx9;`zxfFD}-s$A5x$fG%zv04;t(?_N zPJ#@M{tkzAJfuKr@zAFw7uT4}Ge&Bz3E_IXYe)JygTg-+tmh90b3L_)n8;CPwDU{8 zyXTF{9zdM$%TA5BwQ+3}F&T{3@u>-$d!9!rv(x2I_ zuR63qr`tuTQM6xp<|T)#CiyqFb(WWJekOfC;q2=5lis}KY})(Yxaf}sXxM7!&&~5s z?p3UDWns9&A;ZjQ^suY}Tr3>gbn;iPVa91D?Jbrc_b3?8YybQHSdv6w*P7JChR&t) zcFXNc*A`OTo67yV{)cl_d_`kKw6vPczou&?wtr37H16&?`s3zi&|FEUXivd$cst-zNP1g_p@ql?xXQ!v|yq$huf*n{+ zv@TM5|NDox0#ncPhu2@b+`j$NuB>P4{a<3a?0f>*nVe#8+m>z&$~eBT^txVEd{yVC zx3@#u+nZOl%?T5Dw^;1nWerz1aK?8ER{yuIdy`Jm?QIvovDZ7)ybdeTYiC+zp10uI zn#4!b_aE6^#(QY}T>eE~6M0<^B!x5msgiBD^6TrDeP4wB-v6sN^X=`8hfcN3G}^JE zP)Om{EwAUo{svzjw)52fF^t>UGpX?Kq1s1fS;-3wpUu;33gQLTYG+;d_H9mN*1i8n zP51tg-xGtIXSoMS)YSg4IVa0 zutqsGFL>l+#wNua^>|58O8L7b|G!>;S^JQ6(akr`%>Qkf95ml`KwG{{QRi+&Cl=u?OyZy+V%pYneJ@=S1LK& zHrsi3cjn#g{T|V`pImfwtdLk|*z?owv2WQcP(|3ma3C*J@zUkyzT@xHPJOpeiJE+K zmuU9iDz~YNa?Yf%*~eQrtiHD7c`%DabNuRuyY*iFt25j4k;e!X(}iM*RiOb#=|dXF9W6Ewdnve6^dRUobVee(8gLNDLBMT|W3 z-Fszsug$%*?R%n6)FUZpL!M1;nnGS@XLDqju%-1L3|Z~7L+{u&$LuY+$*xL``ukHC z%Cd|4@m$$f`~7^M@Q?4-e12BGtM<=7x7aa4|W~Ru&$Xgt^8i% z2lM?0{ZyW$3CaDNW0*SS!<1tV(Jl?3sVAYHgI;PIckkM;>E`s&C~os4bXOM)z7V~@zN+fQ%yU{M!4?6q`5lHr;R5$l;p_~x>{t&q_c zz_@wOJlHWU@OZ@y(ymNX>W+%gpOA;I^N6kPr zqK@ka_6?gNmEzSI4y|(78=+GFkm;#)`MaObogz2`5>*q<>7V|-agyhxep|O^v+t{H zt2Xz`*1lmTA*{0~xV|rF^8d9DRv)to{&)9G@&|JVlgSHn@7opq{$^PBs3nJ$hum+%6a0 z`O~GnX6)loFgS3@Axbl6X^M7&S@w(=CdV19UWwbcCT`!#I`j2kN24=y{#MQH)DG8( z+1nMab5EH;>i2Zj`{{Q+r`202+?n40K`II~GroduMX4jr#Q4$2@`FMSGVOz1hN zD<)}Q+;-}~f#%gL4g4a9kFH5Mb7t>f=l5T}Y)n@9_Rdw)RPu|sQNwxG`x0w1rhGZp zyG7p4XUo>F+5UkXhyI@Tb!K@|JD>CZ@!T`>C!AJhau5(>Np{c-G4WcmU;ectn?vuL zd!JYE@EVpqV%d7}VuH;(=Y7|dEq0kds}woRXn&dS-!|VR+oxZ-?4P`St8RZ##*PU0 z=;)VKpFD%4%@)YI?_q3_>;?@Aw<&LWT@(rS!aAFSExR+~<;&DQA81(3us>{Z!BeiE zmzS^f_gSz%?T2r&^@0Q*<$dxpqI3LYzS^uQnRO-DU-0dFcUPs;YLm^5Es$;B#>{@= z?Y-X1mq0muD#r@x!x0g@0)n89jF?Vws8?dE``azK_9hQ@JvCY!U+1|uV$Qc0(MRv@ zJ$tO|!1aA%Yd7A$Cnd z$^3keyb^8t4^7qXnQz$~zxwQpp6bPh8&Y_Z=ea+0?4D`(;J3xyyDEILN|$bPPup6v z>3{XTu7jRSUszO4nHIad;B@NFi!G9uE9|rqS#xc2ctVuGDQ3@u-zz*?Zk(I@!sXGz z-~XRJOl-6JC$T+f=W;EBXu%Itk2&0L&zN%K+FBL)Y97Q91gZ*|+(PC#VJzTeO#?(W4 z&sX*~9{XN+>z2p6+wMt^kFnZs-v1z!L1EhIi**kecG^}uysj*j7YU13UBK5PdAQ}h z@?2HW)KQ+}s<6Xd$2;?GwOqX!qv1HRE>{oo_Ok-o7R2jIAwW-Av}@%d~18MrP+R8`SrEL=9@&B&bB>LQmxCD$k~~i%-7vt1M#!uqGS*3@i^79q<3JJN7x?*n=-$CfHO=cw@fL@zsmK zDUWToMAtt0lzQy^yrZkbE`IxClhiw}Vbk-6yY;g9WfZoR>stxUysla6U44?b%*HS6 zby(E*_seUZ^KD(YaKX+%fwZ4@B$_TW{w(N9$T}r*@M>JV5TgRecXp-w?vl-YE*Eyn zY}{uT9?UAh#pohFO_b>*f3YpFSi; z#nn#zacynm@qdSBy**@9|MQ%z+PS%lt!!-GJD&bw7pCKO`rF%-@9%i$d=8uSgDah7 zi>#H)>34U1Vs<*^rlh#eDH50##^P{6!s69$iKf6q=d{){fu?~Be&_I<@65gH_3B07 zC4W25n&W&WR=t@e59-g>?KW)t^gv;>kC^J2%%?NF7)oGGj-@#7k#q|5#_e-f{IqhZ!$kDsIo0pLp%s z?n4QzVT@CPO6)$eY`<64{m|cT<`!PDmY)kQN^}Hm$>@z+I+a0?;n1qH-+G@foT9X? z&i3Ttc27AwpQQhFO4--$@cy}M886SpustH>$>#Gb?(8(qKU+Up|K{h8ZQCZ@-rRoe z?(U6`KZ;KOd;jxK20ty=>l>4KY1@+O%Xavx3Ml8U z*89NxB11%by7InM?w8Bwt9|`)+2!{yHF0)h=44scign&EpZOm1o4d%&&hG!y`lTJ; zzg;T(VX$$X-W4swQ<>Fo40p||Z2R-`a}cY*vv`)4V3yvX$-RwPqMcx$aQ>4JX8D&h z&7-ReKwi7+OTdO?sZQO4gfrZP`En$iZfn@9)*YOVHNN z*7-ZL^28s}0Btcz%epzYPH%o$s zC*R&K{`$Z%>+$0&$BsR*6BldAvhbR*!LVTIX&*s`L#H_Z^s?>uU|GI<_K$aWm&~h~ zbY-HlOI20c)d%LXUDwwaKc8F9^l+m?U#VC2sqJ}%zo+k4*7gA6=7bYm2i!RJ&yLeXQ*YUvE_RDrAF$&V_qhHtG26J$Iy)JLdOE|Bmlp zCfwd@ZNaJV@Q2pgq~?rdiz1 zGauOH4K`GpcixM8^!BZG-TX}x-?~Rfy|Ag8^6VMx~_O<%H^2TcOFtyDZb01B;y@bTwHtJH^~oyVy)SQ4A*Fo2al?WuhrABB zRQMDuoohRht6_tUvD0a#Ti4xReMk&C-Z$~aX7=5SnMK(iuxkFizuwWN|Ig9xC;D+m z)?XDnzCKR#L+z*Z^F?3X?Un9bbUgj-?drojj?74KJFqdiXRg)JBTrA?__)JH;80VL zY1$=qg{5D+xMnFav^;HnzVLBjQuOvqpKfKR#8-8Cgog{r&-l-_zx`O^+Le8MMs@FG zmP(t8eUGV{@$vq@qxv1+zX-g4r#9*Ax*u%^*yg(T8&y4GIr{qgQDKLO8_#WG!nGd0 zRFV%1NnVh{$Z=YW*<_yklI87pw(s|S{e6G`<*TjyKlc6q%3S@-n%lhX)7oeey_f}7 zMopWhJ3>-`F){twdngrMI_Z{?6W?_@J48MYz_QFS3QnJQID| zzB#v_cza*ogvm?PA;Lv!am(?Zg?da50%C~|*I(`U@=fUWmdulJ%}jr)=3W$5*NE9E zRPq0ONRLEw{QAeCT9a$JevrYNiw>>H;S&&=pxhZf z=jZA7q8FIfZ9R2DeSX2>KoVTeesf&5D5zrx_kGF&1argZt)|s0TD*4Se7&M zOyEmrH?h9NqM_5>dapum;y$}M^X4tBe56mRgI`2Fw6%Z9YNPc94`rcl4DX>kG6+ zLCaHWet+w!-}k<9%bPo%*NxBH+}L#EyWz13uU;$fO?2Kc-M0JtjT@6R;`X20D*u1R z#^B{2UX_0FvJ+)ka&7J6w+}#duM566lr94p#-8&4kuO*mipa?{=`artEnS=oq_ zPbS&kd0Ox(J~t`JZSujM=YnUSo_KrDx9s+|2e!5fJIrI=O?rJz@co-PTkRXR<^HLf z`%zeZ$G`9O{vVI^iq>_5`nYe-%}x3KCeo;%!*NEI^qm5plG!`B6qp<`su&k+IhL1y zJTKe%c#`+GpswwChJ`PK9_+SnRy3*G>sR)>Eb9B)!=5T~JLTu!+R*r>Pxi;x$fzET z)=N_B!yfo3hKWr&3OMUb{MzgFxE! zy9-79%FWBtpU3|He>CK)SD^m%nHKf)9!=FY=JT0YGV5D2d*;oZ#Zj`aW}cm$d3S$( z9;415%dHO+^Zxue&Qt&GX4iiIq@6Nv+4(2k+@>pWR%b86A)#PyZN|qB-+;Se!8x-% z%F7E_0$v}DzcF=@m6g}OJCLj@~>@A z|9fd`+Lt4oZg&4=*2l$fbbsO#q3M->%jNp!^N*GUDKWIDrfyPi*xlRM`?evilFOBW zw$af<{rAb`rya?k{k@$Vv9@f+g+M%zBu>y#Has{ z?LW4Ax4Jf4@miK0N0pY&HecD-x2~c)L4ESV*DnGuNt-WPd@*wVGJ%$#B8*!#dmGQ1 zSUa#ZT)4vI5Ghus)_nPLbC=unBg_ZNV~_dIT~zV+*Oun}pt?C}?p)BY#iHHSQID1? z3Ot%S_sO?+uGZ)7oco~NS{S=ZSvjP=P3?c)QZ9zIBAxf*tmap?MO0T8!~_dWivagu zHx`sRYzEI<&#H0^pVm6{?4vJV7A(H#T36KkX?OTqqhCLtPszV?fa`3v+|OIK{Mtf_ zn@hEK+Ey0`b-vsw-WLBj#b}9np30=t3w5)vg)%G^iQ6m3(Xer2sG(+nHD7jIWow9q z@u3r84Hs0L7F?8=!7t4`{a2+3qk>5(E5qqSr=F^@9!S!A=B3&5Ja9)*>!GWwuXr!b z-2M6ISF4FR(@gLGVB6T&C;9fNbko}AC%3k$Twm+#u2d`~`KFy|fsuhg$AqaiDjIKC zJ-&Ro@bL*~E#(I-(aLEp%NF_>cvbA-tN{%)p6!0WcyZvIb$V}N^3L%VZVr62@44ZJ zf4`@&voC-9JKu(1TS|3vDtD~EU8iXG*^IJAMxEn|7w`I}eNKDhl9X5X&+DNWR|p&X z$5T&33twlfeOd9wFskegN6VZL#ul}eOb6~BJ5YE&`qD86K?bMbFPQ@|LemPU~xh1(j}*~$8Jixy2ZD) zhfiw_Ul|}Nvt-_R_M{|Ho--$NZk2rh(rUuY<*UY&b!5~v;NR4g=Y)mopn8_&*e zIPoseOVj51(t?*lM^8`xxccf#Q-)u5r#5BW+}gcv`TS$%Y0qvhH*^$L5K`QlDg5zW zp3d=^wek(ub}SW|+SqvDXo0BD8RKIDp35`u?w>y`c6Zo1&Pf|`To2gxH72S$XfZTh z;AlFQkhAgsyA;I3XFO@?Q}?6+X{c5HGJ)RKFmrt%U$&NL!we{uJQUK9EX%1 zNFBZ*G9Q$KkJKk$*w#Du^usON*lvDL(!H0sU_BqdanYZOgum8zJFC1dZc1ifTmAjf znWNioNL-i|k*&S5SGurhr)4O^pQzqjFEp-BoEXX7`Lr#cXX7@rD1I4*t#!7iHZ2i% z$ncWtYy5e^uwdh>VkL%_XCe&iy6@idO1Zl$v;5xq_&?zlTca-gt^ZlHq5jW`2l+fd z>?U3di~9and2{CFKYPSZFZz2yaZ+vVkFB@n>&&yW zUu?d|6m+>fW9B%k#Uy3R7@Wqg^F>&T$sxlpMtt|?$uE}qcEA72A9DOSsNwzU!-p-_ zAEu^Gyt%3M&bnBa%{MDgn)MoP$+_*8_W79u$JtNSTBi@K4%3X;yK7b7QOV?vZCft< z{c`!>M6ctKrlJCgKMT%lJp8T2$gK%VDIf7_1X^dar(nsIx(q`4TjO1)d$rhi~p zX^E`0o67Xrf2X)QMkEMc$l#ITcIUh<_9MNEL15OG%m9nnSeM_wnz#-qy}hfo_T#Hk zHNCi$M~{wXvc?FP{X4#UH)y0&=nLS;0&y;HwSGAT(n>SUm8B3(^ zdE?5E;JSEC^;^@R+S+p0*$O6_rO6T>_f+?Bfcj;}o8JD8(AB*mE4w12GBdlr z_q$eHEwXQ&cB#`eJHxu9qas{9ZZW@;z^qUf2H}ScisxsrR>pzK^Y-V0FJB&f`EK5} z-}h>QcGb@A>=s|y*XQ^CdHdaG*>N=!GxW9#2=Z9ZRN0#;yt2SMW!r7;y0v0HrZK90 z4Z%kz&GoqO^N4WDwj55ES)f8;xmSYp9P>0s@RaA-?Qy&HCfv=qRh96?5UwnKV z6se}YFPo2*dgkBpIM1!WK>GQ==Rd!i?Tg;CdGeikwvDD6t{KngUtoId^NIPkyRBFq zDwGzyy`Z?sx?In*)Kef$nJH^wh8D5Ds^FKXM z_-UNJf{%A$&hOXp>z)gqP06^uz5knYyV#aBISkfQmG%}Y&-9(mlzw0)SHlNev2}4r zW0x{9EJ!`hXC+rZ$HQ#*3+81kiQAMGqC4lm!b?b>f*_;znC4PG|isKs+w`|-?Y2e#O5$`92Y;% zqH*W>ciaAj{B|5H2NoY`H++#X<;}OZDdletL}hG0^x;OF%Jjvl$N3hmU8}J+U2Kok z`^|BnG0ME1d$RnPeD?NvWp9e!e(CF%%Mt?R4vCQ$H}KBmXm30FR8ovl!Q|?ro*)ZZ zS&O-IUszh)KWp;)d#$~}f6HpI?GZe6->&cROXm4_FYXqfjKbXyS`jZ!GvD}VvG(`3 zi7#GqZqrB;+|y$yvOa#fk|I#!_0=m;hKHqRa&9`E-+cbjnQvBHhlDyATnnrcO8U|q zl^9x{o&2}eE#1iF_pb|`M;4u9Skc#G@Z;n0D~F!#XqRGWU2i<=^u@|A8Y@d*zx(%B zDbz#f_Oizxk9)3*Hs8HC=UBkLTI(6b8@%3cbu(T1-uk}c)%*J^<}Xw@vzTKAb8=$x zgB(Bh4hDf)$-duer(BCTsp41u{jPE;_g{s~UdB`9?*jIey`9KccjB23w{I)9 z&a&w^v?+O$VZkZT5|=}p)-bFH;aa|2UE=Ppj;H@Sy{Bv3-P8GTPkZ`*uZP#OA|`TV z$JNbD$$$Td-9Pz&gwjl&muF{pJ}cThEARRA(^uTwR!l#Ad6RYA#P73%EN<$&?YDJX zwLktBLrbc6?*YStL*GBntg~<6U|5(bmLl3I@bcxsyln1o55!lhM9WDj)qm5ux^Urw zmG2aP|3CKm!bOdhywWeMszlW1rfGH`10{spJBy#~`xf;$R4asyZAD+tg0PRd%X4Ob zY2#gTZSCQ{qiI(|G&#Jh_}lk~Kg>&3xGW7C#LnpZ@&5OVlu19Xt$k5ZGjZOW0}{(C zo^m}-KldP1D_+~`Pt{xxZ|=3fzX`s7HS4G0p2}sZ$N6sY$+_4c+VH~h{cYXpns;|@ zuKLZejKg1juG+PWf*e+Y42O!u7>#p4OU+E!_w`Tp61=qNeq&*J|@^R}Kka0S8XH~-uKEhdMED#izu5xTlAzkfIXed8*1A?eVXICrJXv#b~7N^cKP zsQ+ivsvZ6zru<}c+LpDw;EFj?*y5J&XP1U;xvyH8n&0m|{?z`r@u}u!ji!kQy54=Rfw*``0fbyU<`|UtiJl zyX7nWeRML}_ZqTkiz#o;6rP!Td&A?8FQ;!f%rJj{LF4E8PwFv0Cfh&!X8k^3Pp$O+ zICssht6CZ+pA^W>b+zuZ=|6NTDz5R7^EFmbD>sDUO&{N)*=D9nw-<3WWMpekjLD07 z8voSd$5*T7_xt@*Gcq#QNAKVjeEmjcdosJ9_vxNh>RM;Moi@)e`nCVpdW(6)t>`nAv#e?V(Bc=Xhn$*=9Cp-8$F% z%~vOS1TdaR(~RB!??l7(3>KZZeLf#=nQoeG#(JD_`ijak3}Gf-dDr!la&xDwi|4ES+sG`R8nYM*W|{U41f&yNlKRY~*wlv@7cGc&c=y7-if(mc2t; zOZEKn4Vzlm$1o+X+#E62D{=ew#g|{Yu4OQ>dbxP*+Q!9k8Sa0!el)77{o2WS%x|8` z_G-svwUpLdYyt(Oo z{`2|E+tSacIK*Tsm+&Sm4|0i)Vq!E1PF4jUFt9nI#}Iz;N6#1?*3rLo z$!W{p+A7|J#Ll~GLnQvaF}9jlGsz|W`J}kRZo<*eCo1>I*`NC{)7WC=l{5SOU+jA^ z_4L6V4-PsW^P8XcWb^q9>w<=F$K{vn&Z)aGW8ns&mIlT-6>~DGcYk5v0L?RAEQp`Q-@ZA&1GJB4f_-`4Hrwh8eVa?%PdF)Fu6-k6UiT~b znBSZk-Lo8Snw^sPQ#BW~I(%JB;)}!llD|GU@0wpbZQ5LG&$aRXYxc^XE}XVsfMff@ zMQhhaoK)%QJz1;?ngVn6h%dCV-e_$VkZ88;1;3+?+m^Mw)>FCE4*mRh{r&$o*-u+9 z27)HrN`9NY+_z6vn4x$6>=&<;Hdh+^sR{F#&dQTNaQNQh)Wk@!Fq5wM)rKGLRfG2U z=Cbo$D15^4ww-U{%`KUmD*b#bbx->;ED$Nva$T}@>ZM-kgMxvTR;{9abC?9CePio6 zUU=))2C3iwy&FV(h0dlGPCEJW_it_2y%9?a-by`<-ahMDRa*HQg}s%=|7`lVg>IAe z$@o&+X7_Ifcl{^zIe+y3d+YtXIp47GW73Du(<(2|nd@=q?m0nc1xGE0rVR%o!_B(y zUTpMJoBk}7Aw|rdgR>lp8IWz-eyqs z$Ksk(tnvNjW@g7ue0)4*U3~hRZv7K)?|Q#Ktv{K!zi5`5X@Vqcko81`Yd3d3nZFRU zA1bV!AtU?n%a;ee)TS@_li%Z*?Ym{Ktor1cfnuw3XCJz{x~J}^>R$c5E?c&ip4?_A zrE-0Z;`VIu@_Uu)23kQ`_l!SR&tlELv%t#c%!`%5Jas=5-?sB#F8!jRYh9jX>1R+l z@##h7gylgh)zwdLihzcTn--gvrK|guo8Q_d|0;a1gwv6eDIMiyivkwakeq9}Ya=z{959;$l zevftUpLBCuZ%DoU#y-9;Mi!<5o@$<7zOj9831MInWN6w@!06(t!nB zbshEi@-69j(iQ2;|CR}!(Y?K_al28nmt*G&u0Ws;p<-@_9-(ueY1_9`x?6d+8*s zp+5Pbm)gge@H-5wAdf0EdcA(Y;^5C;)%orJ`;-5aCMIwzNbOahU6;a?6It=pXsNXM zj2$JNZ@#^~G4aHuob;CWs*_GmxVd9uM7jCXgN`e%Pk&-;b}T?zHucA4f6chPQV~4z zc4uaV$0_d3yZhvI?y|Il$7%!g#SFjxy3-c~-mCm0YeG;IXgI?>=Rnu->0kcrFl5t~ zQr(#+Tc%^yy<*|YzTU#mZ?}Uwl6P3d_?=Swm5)A5LRvm6zmL;}Drv1pRt)S*@-0Dv8h7F4ZF1ycKC&&(wN>?d+JP>Yj?(hvqmQqwzM7d{ z@1? zWB$J0pY~&M|B1Kv{crE91uY7wdhGV*!h}xM6&xAI6))bFHqq{45Lm*b!0}mTfepWC zx8U79lA@g#blBJNOnCMBu%}x3mLH(yS`Vh3?wM=jnOacb>3v*ni&5W`q7vKhGM!tq zXP;Yiz3b?Oi5yE0I_CX*lRoG7^8Zte|NoFLetX=0S;b$gxTTv^7$a_)b*|I95_GOi zfCbb_iDGbBe8eo@&AgMrfQ7fV(=>a75NFN#+50BPIWtW00&QG;`t9w4lewJU?~i@I zxXt%{w|G*_ zqvD=xB8@ZGZMo2NKwyUTQETgsN`fJaEd%|Sz}1bMSY&cQU-Q*lU7O2vPi>p6b~(sv z;=LV$>Jty_cz1?*=6ldo=%1gTx4gO4I@d$zXLWbl<}daCf2WkcN%*q)yo7D#q(80P zSK8WFigW$=YUPzZB~{n=c(p`K?fv8WQ&&v)IwlbrotEDfNwQO_8!A3c@Q@$LI!spki0yE0_>7QMZ7K&ZlOy{G0W zW@bmxgoK7KzrK3M-8Iz2NOSptYZYjF#A8?deZ77J$P= z^+J_gS+w*<-`Pz?d7Gz%OCNO6Op=v#*%bNes8Uti&H2}6b)KFsX*0 zSB8B_$Ey<@pv4G3X5TmXbJ5)~yl$mPPI}AKNk`XkY1}?`CF_ZPT*{v}k(+LB6Fi$H zo>skdBkP3IiLQ%_M8NyTKZ}->n0YVREv-E_?-t+dihTWvF?_`jna+H9v*+55R;H^9 z74JAHzEICda=f-8Dj_oC_(4Cl&eDnHZBO$yhcYyz`14=97FLrlX8f?{V3*5=q>T%*Ya>O{&e%pPcxle5kV!u)6_kB# z!tK4Yzd5(h*iq8i*)6Uav(xF}r_?UtVDHxnZohrI zaM!M;cfZth)=M!>-{s-WU8W_v=a?khhG}!HGjH#gR~I`R�R6`1j-{2W!5S8;mZL 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 UPO9Q$1_lNOPgg&ebxsLQ0L?Fx!2kdN literal 0 HcmV?d00001 diff --git a/app/src/main/play/pl-PL/listing/phoneScreenshots/account-switcher.png b/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/account-switcher.png similarity index 100% rename from app/src/main/play/pl-PL/listing/phoneScreenshots/account-switcher.png rename to app/src/main/play/listings/pl-PL/graphics/phone-screenshots/account-switcher.png diff --git a/app/src/main/play/pl-PL/listing/phoneScreenshots/attendance-dialog.png b/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/attendance-dialog.png similarity index 100% rename from app/src/main/play/pl-PL/listing/phoneScreenshots/attendance-dialog.png rename to app/src/main/play/listings/pl-PL/graphics/phone-screenshots/attendance-dialog.png diff --git a/app/src/main/play/pl-PL/listing/phoneScreenshots/attendance-statistics.png b/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/attendance-statistics.png similarity index 100% rename from app/src/main/play/pl-PL/listing/phoneScreenshots/attendance-statistics.png rename to app/src/main/play/listings/pl-PL/graphics/phone-screenshots/attendance-statistics.png diff --git a/app/src/main/play/pl-PL/listing/phoneScreenshots/grades.png b/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/grades.png similarity index 100% rename from app/src/main/play/pl-PL/listing/phoneScreenshots/grades.png rename to app/src/main/play/listings/pl-PL/graphics/phone-screenshots/grades.png diff --git a/app/src/main/play/pl-PL/listing/phoneScreenshots/more.png b/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/more.png similarity index 100% rename from app/src/main/play/pl-PL/listing/phoneScreenshots/more.png rename to app/src/main/play/listings/pl-PL/graphics/phone-screenshots/more.png diff --git a/app/src/main/play/pl-PL/listing/phoneScreenshots/timetable-dialog.png b/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/timetable-dialog.png similarity index 100% rename from app/src/main/play/pl-PL/listing/phoneScreenshots/timetable-dialog.png rename to app/src/main/play/listings/pl-PL/graphics/phone-screenshots/timetable-dialog.png diff --git a/app/src/main/play/pl-PL/listing/phoneScreenshots/timetable-widget.png b/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/timetable-widget.png similarity index 100% rename from app/src/main/play/pl-PL/listing/phoneScreenshots/timetable-widget.png rename to app/src/main/play/listings/pl-PL/graphics/phone-screenshots/timetable-widget.png diff --git a/app/src/main/play/pl-PL/listing/shortdescription b/app/src/main/play/listings/pl-PL/short-description.txt similarity index 100% rename from app/src/main/play/pl-PL/listing/shortdescription rename to app/src/main/play/listings/pl-PL/short-description.txt diff --git a/app/src/main/play/pl-PL/listing/title b/app/src/main/play/listings/pl-PL/title.txt similarity index 100% rename from app/src/main/play/pl-PL/listing/title rename to app/src/main/play/listings/pl-PL/title.txt diff --git a/app/src/main/play/pl-PL/listing/icon/icon.png b/app/src/main/play/pl-PL/listing/icon/icon.png deleted file mode 100644 index de216d36b258956f80fca256b2f0e7dca99ed3e7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19242 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 N;pyt{!{3*-O* diff --git a/app/src/main/play/pl-PL/listing/video b/app/src/main/play/pl-PL/listing/video deleted file mode 100644 index e69de29bb..000000000 diff --git a/app/src/main/play/pl-PL/whatsnew b/app/src/main/play/release-notes/pl-PL/default.txt similarity index 100% rename from app/src/main/play/pl-PL/whatsnew rename to app/src/main/play/release-notes/pl-PL/default.txt diff --git a/build.gradle b/build.gradle index 26becfc7c..444088e1c 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ buildscript { classpath 'com.android.tools.build:gradle:3.3.0' classpath 'com.google.gms:google-services:4.2.0' classpath "io.fabric.tools:gradle:1.27.0" - classpath "com.github.triplet.gradle:play-publisher:1.2.2" + classpath "com.github.triplet.gradle:play-publisher:2.1.0" classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7" classpath 'com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta02' } From 4da812af392ffbdf55960f8bb8d0d0f46721531b Mon Sep 17 00:00:00 2001 From: Kacper Ziubryniewicz Date: Fri, 25 Jan 2019 20:54:27 +0100 Subject: [PATCH 017/121] Add lucky numbers (#216) --- .travis.yml | 2 +- .../local/LuckyNumberLocalTest.kt | 47 +++++++++++ .../github/wulkanowy/data/RepositoryModule.kt | 4 + .../github/wulkanowy/data/db/AppDatabase.kt | 15 +++- .../wulkanowy/data/db/dao/LuckyNumberDao.kt | 29 +++++++ .../wulkanowy/data/db/entities/LuckyNumber.kt | 28 +++++++ .../data/db/migrations/Migration2.kt | 16 ++++ .../repositories/LuckyNumberRepository.kt | 52 +++++++++++++ .../repositories/local/LuckyNumberLocal.kt | 30 +++++++ .../repositories/remote/LuckyNumberRemote.kt | 26 +++++++ .../wulkanowy/services/job/SyncWorker.kt | 44 ++++++++--- .../notification/LuckyNumberNotification.kt | 48 ++++++++++++ .../luckynumber/LuckyNumberFragment.kt | 64 +++++++++++++++ .../luckynumber/LuckyNumberPresenter.kt | 65 ++++++++++++++++ .../ui/modules/luckynumber/LuckyNumberView.kt | 21 +++++ .../wulkanowy/ui/modules/main/MainModule.kt | 5 ++ .../wulkanowy/ui/modules/more/MoreFragment.kt | 13 ++++ .../ui/modules/more/MorePresenter.kt | 2 + .../wulkanowy/ui/modules/more/MoreView.kt | 4 + .../ic_stat_notify_lucky_number.xml | 14 ++++ .../ic_stat_notify_lucky_number.png | Bin 0 -> 473 bytes .../ic_stat_notify_lucky_number.png | Bin 0 -> 343 bytes .../ic_stat_notify_lucky_number.png | Bin 0 -> 713 bytes .../ic_stat_notify_lucky_number.png | Bin 0 -> 1088 bytes .../drawable/ic_more_lucky_number_24dp.xml | 9 +++ .../main/res/layout/fragment_lucky_number.xml | 73 ++++++++++++++++++ app/src/main/res/values-pl/strings.xml | 9 +++ app/src/main/res/values/strings.xml | 9 +++ .../remote/LuckyNumberRemoteTest.kt | 44 +++++++++++ 29 files changed, 658 insertions(+), 15 deletions(-) create mode 100644 app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/LuckyNumberLocalTest.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/dao/LuckyNumberDao.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/entities/LuckyNumber.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration2.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/local/LuckyNumberLocal.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/remote/LuckyNumberRemote.kt create mode 100644 app/src/main/java/io/github/wulkanowy/services/notification/LuckyNumberNotification.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberFragment.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberView.kt create mode 100644 app/src/main/res/drawable-anydpi-v24/ic_stat_notify_lucky_number.xml create mode 100644 app/src/main/res/drawable-hdpi/ic_stat_notify_lucky_number.png create mode 100644 app/src/main/res/drawable-mdpi/ic_stat_notify_lucky_number.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_stat_notify_lucky_number.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_stat_notify_lucky_number.png create mode 100644 app/src/main/res/drawable/ic_more_lucky_number_24dp.xml create mode 100644 app/src/main/res/layout/fragment_lucky_number.xml create mode 100644 app/src/test/java/io/github/wulkanowy/data/repositories/remote/LuckyNumberRemoteTest.kt diff --git a/.travis.yml b/.travis.yml index a430c90ef..9034de610 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,7 +41,7 @@ before_script: - "curl -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/fossas/fossa-cli/master/install.sh | sudo bash" script: - - ./gradlew build -x test -x lint -x fabricGenerateResourcesRelease -x packageRelease --stacktrace --daemon + - ./gradlew dependencies --stacktrace --daemon - fossa --no-ansi || true - ./gradlew lint -x fabricGenerateResourcesRelease --stacktrace --daemon - ./gradlew test -x fabricGenerateResourcesRelease --stacktrace --daemon diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/LuckyNumberLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/LuckyNumberLocalTest.kt new file mode 100644 index 000000000..67ac4c2b4 --- /dev/null +++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/LuckyNumberLocalTest.kt @@ -0,0 +1,47 @@ +package io.github.wulkanowy.data.repositories.local + +import androidx.room.Room +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.github.wulkanowy.data.db.AppDatabase +import io.github.wulkanowy.data.db.entities.LuckyNumber +import io.github.wulkanowy.data.db.entities.Semester +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.threeten.bp.LocalDate +import kotlin.test.assertEquals + +@RunWith(AndroidJUnit4::class) +class LuckyNumberLocalTest { + + private lateinit var luckyNumberLocal: LuckyNumberLocal + + private lateinit var testDb: AppDatabase + + @Before + fun createDb() { + testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java) + .build() + luckyNumberLocal = LuckyNumberLocal(testDb.luckyNumberDao) + } + + @After + fun closeDb() { + testDb.close() + } + + @Test + fun saveAndReadTest() { + luckyNumberLocal.saveLuckyNumber(LuckyNumber(1, LocalDate.of(2019, 1, 20), 14)) + + val luckyNumber = luckyNumberLocal.getLuckyNumber(Semester(1, 1, 2, "", 3, 1), + LocalDate.of(2019, 1, 20) + ).blockingGet() + + assertEquals(1, luckyNumber.studentId) + assertEquals(LocalDate.of(2019, 1, 20), luckyNumber.date) + assertEquals(14, luckyNumber.luckyNumber) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt b/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt index f5bb1b2a4..e72175bb8 100644 --- a/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt +++ b/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt @@ -113,4 +113,8 @@ internal class RepositoryModule { @Singleton @Provides fun provideSubjectDao(database: AppDatabase) = database.subjectDao + + @Singleton + @Provides + fun provideLuckyNumberDao(database: AppDatabase) = database.luckyNumberDao } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt index 30c957b43..974d5daba 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt @@ -6,6 +6,8 @@ import androidx.room.Room import androidx.room.RoomDatabase import androidx.room.RoomDatabase.JournalMode.TRUNCATE import androidx.room.TypeConverters +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase import io.github.wulkanowy.data.db.dao.AttendanceDao import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao import io.github.wulkanowy.data.db.dao.ExamDao @@ -13,6 +15,7 @@ import io.github.wulkanowy.data.db.dao.GradeDao import io.github.wulkanowy.data.db.dao.GradeSummaryDao import io.github.wulkanowy.data.db.dao.MessagesDao import io.github.wulkanowy.data.db.dao.HomeworkDao +import io.github.wulkanowy.data.db.dao.LuckyNumberDao import io.github.wulkanowy.data.db.dao.NoteDao import io.github.wulkanowy.data.db.dao.SemesterDao import io.github.wulkanowy.data.db.dao.StudentDao @@ -25,11 +28,13 @@ import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Homework +import io.github.wulkanowy.data.db.entities.LuckyNumber import io.github.wulkanowy.data.db.entities.Note import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Subject import io.github.wulkanowy.data.db.entities.Timetable +import io.github.wulkanowy.data.db.migrations.Migration2 import javax.inject.Singleton @Singleton @@ -46,9 +51,10 @@ import javax.inject.Singleton Message::class, Note::class, Homework::class, - Subject::class + Subject::class, + LuckyNumber::class ], - version = 1, + version = 2, exportSchema = false ) @TypeConverters(Converters::class) @@ -58,6 +64,9 @@ abstract class AppDatabase : RoomDatabase() { fun newInstance(context: Context): AppDatabase { return Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database") .setJournalMode(TRUNCATE) + .addMigrations( + Migration2() + ) .build() } } @@ -85,4 +94,6 @@ abstract class AppDatabase : RoomDatabase() { abstract val homeworkDao: HomeworkDao abstract val subjectDao: SubjectDao + + abstract val luckyNumberDao: LuckyNumberDao } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/LuckyNumberDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/LuckyNumberDao.kt new file mode 100644 index 000000000..05736e42d --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/LuckyNumberDao.kt @@ -0,0 +1,29 @@ +package io.github.wulkanowy.data.db.dao + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.Query +import androidx.room.Update +import io.github.wulkanowy.data.db.entities.LuckyNumber +import io.reactivex.Maybe +import org.threeten.bp.LocalDate +import javax.inject.Singleton + +@Singleton +@Dao +interface LuckyNumberDao { + + @Insert + fun insert(luckyNumber: LuckyNumber) + + @Update + fun update(luckyNumber: LuckyNumber) + + @Delete + fun delete(luckyNumber: LuckyNumber) + + @Query("SELECT * FROM LuckyNumbers WHERE student_id = :studentId AND date = :date") + fun loadFromDate(studentId: Int, date: LocalDate): Maybe + +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/LuckyNumber.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/LuckyNumber.kt new file mode 100644 index 000000000..6144b7429 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/LuckyNumber.kt @@ -0,0 +1,28 @@ +package io.github.wulkanowy.data.db.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import org.threeten.bp.LocalDate +import java.io.Serializable + +@Entity(tableName = "LuckyNumbers") +data class LuckyNumber ( + + @ColumnInfo(name = "student_id") + var studentId: Int, + + var date: LocalDate, + + @ColumnInfo(name = "lucky_number") + var luckyNumber: Int + +) : Serializable { + + @PrimaryKey(autoGenerate = true) + var id: Long = 0 + + @ColumnInfo(name = "is_notified") + var isNotified: Boolean = true + +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration2.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration2.kt new file mode 100644 index 000000000..30ddc3bbb --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration2.kt @@ -0,0 +1,16 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration2 : Migration(1, 2) { + + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("CREATE TABLE LuckyNumbers (" + + "id INTEGER NOT NULL PRIMARY KEY, " + + "is_notified INTEGER NOT NULL, " + + "student_id INTEGER NOT NULL, " + + "date INTEGER NOT NULL, " + + "lucky_number INTEGER NOT NULL)") + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt new file mode 100644 index 000000000..737dc925b --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt @@ -0,0 +1,52 @@ +package io.github.wulkanowy.data.repositories + +import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork +import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings +import io.github.wulkanowy.data.db.entities.LuckyNumber +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.repositories.local.LuckyNumberLocal +import io.github.wulkanowy.data.repositories.remote.LuckyNumberRemote +import io.reactivex.Completable +import io.reactivex.Maybe +import org.threeten.bp.LocalDate +import java.net.UnknownHostException +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class LuckyNumberRepository @Inject constructor( + private val settings: InternetObservingSettings, + private val local: LuckyNumberLocal, + private val remote: LuckyNumberRemote +) { + + fun getLuckyNumber(semester: Semester, forceRefresh: Boolean = false, notify: Boolean = false): Maybe { + return local.getLuckyNumber(semester, LocalDate.now()).filter { !forceRefresh } + .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) + .flatMapMaybe { + if (it) remote.getLuckyNumber(semester) + else Maybe.error(UnknownHostException()) + }.flatMap { new -> + local.getLuckyNumber(semester, LocalDate.now()) + .doOnSuccess { old -> + if (new != old) { + local.deleteLuckyNumber(old) + local.saveLuckyNumber(new.apply { + if (notify) isNotified = false + }) + } + } + .doOnComplete { + local.saveLuckyNumber(new.apply { + if (notify) isNotified = false + }) + } + }.flatMap({ local.getLuckyNumber(semester, LocalDate.now()) }, { Maybe.error(it) }, + { local.getLuckyNumber(semester, LocalDate.now()) }) + ) + } + + fun updateLuckyNumber(luckyNumber: LuckyNumber): Completable { + return local.updateLuckyNumber(luckyNumber) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/local/LuckyNumberLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/local/LuckyNumberLocal.kt new file mode 100644 index 000000000..a04ea7404 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/local/LuckyNumberLocal.kt @@ -0,0 +1,30 @@ +package io.github.wulkanowy.data.repositories.local + +import io.github.wulkanowy.data.db.dao.LuckyNumberDao +import io.github.wulkanowy.data.db.entities.LuckyNumber +import io.github.wulkanowy.data.db.entities.Semester +import io.reactivex.Completable +import io.reactivex.Maybe +import org.threeten.bp.LocalDate +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class LuckyNumberLocal @Inject constructor(private val luckyNumberDb: LuckyNumberDao) { + + fun getLuckyNumber(semester: Semester, date: LocalDate): Maybe { + return luckyNumberDb.loadFromDate(semester.studentId, date) + } + + fun saveLuckyNumber(luckyNumber: LuckyNumber) { + luckyNumberDb.insert(luckyNumber) + } + + fun updateLuckyNumber(luckyNumber: LuckyNumber): Completable { + return Completable.fromCallable { luckyNumberDb.update(luckyNumber) } + } + + fun deleteLuckyNumber(luckyNumber: LuckyNumber) { + luckyNumberDb.delete(luckyNumber) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/remote/LuckyNumberRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/remote/LuckyNumberRemote.kt new file mode 100644 index 000000000..36ff57f94 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/remote/LuckyNumberRemote.kt @@ -0,0 +1,26 @@ +package io.github.wulkanowy.data.repositories.remote + +import io.github.wulkanowy.api.Api +import io.github.wulkanowy.data.db.entities.LuckyNumber +import io.github.wulkanowy.data.db.entities.Semester +import io.reactivex.Maybe +import io.reactivex.Single +import org.threeten.bp.LocalDate +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class LuckyNumberRemote @Inject constructor(private val api: Api) { + + fun getLuckyNumber(semester: Semester): Maybe { + return Single.just(api.apply { diaryId = semester.diaryId }) + .flatMapMaybe { it.getLuckyNumber() } + .map { + LuckyNumber( + studentId = semester.studentId, + date = LocalDate.now(), + luckyNumber = it + ) + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/job/SyncWorker.kt b/app/src/main/java/io/github/wulkanowy/services/job/SyncWorker.kt index cf09728a6..5ad8ce0ff 100644 --- a/app/src/main/java/io/github/wulkanowy/services/job/SyncWorker.kt +++ b/app/src/main/java/io/github/wulkanowy/services/job/SyncWorker.kt @@ -8,6 +8,7 @@ import io.github.wulkanowy.data.repositories.ExamRepository import io.github.wulkanowy.data.repositories.GradeRepository import io.github.wulkanowy.data.repositories.GradeSummaryRepository import io.github.wulkanowy.data.repositories.HomeworkRepository +import io.github.wulkanowy.data.repositories.LuckyNumberRepository import io.github.wulkanowy.data.repositories.MessagesRepository import io.github.wulkanowy.data.repositories.MessagesRepository.MessageFolder.RECEIVED import io.github.wulkanowy.data.repositories.NoteRepository @@ -16,12 +17,13 @@ import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.TimetableRepository import io.github.wulkanowy.services.notification.GradeNotification +import io.github.wulkanowy.services.notification.LuckyNumberNotification import io.github.wulkanowy.services.notification.MessageNotification import io.github.wulkanowy.services.notification.NoteNotification import io.github.wulkanowy.utils.friday import io.github.wulkanowy.utils.isHolidays import io.github.wulkanowy.utils.monday -import io.reactivex.Single +import io.reactivex.Completable import io.reactivex.disposables.CompositeDisposable import org.threeten.bp.LocalDate import timber.log.Timber @@ -59,6 +61,9 @@ class SyncWorker : SimpleJobService() { @Inject lateinit var homework: HomeworkRepository + @Inject + lateinit var luckyNumber: LuckyNumberRepository + @Inject lateinit var prefRepository: PreferencesRepository @@ -88,18 +93,19 @@ class SyncWorker : SimpleJobService() { disposable.add(student.getCurrentStudent() .flatMap { semester.getCurrentSemester(it, true).map { semester -> semester to it } } - .flatMapPublisher { - Single.merge( + .flatMapCompletable { + Completable.merge( listOf( - gradesDetails.getGrades(it.first, true, notify), - gradesSummary.getGradesSummary(it.first, true), - attendance.getAttendance(it.first, start, end, true), - exam.getExams(it.first, start, end, true), - timetable.getTimetable(it.first, start, end, true), - message.getMessages(it.second, RECEIVED, true, notify), - note.getNotes(it.first, true, notify), - homework.getHomework(it.first, LocalDate.now(), true), - homework.getHomework(it.first, LocalDate.now().plusDays(1), true) + gradesDetails.getGrades(it.first, true, notify).ignoreElement(), + gradesSummary.getGradesSummary(it.first, true).ignoreElement(), + attendance.getAttendance(it.first, start, end, true).ignoreElement(), + exam.getExams(it.first, start, end, true).ignoreElement(), + timetable.getTimetable(it.first, start, end, true).ignoreElement(), + message.getMessages(it.second, RECEIVED, true, notify).ignoreElement(), + note.getNotes(it.first, true, notify).ignoreElement(), + homework.getHomework(it.first, LocalDate.now(), true).ignoreElement(), + homework.getHomework(it.first, LocalDate.now().plusDays(1), true).ignoreElement(), + luckyNumber.getLuckyNumber(it.first, true, notify).ignoreElement() ) ) } @@ -119,6 +125,7 @@ class SyncWorker : SimpleJobService() { sendGradeNotifications() sendMessageNotification() sendNoteNotification() + sendLuckyNumberNotification() } private fun sendGradeNotifications() { @@ -170,6 +177,19 @@ class SyncWorker : SimpleJobService() { ) } + private fun sendLuckyNumberNotification() { + disposable.add(student.getCurrentStudent() + .flatMap { semester.getCurrentSemester(it) } + .flatMapMaybe { luckyNumber.getLuckyNumber(it) } + .filter { !it.isNotified } + .doOnSuccess { + LuckyNumberNotification(applicationContext).sendNotification(it) + } + .map { it.apply { isNotified = true } } + .flatMapCompletable { luckyNumber.updateLuckyNumber(it) } + .subscribe({}, { Timber.e("Lucky number notification sending failed") })) + } + override fun onDestroy() { super.onDestroy() disposable.clear() diff --git a/app/src/main/java/io/github/wulkanowy/services/notification/LuckyNumberNotification.kt b/app/src/main/java/io/github/wulkanowy/services/notification/LuckyNumberNotification.kt new file mode 100644 index 000000000..9db159aad --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/notification/LuckyNumberNotification.kt @@ -0,0 +1,48 @@ +package io.github.wulkanowy.services.notification + +import android.annotation.TargetApi +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Context +import androidx.core.app.NotificationCompat +import androidx.core.content.ContextCompat +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.LuckyNumber +import io.github.wulkanowy.ui.modules.main.MainActivity + +class LuckyNumberNotification(context: Context) : BaseNotification(context) { + + private val channelId = "Lucky_Number_Notify" + + @TargetApi(26) + override fun createChannel(channelId: String) { + notificationManager.createNotificationChannel(NotificationChannel( + channelId, context.getString(R.string.notify_lucky_number_channel), NotificationManager.IMPORTANCE_HIGH + ).apply { + enableLights(true) + enableVibration(true) + lockscreenVisibility = Notification.VISIBILITY_PUBLIC + }) + } + + fun sendNotification(luckyNumber: LuckyNumber) { + notify(notificationBuilder(channelId) + .setContentTitle(context.getString(R.string.notify_lucky_number_new_item_title)) + .setContentText(context.getString(R.string.notify_lucky_number_new_item, luckyNumber.luckyNumber)) + .setSmallIcon(R.drawable.ic_stat_notify_lucky_number) + .setAutoCancel(true) + .setDefaults(NotificationCompat.DEFAULT_ALL) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setColor(ContextCompat.getColor(context, R.color.colorPrimary)) + .setContentIntent( + PendingIntent.getActivity(context, 0, + MainActivity.getStartIntent(context).putExtra(MainActivity.EXTRA_START_MENU_INDEX, 4), + PendingIntent.FLAG_UPDATE_CURRENT + ) + ) + .build() + ) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberFragment.kt new file mode 100644 index 000000000..d535e8255 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberFragment.kt @@ -0,0 +1,64 @@ +package io.github.wulkanowy.ui.modules.luckynumber + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.LuckyNumber +import io.github.wulkanowy.ui.base.session.BaseSessionFragment +import io.github.wulkanowy.ui.modules.main.MainView +import kotlinx.android.synthetic.main.fragment_lucky_number.* +import javax.inject.Inject + +class LuckyNumberFragment : BaseSessionFragment(), LuckyNumberView, MainView.TitledView { + + @Inject + lateinit var presenter: LuckyNumberPresenter + + companion object { + fun newInstance() = LuckyNumberFragment() + } + + override val titleStringId: Int + get() = R.string.lucky_number_title + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_lucky_number, container, false) + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + presenter.onAttachView(this) + } + + override fun initView() { + showContent(false) + showProgress(true) + luckyNumberSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } + } + + override fun updateData(data: LuckyNumber) { + luckyNumberText.text = data.luckyNumber.toString() + } + + override fun hideRefresh() { + luckyNumberSwipe.isRefreshing = false + } + + override fun showEmpty(show: Boolean) { + luckyNumberEmpty.visibility = if (show) View.VISIBLE else View.GONE + } + + override fun showProgress(show: Boolean) { + luckyNumberProgress.visibility = if (show) View.VISIBLE else View.GONE + } + + override fun showContent(show: Boolean) { + luckyNumberContent.visibility = if (show) View.VISIBLE else View.GONE + } + + override fun isViewEmpty(): Boolean { + return luckyNumberText.text.isBlank() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt new file mode 100644 index 000000000..50de42e57 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt @@ -0,0 +1,65 @@ +package io.github.wulkanowy.ui.modules.luckynumber + +import io.github.wulkanowy.data.repositories.LuckyNumberRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.session.SessionErrorHandler +import io.github.wulkanowy.utils.FirebaseAnalyticsHelper +import io.github.wulkanowy.utils.SchedulersProvider +import io.reactivex.MaybeSource +import javax.inject.Inject + +class LuckyNumberPresenter @Inject constructor( + private val errorHandler: SessionErrorHandler, + private val schedulers: SchedulersProvider, + private val luckyNumberRepository: LuckyNumberRepository, + private val studentRepository: StudentRepository, + private val semesterRepository: SemesterRepository, + private val analytics: FirebaseAnalyticsHelper +) : BasePresenter(errorHandler) { + + override fun onAttachView(view: LuckyNumberView) { + super.onAttachView(view) + view.initView() + loadData() + } + + private fun loadData(forceRefresh: Boolean = false) { + disposable.apply { + clear() + add(studentRepository.getCurrentStudent() + .flatMap { semesterRepository.getCurrentSemester(it) } + .flatMapMaybe { luckyNumberRepository.getLuckyNumber(it, forceRefresh) } + .subscribeOn(schedulers.backgroundThread) + .observeOn(schedulers.mainThread) + .doFinally { + view?.run { + hideRefresh() + showProgress(false) + } + } + .subscribe({ + view?.apply { + updateData(it) + showContent(true) + showEmpty(false) + } + analytics.logEvent("load_lucky_number", mapOf("force_refresh" to forceRefresh)) + }, { + view?.run { showEmpty(isViewEmpty()) } + errorHandler.dispatch(it) + }, { + view?.run { + showContent(false) + showEmpty(true) + } + }) + ) + } + } + + fun onSwipeRefresh() { + loadData(true) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberView.kt new file mode 100644 index 000000000..eece5b705 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberView.kt @@ -0,0 +1,21 @@ +package io.github.wulkanowy.ui.modules.luckynumber + +import io.github.wulkanowy.data.db.entities.LuckyNumber +import io.github.wulkanowy.ui.base.session.BaseSessionView + +interface LuckyNumberView : BaseSessionView { + + fun initView() + + fun updateData(data: LuckyNumber) + + fun hideRefresh() + + fun showEmpty(show: Boolean) + + fun showProgress(show: Boolean) + + fun showContent(show: Boolean) + + fun isViewEmpty(): Boolean +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainModule.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainModule.kt index 9d1c44c07..1d98ce636 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainModule.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainModule.kt @@ -16,6 +16,7 @@ import io.github.wulkanowy.ui.modules.exam.ExamFragment import io.github.wulkanowy.ui.modules.grade.GradeFragment import io.github.wulkanowy.ui.modules.grade.GradeModule import io.github.wulkanowy.ui.modules.homework.HomeworkFragment +import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment import io.github.wulkanowy.ui.modules.message.MessageFragment import io.github.wulkanowy.ui.modules.message.MessageModule import io.github.wulkanowy.ui.modules.message.preview.MessagePreviewFragment @@ -86,6 +87,10 @@ abstract class MainModule { @ContributesAndroidInjector abstract fun bindHomeworkFragment(): HomeworkFragment + @PerFragment + @ContributesAndroidInjector + abstract fun bindLuckyNumberFragment(): LuckyNumberFragment + @PerFragment @ContributesAndroidInjector abstract fun bindsAccountDialog(): AccountDialog diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreFragment.kt index 226fccd6d..5bbf6247a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreFragment.kt @@ -13,6 +13,7 @@ import io.github.wulkanowy.R import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.about.AboutFragment import io.github.wulkanowy.ui.modules.homework.HomeworkFragment +import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.message.MessageFragment @@ -60,6 +61,14 @@ class MoreFragment : BaseFragment(), MoreView, MainView.TitledView, MainView.Mai } } + override val luckyNumberRes: Pair? + get() { + return context?.run { + getString(R.string.lucky_number_title) to + ContextCompat.getDrawable(this, R.drawable.ic_more_lucky_number_24dp) + } + } + override val settingsRes: Pair? get() { return context?.run { @@ -114,6 +123,10 @@ class MoreFragment : BaseFragment(), MoreView, MainView.TitledView, MainView.Mai (activity as? MainActivity)?.pushView(NoteFragment.newInstance()) } + override fun openLuckyNumberView() { + (activity as? MainActivity)?.pushView(LuckyNumberFragment.newInstance()) + } + override fun openSettingsView() { (activity as? MainActivity)?.pushView(SettingsFragment.newInstance()) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MorePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MorePresenter.kt index 6becc8ad1..5fea241c7 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MorePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MorePresenter.kt @@ -23,6 +23,7 @@ class MorePresenter @Inject constructor(errorHandler: ErrorHandler) : BasePresen messagesRes?.first -> openMessagesView() homeworkRes?.first -> openHomeworkView() noteRes?.first -> openNoteView() + luckyNumberRes?.first -> openLuckyNumberView() settingsRes?.first -> openSettingsView() aboutRes?.first -> openAboutView() } @@ -42,6 +43,7 @@ class MorePresenter @Inject constructor(errorHandler: ErrorHandler) : BasePresen messagesRes?.let { MoreItem(it.first, it.second) }, homeworkRes?.let { MoreItem(it.first, it.second) }, noteRes?.let { MoreItem(it.first, it.second) }, + luckyNumberRes?.let { MoreItem(it.first, it.second) }, settingsRes?.let { MoreItem(it.first, it.second) }, aboutRes?.let { MoreItem(it.first, it.second) }) ) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreView.kt index 08ff82adb..333edc5df 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreView.kt @@ -11,6 +11,8 @@ interface MoreView : BaseView { val noteRes: Pair? + val luckyNumberRes: Pair? + val settingsRes: Pair? val aboutRes: Pair? @@ -30,4 +32,6 @@ interface MoreView : BaseView { fun openHomeworkView() fun openNoteView() + + fun openLuckyNumberView() } diff --git a/app/src/main/res/drawable-anydpi-v24/ic_stat_notify_lucky_number.xml b/app/src/main/res/drawable-anydpi-v24/ic_stat_notify_lucky_number.xml new file mode 100644 index 000000000..00538168f --- /dev/null +++ b/app/src/main/res/drawable-anydpi-v24/ic_stat_notify_lucky_number.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/app/src/main/res/drawable-hdpi/ic_stat_notify_lucky_number.png b/app/src/main/res/drawable-hdpi/ic_stat_notify_lucky_number.png new file mode 100644 index 0000000000000000000000000000000000000000..b91c4ae6cd7f35cc89f9920e42527c8f3bc816b1 GIT binary patch literal 473 zcmeAS@N?(olHy`uVBq!ia0y~yU{C>J4mJh`hKCF@W-u@?F7R}545_&FcKX4rLk^HI4e`@E{W(jW5cs4o`c&(|y*&vL)d`4zHZPFPZYiiOX}HiBJERiT}5hzu2vLf1k<) zBlGQ3RW4j)dZEHuvVpmu`nKlzTOla_>}q zmG9o}^*{N?8p+lETmDX+DkHnjbLFlBjui(EzF3@_vLRFbh4BH#TE`OCty8wGnzBK| zzUE|AMsnZ+*GUfP+zXmjE}Ud~p~1Oj6GN02(^p-+E?f6Y0gu{t=q=x2Cm#Ni;reS? hp*7DzLHdm~=Xb)|NwfG)GB7YOc)I$ztaD0e0sv<**-Zcd literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_stat_notify_lucky_number.png b/app/src/main/res/drawable-mdpi/ic_stat_notify_lucky_number.png new file mode 100644 index 0000000000000000000000000000000000000000..bfced4eb088c8e2818255e0c50634dd72673fee9 GIT binary patch literal 343 zcmeAS@N?(olHy`uVBq!ia0y~yV2}V|4mJh`h6m-gKNuJo}P4L!)$WWe*a zDScuAv&|=MiMo~#st589h%>~wzCWODxqC?`?VSHd%SX%G}erEMClwhujoQ1uW{FUcAs{)^cdt7pIVa(n-r_ zr7~xUH`4)5gS8X-f8_6Mh|_XCDsZ%6<`16X9-sOST5mT9+>Ps??ZMhSmA@XUxbW0 yJGk(B@a7j&yRSAow=n(nnY8irv`6P}3d@E6IhTJtZ2~)jkNY=b`A6=dTqJxR6m>ZmvWE>N2bs62rVu#sjqV;T1uR&K6~fU zvK#z%LV4fIp5H$H=1iR4^9K(eG<>kh=d9pa!>sOfYIy^DNczI*Jo?P}d-i8A9Pd0k z;gi^gl-oa)KHTg+#%##-P>F}sW#=#hT5ac{v@!iWBbPcFMjDJ-G?WBukd6^c=K=VizmI3R{S+8=Pqxa+HuqK zPR*cyYePR@0da*H-&r(H#B?XITdztUj7c5Z8*>jq2)7u%%XT*1`wm8A3 z$~3KYp-ZfwS4gYzMh(FW;)U0RS=f@M@*KP)#oFSTk*UAt(VE?NXYvZo{0% zRtHy4-D2rD@s6haO+Hn5*0clNNx}OzF`fvXbS3R+{1l(FGftdg(>N>5ZF7EkOjhCM z2#zh5PPM#$-PhG*DXg^LUa5W2OyR+~X&(=>>2Gtpdv%f10fpEzX{=Q{>Y`7-xYW?V zJ$rV=wFB&ZN$1ou^jq#Kn%-1j=5{33-}1Wl0hKnp2eK_k-%Uxrvhv)d-#V`zJowNp Z-&}TvbH0zEI|Bm)gQu&X%Q~loCIG__N1Olv literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_stat_notify_lucky_number.png b/app/src/main/res/drawable-xxhdpi/ic_stat_notify_lucky_number.png new file mode 100644 index 0000000000000000000000000000000000000000..9bab1373178182d45ec40c6ad1e84181190e5611 GIT binary patch literal 1088 zcmeAS@N?(olHy`uVBq!ia0y~yVDJE84mJh`hS0a0-5D5I*gahwLn>~)o$c!_?I>}4 z@y18ouOdIJk$1ev>x=~h>@P2G>1?=W%5=*_@It4X z!*Vqyt7{xDF1R>kOEIpU!I2T5ykJLXgOrMTjpJA0nCw?E8Gg(QG$*HDDt*A+T5#@w z>C%czoFCZgr^q)>Kd8Ugiu-WjNRP_Yi^tN=DV{PE7R&f@4 zbjs%CDrK&FY7LVQKA3G_^`2`D^LbS_*5y2ZR1a|0o|rH2XD!Qn))m^_r8^qxc>=`Q zJ}eSxi0Z8S`lr+Oq2Ljxt<9;i3yfL*zFNXy;#PA1#4bMp#az*VGH;F9s;@4yur6Rd zG<$WmNP>X9ZlK!RT`t-k?~*H*dKH@OQN5G$ytZ$y_$DRkBegRZGMKDM;G6u7HEAuDE?+c>D`@>9SM|7{^ES7tx&EzPu{TaMd*eN&WnY$Z zyqG5=aDHn0TcPZQEL+46&HlnDI>*g*|D*qPXCA-EUBqA_@~HEX0qcU90{W)UG#Hk0 zSa0#(5OG%ETY4kY1(vI4rfS>?Jh+%iL~1d^70(M3a}^V|2^V}xl%3D<>#7%{#;HHw z#ZSd31%|5_NEe8N?b5sWo$=KngCfbRi|xWvF8u2)n0!{C_t=UFxjS`}RPQfVdaEq` z?SP2S^h3wLYzip1E)MeD6twy0WXst`=WC^oEs<4w|BC6!l@wFGErEyUEO|QBU(%;) z)m=^|-Vj6e-tL!ogr6-b|7g6d`n7G}dyPjz6JkrsDz~&RFyQU$Jf(ckeU80q#LC=n zPv+VtzbZNCbLZhJCX=IAvd@KBY&~=H&XOpJT^R;~0drnnUeMVPX2KL@EO=p|o5OK6 zCabF)FOIl4%$8zYJDWpep + + diff --git a/app/src/main/res/layout/fragment_lucky_number.xml b/app/src/main/res/layout/fragment_lucky_number.xml new file mode 100644 index 000000000..fb05c7e49 --- /dev/null +++ b/app/src/main/res/layout/fragment_lucky_number.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 3c4a4ebba..a379c39de 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -184,6 +184,15 @@ Brak zadań domowych + + Szczęśliwy numerek + Dzisiejszym szczęśliwym numerkiem jest + Brak informacji o szczęśliwym numerku + + + Nowe szczęśliwe numerki + Szczęśliwy numerek na dzisiaj + Dziś szczęśliwym numerkiem jest: %d Dodaj konto diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index be6cec495..2013deac7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -167,6 +167,15 @@ No info about homework + + Lucky number + Today\'s lucky number is + No info about the lucky number + + + New lucky numbers + Lucky number for today + Today\'s lucky number is: %d Add account diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/remote/LuckyNumberRemoteTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/remote/LuckyNumberRemoteTest.kt new file mode 100644 index 000000000..b4f0b0cec --- /dev/null +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/remote/LuckyNumberRemoteTest.kt @@ -0,0 +1,44 @@ +package io.github.wulkanowy.data.repositories.remote + +import io.github.wulkanowy.api.Api +import io.github.wulkanowy.data.db.entities.Semester +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.impl.annotations.SpyK +import io.reactivex.Maybe +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.threeten.bp.LocalDate + +class LuckyNumberRemoteTest { + + @SpyK + private var mockApi = Api() + + @MockK + private lateinit var semesterMock: Semester + + @Before + fun initApi() { + MockKAnnotations.init(this) + } + + @Test + fun getLuckyNumberTest() { + every { mockApi.getLuckyNumber() } returns Maybe.just(14) + + every { mockApi.diaryId } returns 1 + every { semesterMock.studentId } returns 1 + every { semesterMock.diaryId } returns 1 + + val luckyNumber = LuckyNumberRemote(mockApi) + .getLuckyNumber(semesterMock) + .blockingGet() + + assertEquals(14, luckyNumber.luckyNumber) + assertEquals(LocalDate.now(), luckyNumber.date) + assertEquals(semesterMock.studentId, luckyNumber.studentId) + } +} From 7f162441e2864cacb7021038a065f482062fb71f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Fri, 25 Jan 2019 21:42:53 +0100 Subject: [PATCH 018/121] Version 0.6.6 --- .travis.yml | 6 +++--- app/build.gradle | 6 +++--- app/src/main/play/release-notes/pl-PL/default.txt | 9 +++++---- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index e2f4c9511..de271c930 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,9 +11,9 @@ cache: - $HOME/.gradle/caches/ - $HOME/.gradle/wrapper/ -branches: - only: - - master +#branches: +# only: +# - master android: licenses: diff --git a/app/build.gradle b/app/build.gradle index ef33e2343..7d87553a4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,8 +16,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 15 targetSdkVersion 28 - versionCode 24 - versionName "0.6.5" + versionCode 25 + versionName "0.6.6" multiDexEnabled true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true @@ -73,7 +73,7 @@ play { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" - implementation('com.github.wulkanowy:api:0fe0fbc69d') { exclude module: "threetenbp" } + implementation('io.github.wulkanowy:api:0.6.6') { exclude module: "threetenbp" } implementation "androidx.legacy:legacy-support-v4:1.0.0" implementation "androidx.appcompat:appcompat:1.0.2" diff --git a/app/src/main/play/release-notes/pl-PL/default.txt b/app/src/main/play/release-notes/pl-PL/default.txt index 70c0352ad..c8a56ad42 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,6 +1,7 @@ -Wersja 0.6.5 +Wersja 0.6.6 -- zmieniono ikonę zmiany semestru w ocenach -- naprawiono wyświetlanie szczegółowych informacji o błędach +- poprawiono problemy ze stabilnością w widoku ocen i wiadomości +- naprawiono wyświetlanie powiadomień po ich ponownym włączeniu po długim czasie +- ograniczono ilość zbędnych informacji na widżecie planu lekcji -Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases/tag/0.6.5 +Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases/tag/0.6.6 From 2f87779647812675814e56bdcac314faed544877 Mon Sep 17 00:00:00 2001 From: Kacper Ziubryniewicz Date: Tue, 5 Feb 2019 18:18:49 +0100 Subject: [PATCH 019/121] Add Firebase Analytics for the loaded lucky number (#233) --- .../wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt index 50de42e57..e03071b7e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt @@ -45,7 +45,7 @@ class LuckyNumberPresenter @Inject constructor( showContent(true) showEmpty(false) } - analytics.logEvent("load_lucky_number", mapOf("force_refresh" to forceRefresh)) + analytics.logEvent("load_lucky_number", mapOf("lucky_number" to it.luckyNumber, "force_refresh" to forceRefresh)) }, { view?.run { showEmpty(isViewEmpty()) } errorHandler.dispatch(it) From 1b1f2ae3bb06bf30bd32a44abb26f89e44940a94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Mon, 11 Feb 2019 02:04:24 +0100 Subject: [PATCH 020/121] Split login form for two fragments (#230) --- .idea/codeStyles/Project.xml | 1 - app/build.gradle | 18 ++- .../io/github/wulkanowy/data/ApiHelper.kt | 2 +- .../wulkanowy/data/db/entities/Student.kt | 3 +- .../data/repositories/StudentRepository.kt | 10 +- .../data/repositories/remote/GradeRemote.kt | 4 +- .../data/repositories/remote/StudentRemote.kt | 16 +-- .../java/io/github/wulkanowy/di/AppModule.kt | 7 ++ .../ui/modules/about/AboutPresenter.kt | 4 +- .../modules/attendance/AttendancePresenter.kt | 2 +- .../summary/AttendanceSummaryPresenter.kt | 2 +- .../ui/modules/exam/ExamPresenter.kt | 2 +- .../ui/modules/grade/GradePresenter.kt | 2 +- .../grade/details/GradeDetailsPresenter.kt | 2 +- .../grade/summary/GradeSummaryPresenter.kt | 2 +- .../ui/modules/homework/HomeworkPresenter.kt | 2 +- .../ui/modules/login/LoginActivity.kt | 38 ++++--- .../wulkanowy/ui/modules/login/LoginModule.kt | 13 ++- .../ui/modules/login/LoginPresenter.kt | 44 ++++++-- .../wulkanowy/ui/modules/login/LoginView.kt | 9 +- .../modules/login/form/LoginFormFragment.kt | 92 +++++---------- .../modules/login/form/LoginFormPresenter.kt | 45 ++------ .../ui/modules/login/form/LoginFormView.kt | 19 +--- .../LoginStudentSelectFragment.kt} | 33 ++++-- .../LoginStudentSelectItem.kt} | 6 +- .../LoginStudentSelectPresenter.kt} | 37 +++--- .../LoginStudentSelectView.kt} | 6 +- .../login/symbol/LoginSymbolFragment.kt | 106 ++++++++++++++++++ .../login/symbol/LoginSymbolPresenter.kt | 82 ++++++++++++++ .../modules/login/symbol/LoginSymbolView.kt | 25 +++++ .../luckynumber/LuckyNumberPresenter.kt | 3 +- .../ui/modules/main/MainPresenter.kt | 4 +- .../preview/MessagePreviewPresenter.kt | 4 +- .../message/tab/MessageTabPresenter.kt | 2 +- .../ui/modules/note/NotePresenter.kt | 2 +- .../ui/modules/settings/SettingsPresenter.kt | 2 +- .../modules/timetable/TimetablePresenter.kt | 2 +- .../timetable/TimetableWidgetProvider.kt | 2 +- .../utils/FirebaseAnalyticsHelper.kt | 12 +- .../main/res/layout/fragment_login_form.xml | 89 +++++---------- ....xml => fragment_login_student_select.xml} | 9 +- .../main/res/layout/fragment_login_symbol.xml | 88 +++++++++++++++ .../repositories/remote/StudentRemoteTest.kt | 6 +- .../ui/modules/login/LoginPresenterTest.kt | 22 +--- .../login/form/LoginFormPresenterTest.kt | 41 ++----- .../LoginStudentSelectPresenterTest.kt} | 48 +++----- build.gradle | 5 +- 47 files changed, 597 insertions(+), 378 deletions(-) rename app/src/main/java/io/github/wulkanowy/ui/modules/login/{options/LoginOptionsFragment.kt => studentselect/LoginStudentSelectFragment.kt} (68%) rename app/src/main/java/io/github/wulkanowy/ui/modules/login/{options/LoginOptionsItem.kt => studentselect/LoginStudentSelectItem.kt} (87%) rename app/src/main/java/io/github/wulkanowy/ui/modules/login/{options/LoginOptionsPresenter.kt => studentselect/LoginStudentSelectPresenter.kt} (68%) rename app/src/main/java/io/github/wulkanowy/ui/modules/login/{options/LoginOptionsView.kt => studentselect/LoginStudentSelectView.kt} (56%) create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt rename app/src/main/res/layout/{fragment_login_options.xml => fragment_login_student_select.xml} (83%) create mode 100644 app/src/main/res/layout/fragment_login_symbol.xml rename app/src/test/java/io/github/wulkanowy/ui/modules/login/{options/LoginOptionsPresenterTest.kt => studentselect/LoginStudentSelectPresenterTest.kt} (57%) diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index eb4efba71..0c64df610 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -19,7 +19,6 @@