diff --git a/.travis.yml b/.travis.yml index a430c90e..9034de61 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 00000000..67ac4c2b --- /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 f5bb1b2a..e72175bb 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 30c957b4..974d5dab 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 00000000..05736e42 --- /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 00000000..6144b742 --- /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 00000000..30ddc3bb --- /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 00000000..737dc925 --- /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 00000000..a04ea740 --- /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 00000000..36ff57f9 --- /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 cf09728a..5ad8ce0f 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 00000000..9db159aa --- /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 00000000..d535e825 --- /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 00000000..50de42e5 --- /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 00000000..eece5b70 --- /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 9d1c44c0..1d98ce63 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 226fccd6..5bbf6247 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 6becc8ad..5fea241c 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 08ff82ad..333edc5d 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 00000000..00538168 --- /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 00000000..b91c4ae6 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_stat_notify_lucky_number.png differ 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 00000000..bfced4eb Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_stat_notify_lucky_number.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_stat_notify_lucky_number.png b/app/src/main/res/drawable-xhdpi/ic_stat_notify_lucky_number.png new file mode 100644 index 00000000..49e502ac Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_stat_notify_lucky_number.png differ 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 00000000..9bab1373 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_stat_notify_lucky_number.png differ diff --git a/app/src/main/res/drawable/ic_more_lucky_number_24dp.xml b/app/src/main/res/drawable/ic_more_lucky_number_24dp.xml new file mode 100644 index 00000000..f2e6152d --- /dev/null +++ b/app/src/main/res/drawable/ic_more_lucky_number_24dp.xml @@ -0,0 +1,9 @@ + + + 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 00000000..fb05c7e4 --- /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 3c4a4ebb..a379c39d 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 be6cec49..2013deac 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 00000000..b4f0b0ce --- /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) + } +}