Refactor grade module (#156)

This commit is contained in:
Rafał Borcz 2018-10-03 21:28:23 +02:00 committed by Mikołaj Pich
parent 357b2350cb
commit f2b7c0e781
119 changed files with 2629 additions and 1384 deletions

View File

@ -1,15 +1,15 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'// sync warning probably caused by bug https://issuetracker.google.com/issues/74537216, fix in AS 3.2
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'io.fabric'
apply plugin: 'com.github.triplet.play'
apply from: 'jacoco.gradle'
apply from: 'sonarqube.gradle'
apply plugin: 'com.github.triplet.play'
android {
compileSdkVersion 28
buildToolsVersion '28.0.2'
buildToolsVersion '28.0.3'
playAccountConfigs {
defaultAccountConfig {
@ -75,13 +75,15 @@ dependencies {
implementation('com.github.wulkanowy:api:07201a4') {
exclude module: "threetenbp"
}
implementation "com.android.support:support-v4:$supportVersion"
implementation "com.android.support:appcompat-v7:$supportVersion"
implementation "com.android.support:design:$supportVersion"
implementation "com.android.support:cardview-v7:$supportVersion"
implementation "com.android.support:preference-v14:$supportVersion"
implementation 'com.android.support:multidex:1.0.3'
implementation "com.google.android.gms:play-services-oss-licenses:16.0.0"
implementation "com.google.android.gms:play-services-oss-licenses:16.0.1"
implementation "com.firebase:firebase-jobdispatcher:0.8.5"
implementation "com.google.dagger:dagger-android-support:2.17"
@ -92,7 +94,7 @@ dependencies {
implementation "android.arch.persistence.room:rxjava2:1.1.1"
kapt "android.arch.persistence.room:compiler:1.1.1"
implementation "eu.davidea:flexible-adapter:5.0.5"
implementation "eu.davidea:flexible-adapter:5.0.6"
implementation "eu.davidea:flexible-adapter-ui:1.0.0-b5"
implementation "com.aurelhubert:ahbottomnavigation:2.2.0"
implementation 'com.ncapdevi:frag-nav:3.0.0-RC3'
@ -101,8 +103,6 @@ dependencies {
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
implementation "io.reactivex.rxjava2:rxjava:2.2.1"
implementation "org.apache.commons:commons-lang3:3.8"
implementation "org.apache.commons:commons-collections4:4.2"
implementation "com.jakewharton.threetenabp:threetenabp:1.1.0"
implementation "com.jakewharton.timber:timber:4.7.1"

View File

@ -34,13 +34,13 @@ class AttendanceLocalTest {
@Test
fun saveAndReadTest() {
attendanceLocal.saveAttendance(listOf(
Attendance(studentId = "1", diaryId = "2", date = LocalDate.of(2018, 9, 10)),
Attendance(studentId = "1", diaryId = "2", date = LocalDate.of(2018, 9, 14)),
Attendance(studentId = "1", diaryId = "2", date = LocalDate.of(2018, 9, 17)) // in next week
Attendance(0, "1", "2", LocalDate.of(2018, 9, 10), 0, "", ""),
Attendance(0, "1", "2", LocalDate.of(2018, 9, 14), 0, "", ""),
Attendance(0, "1", "2", LocalDate.of(2018, 9, 17), 0, "", "")
))
val attendance = attendanceLocal
.getAttendance(Semester(studentId = "1", diaryId = "2", semesterId = "3"),
.getAttendance(Semester(1, "1", "2", "", "3", 1),
LocalDate.of(2018, 9, 10),
LocalDate.of(2018, 9, 14)
)

View File

@ -34,13 +34,13 @@ class ExamLocalTest {
@Test
fun saveAndReadTest() {
examLocal.saveExams(listOf(
Exam(studentId = "1", diaryId = "2", date = LocalDate.of(2018, 9, 10)),
Exam(studentId = "1", diaryId = "2", date = LocalDate.of(2018, 9, 14)),
Exam(studentId = "1", diaryId = "2", date = LocalDate.of(2018, 9, 17)) // in next week
Exam(0, "1", "2", LocalDate.of(2018, 9, 10), LocalDate.now(), "", "", "", "", "", ""),
Exam(0, "1", "2", LocalDate.of(2018, 9, 14), LocalDate.now(), "", "", "", "", "", ""),
Exam(0, "1", "2", LocalDate.of(2018, 9, 17), LocalDate.now(), "", "", "", "", "", "")
))
val exams = examLocal
.getExams(Semester(studentId = "1", diaryId = "2", semesterId = "3"),
.getExams(Semester(1, "1", "2", "", "3", 1),
LocalDate.of(2018, 9, 10),
LocalDate.of(2018, 9, 14)
)

View File

@ -16,7 +16,7 @@ import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class)
class SessionLocalTest {
private lateinit var studentLocal: SessionLocal
private lateinit var sessionLocal: SessionLocal
private lateinit var testDb: AppDatabase
@ -28,7 +28,7 @@ class SessionLocalTest {
testDb = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java)
.build()
sharedHelper = SharedPrefHelper(context.getSharedPreferences("TEST", Context.MODE_PRIVATE))
studentLocal = SessionLocal(testDb.studentDao(), testDb.semesterDao(), sharedHelper, context)
sessionLocal = SessionLocal(testDb.studentDao(), testDb.semesterDao(), sharedHelper, context)
}
@After
@ -38,12 +38,12 @@ class SessionLocalTest {
@Test
fun saveAndReadTest() {
studentLocal.saveStudent(Student(email = "test", password = "test123", schoolId = "23")).blockingAwait()
sessionLocal.saveStudent(Student(email = "test", password = "test123", schoolId = "23")).blockingAwait()
assert(sharedHelper.getLong(SessionLocal.LAST_USER_KEY, 0) == 1L)
assert(studentLocal.isSessionSaved)
assert(sessionLocal.isSessionSaved)
val student = studentLocal.getLastStudent().blockingGet()
val student = sessionLocal.getLastStudent().blockingGet()
assertEquals("23", student.schoolId)
}
}

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="io.github.wulkanowy"
android:installLocation="internalOnly">
@ -15,13 +14,11 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/WulkanowyTheme"
android:usesCleartextTraffic="true"
tools:targetApi="m">
android:usesCleartextTraffic="true">
<activity
android:name=".ui.splash.SplashActivity"
android:configChanges="orientation|screenSize"
android:noHistory="true"
android:theme="@style/WulkanowyTheme.SplashTheme">
android:screenOrientation="portrait"
android:theme="@style/WulkanowyTheme.SplashScreen">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@ -32,39 +29,13 @@
android:name=".ui.login.LoginActivity"
android:configChanges="orientation|screenSize"
android:label="@string/login_title"
android:theme="@style/WulkanowyTheme.DarkActionBar"
android:windowSoftInputMode="adjustResize" />
<activity
android:name=".ui.main.MainActivity"
android:configChanges="orientation|screenSize"
android:label="@string/main_title"
android:launchMode="singleTop" />
<activity
android:name="com.google.android.gms.oss.licenses.OssLicensesMenuActivity"
android:theme="@style/WulkanowyTheme.DarkActionBar" />
<activity
android:name="com.google.android.gms.oss.licenses.OssLicensesActivity"
android:theme="@style/WulkanowyTheme.DarkActionBar" />
<service
android:name=".services.jobs.SyncJob"
android:exported="false">
<intent-filter>
<action android:name="com.firebase.jobdispatcher.ACTION_EXECUTE" />
</intent-filter>
</service>
<service
android:name=".services.widgets.TimetableWidgetServices"
android:permission="android.permission.BIND_REMOTEVIEWS" />
<receiver android:name=".ui.widgets.TimetableWidgetProvider">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/provider_widget_timetable" />
</receiver>
android:launchMode="singleTop"
android:theme="@style/WulkanowyTheme.NoActionBar" />
<meta-data
android:name="io.fabric.ApiKey"

View File

@ -9,9 +9,12 @@ import com.jakewharton.threetenabp.AndroidThreeTen
import dagger.android.AndroidInjector
import dagger.android.support.DaggerApplication
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.utils.Log
import io.fabric.sdk.android.Fabric
import io.github.wulkanowy.BuildConfig.DEBUG
import io.github.wulkanowy.di.DaggerAppComponent
import io.github.wulkanowy.utils.LoggerUtils
import io.github.wulkanowy.utils.CrashlyticsTree
import io.github.wulkanowy.utils.DebugLogTree
import timber.log.Timber
class WulkanowyApp : DaggerApplication() {
@ -24,31 +27,27 @@ class WulkanowyApp : DaggerApplication() {
override fun onCreate() {
super.onCreate()
AndroidThreeTen.init(this)
if (BuildConfig.DEBUG) {
enableDebugLog()
}
initializeFabric()
if (DEBUG) enableDebugLog()
}
private fun enableDebugLog() {
FlexibleAdapter.enableLogs(eu.davidea.flexibleadapter.utils.Log.Level.DEBUG)
Timber.plant(LoggerUtils.DebugLogTree())
Timber.plant(DebugLogTree)
FlexibleAdapter.enableLogs(Log.Level.DEBUG)
}
private fun initializeFabric() {
Fabric.with(Fabric.Builder(this)
.kits(
Crashlytics.Builder()
.core(CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build())
.build(),
Answers()
)
.kits(Crashlytics.Builder()
.core(CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build())
.build(),
Answers())
.debuggable(BuildConfig.DEBUG)
.build())
Timber.plant(LoggerUtils.CrashlyticsTree())
Timber.plant(CrashlyticsTree)
}
override fun applicationInjector(): AndroidInjector<out DaggerApplication> =
DaggerAppComponent.builder().create(this)
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.builder().create(this)
}
}

View File

@ -1,15 +1,14 @@
package io.github.wulkanowy.data
import android.arch.persistence.room.Room
import android.content.Context
import android.content.SharedPreferences
import android.support.v7.preference.PreferenceManager
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.strategy.SocketInternetObservingStrategy
import dagger.Module
import dagger.Provides
import io.github.wulkanowy.api.Api
import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.utils.DATABASE_NAME
import javax.inject.Singleton
@Module
@ -18,7 +17,10 @@ internal class RepositoryModule {
@Singleton
@Provides
fun provideInternetObservingSettings(): InternetObservingSettings {
return InternetObservingSettings.create()
return InternetObservingSettings
.strategy(SocketInternetObservingStrategy())
.host("www.google.com")
.build()
}
@Singleton
@ -27,10 +29,7 @@ internal class RepositoryModule {
@Singleton
@Provides
fun provideDatabase(context: Context): AppDatabase {
return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME)
.build()
}
fun provideDatabase(context: Context) = AppDatabase.newInstance(context)
@Provides
fun provideErrorHandler(context: Context) = ErrorHandler(context.resources)
@ -49,6 +48,14 @@ internal class RepositoryModule {
@Provides
fun provideSemesterDao(database: AppDatabase) = database.semesterDao()
@Singleton
@Provides
fun provideGradeDao(database: AppDatabase) = database.gradeDao()
@Singleton
@Provides
fun provideGradeSummaryDao(database: AppDatabase) = database.gradeSummaryDao()
@Singleton
@Provides
fun provideExamDao(database: AppDatabase) = database.examsDao()

View File

@ -1,16 +1,12 @@
package io.github.wulkanowy.data.db
import android.arch.persistence.room.Database
import android.arch.persistence.room.Room
import android.arch.persistence.room.RoomDatabase
import android.arch.persistence.room.TypeConverters
import io.github.wulkanowy.data.db.dao.AttendanceDao
import io.github.wulkanowy.data.db.dao.ExamDao
import io.github.wulkanowy.data.db.dao.SemesterDao
import io.github.wulkanowy.data.db.dao.StudentDao
import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import android.content.Context
import io.github.wulkanowy.data.db.dao.*
import io.github.wulkanowy.data.db.entities.*
import javax.inject.Singleton
@Singleton
@ -19,7 +15,9 @@ import javax.inject.Singleton
Student::class,
Semester::class,
Exam::class,
Attendance::class
Attendance::class,
Grade::class,
GradeSummary::class
],
version = 1,
exportSchema = false
@ -27,6 +25,13 @@ import javax.inject.Singleton
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
companion object {
fun newInstance(context: Context): AppDatabase {
return Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database")
.build()
}
}
abstract fun studentDao(): StudentDao
abstract fun semesterDao(): SemesterDao
@ -34,4 +39,8 @@ abstract class AppDatabase : RoomDatabase() {
abstract fun examsDao(): ExamDao
abstract fun attendanceDao(): AttendanceDao
abstract fun gradeDao(): GradeDao
abstract fun gradeSummaryDao(): GradeSummaryDao
}

View File

@ -0,0 +1,21 @@
package io.github.wulkanowy.data.db.dao
import android.arch.persistence.room.*
import io.github.wulkanowy.data.db.entities.Grade
import io.reactivex.Maybe
@Dao
interface GradeDao {
@Insert
fun insertAll(grades: List<Grade>)
@Update
fun update(grade: Grade)
@Delete
fun deleteAll(grades: List<Grade>)
@Query("SELECT * FROM Grades WHERE semester_id = :semesterId AND student_id = :studentId")
fun getGrades(semesterId: String, studentId: String): Maybe<List<Grade>>
}

View File

@ -0,0 +1,18 @@
package io.github.wulkanowy.data.db.dao
import android.arch.persistence.room.Dao
import android.arch.persistence.room.Insert
import android.arch.persistence.room.OnConflictStrategy.REPLACE
import android.arch.persistence.room.Query
import io.github.wulkanowy.data.db.entities.GradeSummary
import io.reactivex.Maybe
@Dao
interface GradeSummaryDao {
@Insert(onConflict = REPLACE)
fun insertAll(gradesSummary: List<GradeSummary>)
@Query("SELECT * FROM grades_summary WHERE student_id = :studentId AND semester_id = :semesterId")
fun getGradesSummary(semesterId: String, studentId: String): Maybe<List<GradeSummary>>
}

View File

@ -13,18 +13,18 @@ data class Attendance(
var id: Long = 0,
@ColumnInfo(name = "student_id")
var studentId: String = "",
var studentId: String,
@ColumnInfo(name = "diary_id")
var diaryId: String = "",
var diaryId: String,
var date: LocalDate,
var number: Int = 0,
var number: Int,
var subject: String = "",
var subject: String,
var name: String = "",
var name: String,
var presence: Boolean = false,

View File

@ -13,26 +13,26 @@ data class Exam(
var id: Long = 0,
@ColumnInfo(name = "student_id")
var studentId: String = "",
var studentId: String,
@ColumnInfo(name = "diary_id")
var diaryId: String = "",
var diaryId: String,
var date: LocalDate,
@ColumnInfo(name = "entry_date")
var entryDate: LocalDate = LocalDate.now(),
var subject: String = "",
var subject: String,
var group: String = "",
var group: String,
var type: String = "",
var type: String,
var description: String = "",
var description: String,
var teacher: String = "",
var teacher: String,
@ColumnInfo(name = "teacher_symbol")
var teacherSymbol: String = ""
var teacherSymbol: String
) : Serializable

View File

@ -0,0 +1,49 @@
package io.github.wulkanowy.data.db.entities
import android.arch.persistence.room.ColumnInfo
import android.arch.persistence.room.Entity
import android.arch.persistence.room.PrimaryKey
import org.threeten.bp.LocalDate
import java.io.Serializable
@Entity(tableName = "Grades")
data class Grade(
@ColumnInfo(name = "semester_id")
var semesterId: String,
@ColumnInfo(name = "student_id")
var studentId: String,
var subject: String,
var entry: String,
var value: Int,
var modifier: Double,
var comment: String,
var color: String,
@ColumnInfo(name = "grade_symbol")
var gradeSymbol: String,
var description: String,
var weight: String,
var weightValue: Int,
var date: LocalDate,
var teacher: String
) : Serializable {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
@ColumnInfo(name = "is_new")
var isNew: Boolean = false
}

View File

@ -0,0 +1,26 @@
package io.github.wulkanowy.data.db.entities
import android.arch.persistence.room.ColumnInfo
import android.arch.persistence.room.Entity
import android.arch.persistence.room.Index
import android.arch.persistence.room.PrimaryKey
@Entity(tableName = "Grades_Summary",
indices = [Index(value = ["semester_id", "student_id", "subject"], unique = true)])
data class GradeSummary(
@PrimaryKey(autoGenerate = true)
var id: Long = 0,
@ColumnInfo(name = "semester_id")
var semesterId: String,
@ColumnInfo(name = "student_id")
var studentId: String,
var subject: String,
var predictedGrade: String,
var finalGrade: String
)

View File

@ -19,13 +19,13 @@ data class Semester(
var diaryId: String,
@ColumnInfo(name = "diary_name")
var diaryName: String = "",
var diaryName: String,
@ColumnInfo(name = "semester_id")
var semesterId: String,
@ColumnInfo(name = "semester_name")
var semesterName: Int = 0,
var semesterName: Int,
@ColumnInfo(name = "is_current")
var current: Boolean = false

View File

@ -6,7 +6,7 @@ import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.repositories.local.AttendanceLocal
import io.github.wulkanowy.data.repositories.remote.AttendanceRemote
import io.github.wulkanowy.utils.extension.getWeekFirstDayAlwaysCurrent
import io.github.wulkanowy.utils.weekFirstDayAlwaysCurrent
import io.reactivex.Single
import org.threeten.bp.DayOfWeek
import org.threeten.bp.LocalDate
@ -21,7 +21,7 @@ class AttendanceRepository @Inject constructor(
) {
fun getAttendance(semester: Semester, startDate: LocalDate, endDate: LocalDate, forceRefresh: Boolean = false): Single<List<Attendance>> {
val start = startDate.getWeekFirstDayAlwaysCurrent()
val start = startDate.weekFirstDayAlwaysCurrent
val end = endDate.with(TemporalAdjusters.nextOrSame(DayOfWeek.FRIDAY))
return local.getAttendance(semester, start, end).filter { !forceRefresh }

View File

@ -6,8 +6,7 @@ import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.repositories.local.ExamLocal
import io.github.wulkanowy.data.repositories.remote.ExamRemote
import io.github.wulkanowy.utils.extension.getWeekFirstDayAlwaysCurrent
import io.github.wulkanowy.utils.extension.toDate
import io.github.wulkanowy.utils.weekFirstDayAlwaysCurrent
import io.reactivex.Single
import org.threeten.bp.DayOfWeek
import org.threeten.bp.LocalDate
@ -24,7 +23,7 @@ class ExamRepository @Inject constructor(
) {
fun getExams(semester: Semester, startDate: LocalDate, endDate: LocalDate, forceRefresh: Boolean = false): Single<List<Exam>> {
val start = startDate.getWeekFirstDayAlwaysCurrent()
val start = startDate.weekFirstDayAlwaysCurrent
val end = endDate.with(TemporalAdjusters.nextOrSame(DayOfWeek.FRIDAY))
return local.getExams(semester, start, end).filter { !forceRefresh }

View File

@ -0,0 +1,42 @@
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.Grade
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.repositories.local.GradeLocal
import io.github.wulkanowy.data.repositories.remote.GradeRemote
import io.reactivex.Completable
import io.reactivex.Single
import java.net.UnknownHostException
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class GradeRepository @Inject constructor(
private val settings: InternetObservingSettings,
private val local: GradeLocal,
private val remote: GradeRemote
) {
fun getGrades(semester: Semester, forceRefresh: Boolean = false): Single<List<Grade>> {
return local.getGrades(semester).filter { !forceRefresh }
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
.flatMap {
if (it) remote.getGrades(semester)
else Single.error(UnknownHostException())
}.flatMap { newGrades ->
local.getGrades(semester).toSingle(emptyList())
.doOnSuccess { oldGrades ->
local.deleteGrades(oldGrades - newGrades)
local.saveGrades((newGrades - oldGrades)
.onEach { if (oldGrades.isNotEmpty()) it.isNew = true })
}
}.flatMap { local.getGrades(semester).toSingle(emptyList()) })
}
fun updateGrade(grade: Grade): Completable {
return local.updateGrade(grade)
}
}

View File

@ -0,0 +1,30 @@
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.GradeSummary
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.repositories.local.GradeSummaryLocal
import io.github.wulkanowy.data.repositories.remote.GradeSummaryRemote
import io.reactivex.Single
import java.net.UnknownHostException
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class GradeSummaryRepository @Inject constructor(
private val settings: InternetObservingSettings,
private val local: GradeSummaryLocal,
private val remote: GradeSummaryRemote
) {
fun getGradesSummary(semester: Semester, forceRefresh: Boolean = false): Single<List<GradeSummary>> {
return local.getGradesSummary(semester).filter { !forceRefresh }
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
.flatMap {
if (it) remote.getGradeSummary(semester)
else Single.error(UnknownHostException())
}
).doOnSuccess { local.saveGradesSummary(it) }
}
}

View File

@ -42,9 +42,8 @@ class SessionRepository @Inject constructor(
}
fun saveStudent(student: Student): Completable {
return remote.getSemesters(student).flatMapCompletable {
local.saveSemesters(it)
}.concatWith(local.saveStudent(student))
return remote.getSemesters(student).flatMapCompletable { local.saveSemesters(it) }
.concatWith(local.saveStudent(student))
}
fun clearCache() {

View File

@ -3,11 +3,8 @@ package io.github.wulkanowy.data.repositories.local
import io.github.wulkanowy.data.db.dao.ExamDao
import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.utils.extension.toDate
import io.reactivex.Maybe
import org.threeten.bp.DayOfWeek
import org.threeten.bp.LocalDate
import org.threeten.bp.temporal.TemporalAdjusters
import javax.inject.Inject
class ExamLocal @Inject constructor(private val examDb: ExamDao) {

View File

@ -0,0 +1,29 @@
package io.github.wulkanowy.data.repositories.local
import io.github.wulkanowy.data.db.dao.GradeDao
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.Semester
import io.reactivex.Completable
import io.reactivex.Maybe
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class GradeLocal @Inject constructor(private val gradeDb: GradeDao) {
fun getGrades(semester: Semester): Maybe<List<Grade>> {
return gradeDb.getGrades(semester.semesterId, semester.studentId).filter { !it.isEmpty() }
}
fun saveGrades(grades: List<Grade>) {
gradeDb.insertAll(grades)
}
fun updateGrade(grade: Grade): Completable {
return Completable.fromCallable { gradeDb.update(grade) }
}
fun deleteGrades(grades: List<Grade>) {
gradeDb.deleteAll(grades)
}
}

View File

@ -0,0 +1,21 @@
package io.github.wulkanowy.data.repositories.local
import io.github.wulkanowy.data.db.dao.GradeSummaryDao
import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Semester
import io.reactivex.Maybe
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class GradeSummaryLocal @Inject constructor(private val gradeSummaryDb: GradeSummaryDao) {
fun getGradesSummary(semester: Semester): Maybe<List<GradeSummary>> {
return gradeSummaryDb.getGradesSummary(semester.semesterId, semester.studentId)
.filter { !it.isEmpty() }
}
fun saveGradesSummary(gradesSummary: List<GradeSummary>) {
gradeSummaryDb.insertAll(gradesSummary)
}
}

View File

@ -3,7 +3,7 @@ package io.github.wulkanowy.data.repositories.remote
import io.github.wulkanowy.api.Api
import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.utils.extension.toLocalDate
import io.github.wulkanowy.utils.toLocalDate
import io.reactivex.Single
import org.threeten.bp.LocalDate
import javax.inject.Inject

View File

@ -3,7 +3,7 @@ package io.github.wulkanowy.data.repositories.remote
import io.github.wulkanowy.api.Api
import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.utils.extension.toLocalDate
import io.github.wulkanowy.utils.toLocalDate
import io.reactivex.Single
import org.threeten.bp.LocalDate
import javax.inject.Inject

View File

@ -0,0 +1,42 @@
package io.github.wulkanowy.data.repositories.remote
import io.github.wulkanowy.api.Api
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.utils.toLocalDate
import io.reactivex.Single
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class GradeRemote @Inject constructor(private val api: Api) {
fun getGrades(semester: Semester): Single<List<Grade>> {
return Single.just(api.run {
if (diaryId != semester.diaryId) {
diaryId = semester.diaryId
notifyDataChanged()
}
}).flatMap { api.getGrades(semester.semesterId.toInt()) }
.map { grades ->
grades.map {
Grade(
semesterId = semester.semesterId,
studentId = semester.studentId,
subject = it.subject,
entry = it.entry,
value = it.value,
modifier = it.modifier.toDouble(),
comment = it.comment,
color = it.color,
gradeSymbol = it.symbol,
description = it.description,
weight = it.weight,
weightValue = it.weightValue,
date = it.date.toLocalDate(),
teacher = it.teacher
)
}
}
}
}

View File

@ -0,0 +1,32 @@
package io.github.wulkanowy.data.repositories.remote
import io.github.wulkanowy.api.Api
import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Semester
import io.reactivex.Single
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class GradeSummaryRemote @Inject constructor(private val api: Api) {
fun getGradeSummary(semester: Semester): Single<List<GradeSummary>> {
return Single.just(api.run {
if (diaryId != semester.diaryId) {
diaryId = semester.diaryId
notifyDataChanged()
}
}).flatMap { api.getGradesSummary(semester.semesterId.toInt()) }
.map { gradesSummary ->
gradesSummary.map {
GradeSummary(
semesterId = semester.semesterId,
studentId = semester.studentId,
subject = it.name,
predictedGrade = it.predicted,
finalGrade = it.final
)
}
}
}
}

View File

@ -8,13 +8,16 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.WulkanowyApp
import io.github.wulkanowy.utils.schedulers.SchedulersManager
import io.github.wulkanowy.utils.schedulers.SchedulersProvider
import javax.inject.Singleton
@Module
internal class AppModule {
@Singleton
@Provides
fun provideContext(app: WulkanowyApp): Context = app
@Singleton
@Provides
fun provideSchedulers(): SchedulersManager = SchedulersProvider()

View File

@ -6,11 +6,10 @@ import android.support.design.widget.Snackbar.LENGTH_LONG
import android.support.v7.app.AppCompatDelegate
import android.view.View
import dagger.android.support.DaggerAppCompatActivity
import io.github.wulkanowy.R
abstract class BaseActivity : DaggerAppCompatActivity(), BaseView {
protected lateinit var messageView: View
protected lateinit var messageContainer: View
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -18,12 +17,7 @@ abstract class BaseActivity : DaggerAppCompatActivity(), BaseView {
}
override fun showMessage(text: String) {
Snackbar.make(messageView, text, LENGTH_LONG).show()
}
override fun showNoNetworkMessage() {
showMessage(getString(R.string.all_no_internet))
Snackbar.make(messageContainer, text, LENGTH_LONG).show()
}
override fun onDestroy() {

View File

@ -1,24 +1,10 @@
package io.github.wulkanowy.ui.base
import android.support.annotation.StringRes
import dagger.android.support.DaggerFragment
abstract class BaseFragment : DaggerFragment(), BaseView {
fun setTitle(title: String) {
activity?.title = title
}
override fun showMessage(text: String) {
(activity as BaseActivity?)?.showMessage(text)
}
fun showMessage(@StringRes stringId: Int) {
showMessage(getString(stringId))
}
override fun showNoNetworkMessage() {
(activity as BaseActivity?)?.showNoNetworkMessage()
}
}

View File

@ -3,28 +3,30 @@ package io.github.wulkanowy.ui.base
import android.support.v4.app.Fragment
import android.support.v4.app.FragmentManager
import android.support.v4.app.FragmentStatePagerAdapter
import android.view.ViewGroup
class BasePagerAdapter(fragmentManager: FragmentManager) : FragmentStatePagerAdapter(fragmentManager) {
private val fragmentList = mutableListOf<Fragment>()
val fragments = mutableMapOf<String?, Fragment>()
private val titleList = mutableListOf<String>()
val registeredFragments = mutableMapOf<Int, Fragment>()
fun addFragment(fragment: Fragment, title: String) {
fragmentList.add(fragment)
titleList.add(title)
}
override fun getItem(position: Int) = fragments.values.elementAt(position)
fun addFragments(vararg fragments: Fragment) {
fragmentList.addAll(fragments)
}
override fun getItem(position: Int): Fragment = fragmentList[position]
override fun getCount(): Int = fragmentList.size
override fun getCount() = fragments.size
override fun getPageTitle(position: Int): CharSequence? {
return if (!titleList.isEmpty() && titleList.size == fragmentList.size) titleList[position]
else null
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)
}
}

View File

@ -3,6 +3,4 @@ package io.github.wulkanowy.ui.base
interface BaseView {
fun showMessage(text: String)
fun showNoNetworkMessage()
}

View File

@ -8,10 +8,9 @@ import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.base.BasePagerAdapter
import io.github.wulkanowy.ui.login.form.LoginFormFragment
import io.github.wulkanowy.ui.login.options.LoginOptionsFragment
import io.github.wulkanowy.utils.extension.setOnSelectPageListener
import io.github.wulkanowy.utils.setOnSelectPageListener
import kotlinx.android.synthetic.main.activity_login.*
import javax.inject.Inject
import javax.inject.Named
class LoginActivity : BaseActivity(), LoginView, LoginSwitchListener {
@ -19,7 +18,6 @@ class LoginActivity : BaseActivity(), LoginView, LoginSwitchListener {
lateinit var presenter: LoginPresenter
@Inject
@field:Named("Login")
lateinit var loginAdapter: BasePagerAdapter
companion object {
@ -30,7 +28,7 @@ class LoginActivity : BaseActivity(), LoginView, LoginSwitchListener {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
presenter.attachView(this)
messageView = loginContainer
messageContainer = loginContainer
}
override fun onBackPressed() {
@ -38,7 +36,10 @@ class LoginActivity : BaseActivity(), LoginView, LoginSwitchListener {
}
override fun initAdapter() {
loginAdapter.addFragments(LoginFormFragment(), LoginOptionsFragment())
loginAdapter.fragments.putAll(mapOf(
"1" to LoginFormFragment.newInstance(),
"2" to LoginOptionsFragment.newInstance()
))
loginViewpager.run {
adapter = loginAdapter
setOnSelectPageListener { presenter.onPageSelected(it) }
@ -61,7 +62,7 @@ class LoginActivity : BaseActivity(), LoginView, LoginSwitchListener {
(loginAdapter.getItem(index) as LoginOptionsFragment).loadData()
}
override fun currentViewPosition(): Int = loginViewpager.currentItem
override fun currentViewPosition() = loginViewpager.currentItem
public override fun onDestroy() {
presenter.detachView()

View File

@ -9,8 +9,6 @@ import io.github.wulkanowy.di.scopes.PerFragment
import io.github.wulkanowy.ui.base.BasePagerAdapter
import io.github.wulkanowy.ui.login.form.LoginFormFragment
import io.github.wulkanowy.ui.login.options.LoginOptionsFragment
import io.github.wulkanowy.ui.login.options.LoginOptionsModule
import javax.inject.Named
@Module
internal abstract class LoginModule {
@ -19,8 +17,8 @@ internal abstract class LoginModule {
companion object {
@JvmStatic
@PerActivity
@Provides
@Named("Login")
fun provideLoginAdapter(activity: LoginActivity) = BasePagerAdapter(activity.supportFragmentManager)
@JvmStatic
@ -34,6 +32,6 @@ internal abstract class LoginModule {
abstract fun bindLoginFormFragment(): LoginFormFragment
@PerFragment
@ContributesAndroidInjector(modules = [LoginOptionsModule::class])
@ContributesAndroidInjector()
abstract fun bindLoginOptionsFragment(): LoginOptionsFragment
}

View File

@ -11,8 +11,8 @@ import android.widget.ArrayAdapter
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.login.LoginSwitchListener
import io.github.wulkanowy.utils.extension.hideSoftInput
import io.github.wulkanowy.utils.extension.showSoftInput
import io.github.wulkanowy.utils.hideSoftInput
import io.github.wulkanowy.utils.showSoftInput
import kotlinx.android.synthetic.main.fragment_login_form.*
import javax.inject.Inject
@ -21,6 +21,10 @@ class LoginFormFragment : BaseFragment(), LoginFormView {
@Inject
lateinit var presenter: LoginFormPresenter
companion object {
fun newInstance() = LoginFormFragment()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_login_form, container, false)
}
@ -132,8 +136,8 @@ class LoginFormFragment : BaseFragment(), LoginFormView {
loginFormProgressContainer.visibility = if (show) VISIBLE else GONE
}
override fun onDestroy() {
super.onDestroy()
override fun onDestroyView() {
super.onDestroyView()
presenter.detachView()
}
}

View File

@ -3,7 +3,6 @@ package io.github.wulkanowy.ui.login.form
import io.github.wulkanowy.data.repositories.SessionRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.login.LoginErrorHandler
import io.github.wulkanowy.utils.DEFAULT_SYMBOL
import io.github.wulkanowy.utils.schedulers.SchedulersManager
import javax.inject.Inject
@ -83,6 +82,6 @@ class LoginFormPresenter @Inject constructor(
}
private fun normalizeSymbol(symbol: String): String {
return if (symbol.isEmpty()) DEFAULT_SYMBOL else symbol
return if (symbol.isEmpty()) "Default" else symbol
}
}

View File

@ -13,7 +13,7 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.main.MainActivity
import io.github.wulkanowy.utils.extension.setOnItemClickListener
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_login_options.*
import javax.inject.Inject

View File

@ -1,14 +0,0 @@
package io.github.wulkanowy.ui.login.options
import dagger.Module
import dagger.Provides
import eu.davidea.flexibleadapter.FlexibleAdapter
import io.github.wulkanowy.di.scopes.PerFragment
@Module
internal class LoginOptionsModule {
@Provides
@PerFragment
fun provideLoginOptionsAdapter() = FlexibleAdapter<LoginOptionsItem>(null)
}

View File

@ -3,7 +3,6 @@ package io.github.wulkanowy.ui.main
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v4.content.ContextCompat
import com.aurelhubert.ahbottomnavigation.AHBottomNavigation
import com.aurelhubert.ahbottomnavigation.AHBottomNavigationItem
@ -16,10 +15,12 @@ import io.github.wulkanowy.ui.main.exam.ExamFragment
import io.github.wulkanowy.ui.main.grade.GradeFragment
import io.github.wulkanowy.ui.main.more.MoreFragment
import io.github.wulkanowy.ui.main.timetable.TimetableFragment
import io.github.wulkanowy.utils.setOnTabTransactionListener
import kotlinx.android.synthetic.main.activity_main.*
import javax.inject.Inject
class MainActivity : BaseActivity(), MainView, FragNavController.TransactionListener {
class MainActivity : BaseActivity(), MainView {
@Inject
lateinit var presenter: MainPresenter
@ -27,7 +28,7 @@ class MainActivity : BaseActivity(), MainView, FragNavController.TransactionList
lateinit var navController: FragNavController
companion object {
const val DEFAULT_TAB = 0
const val DEFAULT_TAB = 2
fun getStartIntent(context: Context) = Intent(context, MainActivity::class.java)
}
@ -35,27 +36,19 @@ class MainActivity : BaseActivity(), MainView, FragNavController.TransactionList
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
messageView = mainContainer
setSupportActionBar(mainToolbar)
messageContainer = mainFragmentContainer
presenter.attachView(this)
navController.initialize(DEFAULT_TAB, savedInstanceState)
}
override fun initFragmentController() {
navController.run {
rootFragments = listOf(
GradeFragment.newInstance(),
AttendanceFragment.newInstance(),
ExamFragment.newInstance(),
TimetableFragment.newInstance(),
MoreFragment.newInstance()
)
fragmentHideStrategy = DETACH_ON_NAVIGATE_HIDE_ON_SWITCH
createEager = true
transactionListener = this@MainActivity
}
override fun onStart() {
super.onStart()
presenter.onStartView()
}
override fun initBottomNav() {
override fun initView() {
mainBottomNav.run {
addItems(mutableListOf(
AHBottomNavigationItem(R.string.grade_title, R.drawable.ic_menu_main_grade_26dp, 0),
@ -69,39 +62,63 @@ class MainActivity : BaseActivity(), MainView, FragNavController.TransactionList
titleState = AHBottomNavigation.TitleState.ALWAYS_SHOW
currentItem = DEFAULT_TAB
isBehaviorTranslationEnabled = false
setOnTabSelectedListener { position, _ ->
presenter.onTabSelected(position)
setTitleTextSizeInSp(10f, 10f)
setOnTabSelectedListener { position, wasSelected ->
presenter.onTabSelected(position, wasSelected)
}
}
navController.run {
setOnTabTransactionListener { presenter.onMenuViewChange(it) }
fragmentHideStrategy = DETACH_ON_NAVIGATE_HIDE_ON_SWITCH
rootFragments = listOf(
GradeFragment.newInstance(),
AttendanceFragment.newInstance(),
ExamFragment.newInstance(),
TimetableFragment.newInstance(),
MoreFragment.newInstance()
)
}
}
override fun onFragmentTransaction(fragment: Fragment?, transactionType: FragNavController.TransactionType) {}
override fun onTabTransaction(fragment: Fragment?, index: Int) {
presenter.onMenuFragmentChange(index)
}
override fun switchMenuFragment(position: Int) {
override fun switchMenuView(position: Int) {
navController.switchTab(position)
}
override fun setViewTitle(title: String) {
setTitle(title)
supportActionBar?.title = title
}
override fun defaultTitle(): String = getString(R.string.main_title)
override fun expandActionBar(show: Boolean) {
mainAppBarContainer.setExpanded(show, true)
}
override fun mapOfTitles(): Map<Int, String> {
return mapOf(0 to R.string.grade_title,
1 to R.string.attendance_title,
2 to R.string.exam_title,
3 to R.string.timetable_title,
4 to R.string.more_title
).mapValues { getString(it.value) }
override fun viewTitle(index: Int): String {
return getString(listOf(R.string.grade_title,
R.string.attendance_title,
R.string.exam_title,
R.string.timetable_title,
R.string.more_title)[index])
}
override fun currentMenuIndex() = navController.currentStackIndex
override fun notifyMenuViewReselected() {
(navController.currentFrag as? MainView.MenuFragmentView)?.onFragmentReselected()
}
override fun onBackPressed() {
navController.apply { if (isRootFragment) super.onBackPressed() else popFragment() }
}
override fun onSaveInstanceState(outState: Bundle?) {
super.onSaveInstanceState(outState)
navController.onSaveInstanceState(outState)
}
override fun onDestroy() {
super.onDestroy()
presenter.detachView()
}
}

View File

@ -10,6 +10,7 @@ import io.github.wulkanowy.di.scopes.PerFragment
import io.github.wulkanowy.ui.main.attendance.AttendanceFragment
import io.github.wulkanowy.ui.main.exam.ExamFragment
import io.github.wulkanowy.ui.main.grade.GradeFragment
import io.github.wulkanowy.ui.main.grade.GradeModule
import io.github.wulkanowy.ui.main.more.MoreFragment
import io.github.wulkanowy.ui.main.timetable.TimetableFragment
@ -36,7 +37,7 @@ abstract class MainModule {
abstract fun bindExamFragment(): ExamFragment
@PerFragment
@ContributesAndroidInjector
@ContributesAndroidInjector(modules = [GradeModule::class])
abstract fun bindGradeFragment(): GradeFragment
@PerFragment
@ -47,4 +48,3 @@ abstract class MainModule {
@ContributesAndroidInjector
abstract fun bindTimetableFragment(): TimetableFragment
}

View File

@ -9,21 +9,27 @@ class MainPresenter @Inject constructor(errorHandler: ErrorHandler)
override fun attachView(view: MainView) {
super.attachView(view)
view.run {
initFragmentController()
initBottomNav()
}
view.initView()
}
fun onTabSelected(position: Int): Boolean {
view?.switchMenuFragment(position)
return true
fun onStartView() {
view?.run { setViewTitle(viewTitle(currentMenuIndex())) }
}
fun onMenuFragmentChange(position: Int) {
view?.run {
setViewTitle(mapOfTitles()[position] ?: defaultTitle())
}
fun onMenuViewChange(index: Int) {
view?.run { setViewTitle(viewTitle(index)) }
}
fun onTabSelected(index: Int, wasSelected: Boolean): Boolean {
return view?.run {
expandActionBar(true)
if (wasSelected) {
notifyMenuViewReselected()
false
} else {
switchMenuView(index)
true
}
} == true
}
}

View File

@ -4,15 +4,22 @@ import io.github.wulkanowy.ui.base.BaseView
interface MainView : BaseView {
fun initFragmentController()
fun initView()
fun initBottomNav()
fun switchMenuFragment(position: Int)
fun switchMenuView(position: Int)
fun setViewTitle(title: String)
fun defaultTitle(): String
fun expandActionBar(show: Boolean)
fun mapOfTitles(): Map<Int, String>
fun viewTitle(index: Int): String
fun currentMenuIndex(): Int
fun notifyMenuViewReselected()
interface MenuFragmentView {
fun onFragmentReselected()
}
}

View File

@ -7,7 +7,7 @@ import android.view.View
import android.view.ViewGroup
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.utils.extension.toFormat
import io.github.wulkanowy.utils.toFormattedString
import kotlinx.android.synthetic.main.dialog_attendance.*
class AttendanceDialog : DialogFragment() {
@ -42,7 +42,7 @@ class AttendanceDialog : DialogFragment() {
attendanceDialogSubject.text = attendance.subject
attendanceDialogDescription.text = attendance.name
attendanceDialogDate.text = attendance.date.toFormat()
attendanceDialogDate.text = attendance.date.toFormattedString()
attendanceDialogNumber.text = attendance.number.toString()
attendanceDialogClose.setOnClickListener { dismiss() }
}

View File

@ -10,7 +10,7 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.utils.extension.setOnItemClickListener
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_attendance.*
import javax.inject.Inject

View File

@ -7,7 +7,7 @@ import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.repositories.AttendanceRepository
import io.github.wulkanowy.data.repositories.SessionRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.utils.extension.*
import io.github.wulkanowy.utils.*
import io.github.wulkanowy.utils.schedulers.SchedulersManager
import org.threeten.bp.LocalDate
import javax.inject.Inject
@ -19,7 +19,7 @@ class AttendancePresenter @Inject constructor(
private val sessionRepository: SessionRepository
) : BasePresenter<AttendanceView>(errorHandler) {
var currentDate: LocalDate = LocalDate.now().getNearSchoolDayPrevOnWeekEnd()
var currentDate: LocalDate = LocalDate.now().nearSchoolDayPrevOnWeekEnd
private set
override fun attachView(view: AttendanceView) {
@ -27,13 +27,14 @@ class AttendancePresenter @Inject constructor(
view.initView()
}
fun loadAttendanceForPreviousDay() = loadData(currentDate.getPreviousWorkDay().toEpochDay())
fun loadAttendanceForPreviousDay() = loadData(currentDate.previousWorkDay.toEpochDay())
fun loadAttendanceForNextDay() = loadData(currentDate.getNextWorkDay().toEpochDay())
fun loadAttendanceForNextDay() = loadData(currentDate.nextWorkDay.toEpochDay())
fun loadData(date: Long?, forceRefresh: Boolean = false) {
this.currentDate = LocalDate.ofEpochDay(date ?: currentDate.getNearSchoolDayPrevOnWeekEnd().toEpochDay())
if (currentDate.isHolidays()) return
this.currentDate = LocalDate.ofEpochDay(date
?: currentDate.nearSchoolDayPrevOnWeekEnd.toEpochDay())
if (currentDate.isHolidays) return
disposable.clear()
disposable.add(sessionRepository.getSemesters()
@ -50,9 +51,9 @@ class AttendancePresenter @Inject constructor(
showEmpty(false)
clearData()
}
showPreButton(!currentDate.minusDays(1).isHolidays())
showNextButton(!currentDate.plusDays(1).isHolidays())
updateNavigationDay(currentDate.toFormat("EEEE \n dd.MM.YYYY").capitalize())
showPreButton(!currentDate.minusDays(1).isHolidays)
showNextButton(!currentDate.plusDays(1).isHolidays)
updateNavigationDay(currentDate.toFormattedString("EEEE \n dd.MM.YYYY").capitalize())
}
}
.doFinally {

View File

@ -7,7 +7,7 @@ import android.view.View
import android.view.ViewGroup
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.utils.extension.toFormat
import io.github.wulkanowy.utils.toFormattedString
import kotlinx.android.synthetic.main.dialog_exam.*
class ExamDialog : DialogFragment() {
@ -43,7 +43,7 @@ class ExamDialog : DialogFragment() {
examDialogSubjectValue.text = exam.subject
examDialogTypeValue.text = exam.type
examDialogTeacherValue.text = exam.teacher
examDialogDateValue.text = exam.entryDate.toFormat()
examDialogDateValue.text = exam.entryDate.toFormattedString()
examDialogDescriptionValue.text = exam.description
examDialogClose.setOnClickListener { dismiss() }

View File

@ -11,7 +11,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.BaseFragment
import io.github.wulkanowy.utils.extension.setOnItemClickListener
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_exam.*
import javax.inject.Inject

View File

@ -6,8 +6,8 @@ import eu.davidea.flexibleadapter.items.AbstractHeaderItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.ExpandableViewHolder
import io.github.wulkanowy.R
import io.github.wulkanowy.utils.extension.getWeekDayName
import io.github.wulkanowy.utils.extension.toFormat
import io.github.wulkanowy.utils.toFormattedString
import io.github.wulkanowy.utils.weekDayName
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.header_exam.*
import org.threeten.bp.LocalDate
@ -40,8 +40,8 @@ class ExamHeader : AbstractHeaderItem<ExamHeader.ViewHolder>() {
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>?, holder: ViewHolder,
position: Int, payloads: MutableList<Any>?) {
holder.run {
examHeaderDay.text = date.getWeekDayName().capitalize()
examHeaderDate.text = date.toFormat()
examHeaderDay.text = date.weekDayName.capitalize()
examHeaderDate.text = date.toFormattedString()
}
}

View File

@ -7,10 +7,10 @@ import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.repositories.ExamRepository
import io.github.wulkanowy.data.repositories.SessionRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.utils.extension.getWeekFirstDayNextOnWeekEnd
import io.github.wulkanowy.utils.extension.isHolidays
import io.github.wulkanowy.utils.extension.toFormat
import io.github.wulkanowy.utils.isHolidays
import io.github.wulkanowy.utils.schedulers.SchedulersManager
import io.github.wulkanowy.utils.toFormattedString
import io.github.wulkanowy.utils.weekFirstDayNextOnWeekEnd
import org.threeten.bp.LocalDate
import javax.inject.Inject
@ -21,7 +21,7 @@ class ExamPresenter @Inject constructor(
private val sessionRepository: SessionRepository
) : BasePresenter<ExamView>(errorHandler) {
var currentDate: LocalDate = LocalDate.now().getWeekFirstDayNextOnWeekEnd()
var currentDate: LocalDate = LocalDate.now().weekFirstDayNextOnWeekEnd
private set
override fun attachView(view: ExamView) {
@ -34,8 +34,9 @@ class ExamPresenter @Inject constructor(
fun loadExamsForNextWeek() = loadData(currentDate.plusDays(7).toEpochDay())
fun loadData(date: Long?, forceRefresh: Boolean = false) {
this.currentDate = LocalDate.ofEpochDay(date ?: currentDate.getWeekFirstDayNextOnWeekEnd().toEpochDay())
if (currentDate.isHolidays()) return
this.currentDate = LocalDate.ofEpochDay(date
?: currentDate.weekFirstDayNextOnWeekEnd.toEpochDay())
if (currentDate.isHolidays) return
disposable.clear()
disposable.add(sessionRepository.getSemesters()
@ -51,9 +52,10 @@ class ExamPresenter @Inject constructor(
showProgress(!forceRefresh)
if (!forceRefresh) showEmpty(false)
showContent(null == date && forceRefresh)
showPreButton(!currentDate.minusDays(7).isHolidays())
showNextButton(!currentDate.plusDays(7).isHolidays())
updateNavigationWeek("${currentDate.toFormat("dd.MM")}-${currentDate.plusDays(4).toFormat("dd.MM")}")
showPreButton(!currentDate.minusDays(7).isHolidays)
showNextButton(!currentDate.plusDays(7).isHolidays)
updateNavigationWeek(currentDate.toFormattedString("dd.MM") +
"-${currentDate.plusDays(4).toFormattedString("dd.MM")}")
}
}
.doAfterSuccess {

View File

@ -1,19 +1,120 @@
package io.github.wulkanowy.ui.main.grade
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.support.v7.app.AlertDialog
import android.view.*
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.base.BasePagerAdapter
import io.github.wulkanowy.ui.main.MainView
import io.github.wulkanowy.ui.main.grade.details.GradeDetailsFragment
import io.github.wulkanowy.ui.main.grade.summary.GradeSummaryFragment
import io.github.wulkanowy.utils.setOnSelectPageListener
import kotlinx.android.synthetic.main.fragment_grade.*
import javax.inject.Inject
class GradeFragment : BaseFragment() {
class GradeFragment : BaseFragment(), GradeView, MainView.MenuFragmentView {
@Inject
lateinit var presenter: GradePresenter
@Inject
lateinit var pagerAdapter: BasePagerAdapter
companion object {
fun newInstance() = GradeFragment()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_grade, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
presenter.attachView(this)
}
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
inflater?.inflate(R.menu.action_menu_grade, menu)
}
override fun initView() {
pagerAdapter.fragments.putAll(mapOf(
getString(R.string.all_details) to GradeDetailsFragment.newInstance(),
getString(R.string.grade_menu_summary) to GradeSummaryFragment.newInstance()
))
gradeViewPager.run {
adapter = pagerAdapter
setOnSelectPageListener { presenter.onPageSelected(it) }
}
gradeTabLayout.setupWithViewPager(gradeViewPager)
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
return if (item?.itemId == R.id.gradeMenuSemester) presenter.onSemesterSwitch()
else false
}
override fun onFragmentReselected() {
presenter.onViewReselected()
}
override fun showContent(show: Boolean) {
gradeViewPager.visibility = if (show) VISIBLE else INVISIBLE
gradeTabLayout.visibility = if (show) VISIBLE else INVISIBLE
}
override fun showProgress(show: Boolean) {
gradeProgress.visibility = if (show) VISIBLE else INVISIBLE
}
override fun showSemesterDialog(selectedIndex: Int) {
arrayOf(getString(R.string.grade_semester, 1),
getString(R.string.grade_semester, 2)).also { array ->
context?.let {
AlertDialog.Builder(it)
.setSingleChoiceItems(array, selectedIndex) { dialog, which ->
presenter.onSemesterSelected(which)
dialog.dismiss()
}
.setTitle(R.string.grade_switch_semester)
.setNegativeButton(R.string.all_cancel) { dialog, _ -> dialog.dismiss() }
.show()
}
}
}
override fun currentPageIndex() = gradeViewPager.currentItem
fun onChildRefresh() {
presenter.onChildViewRefresh()
}
fun onChildFragmentLoaded(semesterId: String) {
presenter.onChildViewLoaded(semesterId)
}
override fun notifyChildLoadData(index: Int, semesterId: String, forceRefresh: Boolean) {
(childFragmentManager.fragments[index] as GradeView.GradeChildView).onParentLoadData(semesterId, forceRefresh)
}
override fun notifyChildParentReselected(index: Int) {
(pagerAdapter.registeredFragments[index] as? GradeView.GradeChildView)?.onParentReselected()
}
override fun notifyChildSemesterChange(index: Int) {
(pagerAdapter.registeredFragments[index] as? GradeView.GradeChildView)?.onParentChangeSemester()
}
override fun onDestroyView() {
super.onDestroyView()
presenter.detachView()
}
}

View File

@ -0,0 +1,32 @@
package io.github.wulkanowy.ui.main.grade
import dagger.Module
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.main.grade.details.GradeDetailsFragment
import io.github.wulkanowy.ui.main.grade.summary.GradeSummaryFragment
@Module
abstract class GradeModule {
@Module
companion object {
@JvmStatic
@PerFragment
@Provides
fun provideGradePagerAdapter(fragment: GradeFragment) = BasePagerAdapter(fragment.childFragmentManager)
}
@PerChildFragment
@ContributesAndroidInjector()
abstract fun bindGradeDetailsFragment(): GradeDetailsFragment
@PerChildFragment
@ContributesAndroidInjector
abstract fun binGradeSummaryFragment(): GradeSummaryFragment
}

View File

@ -0,0 +1,94 @@
package io.github.wulkanowy.ui.main.grade
import io.github.wulkanowy.data.ErrorHandler
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.repositories.SessionRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.utils.schedulers.SchedulersManager
import io.reactivex.Completable
import java.util.concurrent.TimeUnit
import javax.inject.Inject
class GradePresenter @Inject constructor(
private val errorHandler: ErrorHandler,
private val schedulers: SchedulersManager,
private val sessionRepository: SessionRepository) : BasePresenter<GradeView>(errorHandler) {
private var semesters = emptyList<Semester>()
private var selectedIndex = 0
private val loadedSemesterId = mutableMapOf<Int, String>()
override fun attachView(view: GradeView) {
super.attachView(view)
disposable.add(Completable.timer(150, TimeUnit.MILLISECONDS, schedulers.mainThread())
.subscribe {
view.initView()
loadData()
})
}
fun onViewReselected() {
view?.run { notifyChildParentReselected(currentPageIndex()) }
}
fun onSemesterSwitch(): Boolean {
if (semesters.isNotEmpty()) view?.showSemesterDialog(selectedIndex)
return true
}
fun onSemesterSelected(index: Int) {
if (selectedIndex != index) {
selectedIndex = index
loadedSemesterId.clear()
view?.let {
notifyChildrenSemesterChange()
loadChild(it.currentPageIndex())
}
}
}
fun onChildViewRefresh() {
view?.let { loadChild(it.currentPageIndex(), forceRefresh = true) }
}
fun onChildViewLoaded(semesterId: String) {
view?.apply {
showContent(true)
showProgress(false)
loadedSemesterId[currentPageIndex()] = semesterId
}
}
fun onPageSelected(index: Int) {
loadChild(index)
}
private fun loadData() {
disposable.add(sessionRepository.getSemesters()
.map {
it.first { item -> item.current }.also { current ->
selectedIndex = current.semesterName - 1
semesters = it.filter { semester -> semester.diaryId == current.diaryId }
}
}
.subscribeOn(schedulers.backgroundThread())
.observeOn(schedulers.mainThread())
.subscribe({ _ ->
view?.let { loadChild(it.currentPageIndex()) }
}) { errorHandler.proceed(it) })
}
private fun loadChild(index: Int, forceRefresh: Boolean = false) {
semesters.first { it.semesterName == selectedIndex + 1 }.semesterId.also {
if (forceRefresh || loadedSemesterId[index] != it) {
view?.notifyChildLoadData(index, it, forceRefresh)
}
}
}
private fun notifyChildrenSemesterChange() {
for (i in 0..1) view?.notifyChildSemesterChange(i)
}
}

View File

@ -0,0 +1,31 @@
package io.github.wulkanowy.ui.main.grade
import io.github.wulkanowy.ui.base.BaseView
interface GradeView : BaseView {
fun initView()
fun currentPageIndex(): Int
fun showContent(show: Boolean)
fun showProgress(show: Boolean)
fun showSemesterDialog(selectedIndex: Int)
fun notifyChildLoadData(index: Int, semesterId: String, forceRefresh: Boolean)
fun notifyChildParentReselected(index: Int)
fun notifyChildSemesterChange(index: Int)
interface GradeChildView {
fun onParentChangeSemester()
fun onParentLoadData(semesterId: String, forceRefresh: Boolean)
fun onParentReselected()
}
}

View File

@ -0,0 +1,79 @@
package io.github.wulkanowy.ui.main.grade.details
import android.os.Bundle
import android.support.v4.app.DialogFragment
import android.view.LayoutInflater
import android.view.View
import android.view.View.GONE
import android.view.ViewGroup
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.utils.colorStringId
import io.github.wulkanowy.utils.toFormattedString
import io.github.wulkanowy.utils.valueColor
import kotlinx.android.synthetic.main.dialog_grade.*
class GradeDetailsDialog : DialogFragment() {
private lateinit var grade: Grade
companion object {
private const val ARGUMENT_KEY = "Item"
fun newInstance(grade: Grade): GradeDetailsDialog {
return GradeDetailsDialog().apply {
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, grade) }
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.run {
grade = getSerializable(ARGUMENT_KEY) as Grade
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.dialog_grade, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
gradeDialogSubject.text = grade.subject
gradeDialogWeightValue.text = grade.weight
gradeDialogDateValue.text = grade.date.toFormattedString()
gradeDialogColorValue.text = getString(grade.colorStringId)
gradeDialogCommentValue.apply {
if (grade.comment.isEmpty()) {
visibility = GONE
gradeDialogComment.visibility = GONE
} else text = grade.comment
}
gradeDialogValue.run {
text = grade.entry
setBackgroundResource(grade.valueColor)
}
gradeDialogTeacherValue.text = if (grade.teacher.isEmpty()) {
getString(R.string.all_no_data)
} else grade.teacher
gradeDialogDescriptionValue.text = grade.run {
when {
description.isEmpty() && gradeSymbol.isNotEmpty() -> gradeSymbol
description.isEmpty() && gradeSymbol.isEmpty() -> getString(R.string.all_no_description)
gradeSymbol.isNotEmpty() && description.isNotEmpty() -> "$gradeSymbol - $description"
else -> description
}
}
gradeDialogClose.setOnClickListener { dismiss() }
}
}

View File

@ -0,0 +1,134 @@
package io.github.wulkanowy.ui.main.grade.details
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.View.*
import android.view.ViewGroup
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IExpandable
import eu.davidea.flexibleadapter.items.IFlexible
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.main.grade.GradeFragment
import io.github.wulkanowy.ui.main.grade.GradeView
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_grade_details.*
import javax.inject.Inject
class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeChildView {
@Inject
lateinit var presenter: GradeDetailsPresenter
@Inject
lateinit var gradeDetailsAdapter: FlexibleAdapter<AbstractFlexibleItem<*>>
companion object {
fun newInstance() = GradeDetailsFragment()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_grade_details, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
presenter.attachView(this)
}
override fun initView() {
gradeDetailsAdapter.run {
isAutoCollapseOnExpand = true
isAutoScrollOnExpand = true
setOnItemClickListener { presenter.onGradeItemSelected(getItem(it)) }
}
gradeDetailsRecycler.run {
layoutManager = SmoothScrollLinearLayoutManager(context)
adapter = gradeDetailsAdapter
}
gradeDetailsSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
}
override fun updateData(data: List<GradeDetailsHeader>) {
gradeDetailsAdapter.updateDataSet(data, true)
}
override fun updateItem(item: AbstractFlexibleItem<*>) {
gradeDetailsAdapter.updateItem(item)
}
override fun clearView() {
gradeDetailsAdapter.clear()
}
override fun resetView() {
gradeDetailsAdapter.apply {
smoothScrollToPosition(0)
collapseAll()
}
}
override fun getHeaderOfItem(item: AbstractFlexibleItem<*>): IExpandable<*, out IFlexible<*>>? {
return gradeDetailsAdapter.getExpandableOf(item)
}
override fun isViewEmpty() = gradeDetailsAdapter.isEmpty
override fun showProgress(show: Boolean) {
gradeDetailsProgress.visibility = if (show) VISIBLE else GONE
}
override fun showContent(show: Boolean) {
gradeDetailsRecycler.visibility = if (show) VISIBLE else INVISIBLE
}
override fun showEmpty(show: Boolean) {
gradeDetailsEmpty.visibility = if (show) VISIBLE else INVISIBLE
}
override fun showRefresh(show: Boolean) {
gradeDetailsSwipe.isRefreshing = show
}
override fun showGradeDialog(grade: Grade) {
GradeDetailsDialog.newInstance(grade).show(fragmentManager, grade.toString())
}
override fun onParentLoadData(semesterId: String, forceRefresh: Boolean) {
presenter.loadData(semesterId, forceRefresh)
}
override fun onParentReselected() {
presenter.onParentViewReselected()
}
override fun onParentChangeSemester() {
presenter.onParentChangeSemester()
}
override fun notifyParentDataLoaded(semesterId: String) {
(parentFragment as? GradeFragment)?.onChildFragmentLoaded(semesterId)
}
override fun notifyParentRefresh() {
(parentFragment as? GradeFragment)?.onChildRefresh()
}
override fun emptyAverageString(): String = getString(R.string.grade_no_average)
override fun averageString(): String = getString(R.string.grade_average)
override fun gradeNumberString(number: Int): String = resources.getQuantityString(R.plurals.grade_number_item, number, number)
override fun weightString(): String = getString(R.string.grade_weight)
override fun onDestroyView() {
super.onDestroyView()
presenter.detachView()
}
}

View File

@ -0,0 +1,73 @@
package io.github.wulkanowy.ui.main.grade.details
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractExpandableItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.ExpandableViewHolder
import io.github.wulkanowy.R
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.header_grade_details.*
class GradeDetailsHeader(
private val subject: String,
private val number: String,
private val average: String,
var newGrades: Int)
: AbstractExpandableItem<GradeDetailsHeader.ViewHolder, GradeDetailsItem>() {
override fun getLayoutRes() = R.layout.header_grade_details
override fun createViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?): ViewHolder {
return ViewHolder(view, adapter)
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>?, holder: ViewHolder,
position: Int, payloads: MutableList<Any>?) {
holder.run {
gradeHeaderSubject.text = subject
gradeHeaderAverage.text = average
gradeHeaderNumber.text = number
gradeHeaderPredicted.visibility = GONE
gradeHeaderFinal.visibility = GONE
gradeHeaderNote.visibility = if (newGrades > 0) VISIBLE else GONE
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as GradeDetailsHeader
if (subject != other.subject) return false
if (number != other.number) return false
if (average != other.average) return false
return true
}
override fun hashCode(): Int {
var result = subject.hashCode()
result = 31 * result + number.hashCode()
result = 31 * result + average.hashCode()
return result
}
class ViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?) : ExpandableViewHolder(view, adapter),
LayoutContainer {
init {
contentView.setOnClickListener(this)
}
override fun shouldNotifyParentOnClick() = true
override val containerView: View
get() = contentView
}
}

View File

@ -0,0 +1,68 @@
package io.github.wulkanowy.ui.main.grade.details
import android.annotation.SuppressLint
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.utils.toFormattedString
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_grade_details.*
class GradeDetailsItem(val grade: Grade, private val weightString: String, private val valueColor: Int)
: AbstractFlexibleItem<GradeDetailsItem.ViewHolder>() {
override fun getLayoutRes() = R.layout.item_grade_details
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ViewHolder {
return ViewHolder(view, adapter)
}
@SuppressLint("SetTextI18n")
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder,
position: Int, payloads: MutableList<Any>?) {
holder.run {
gradeItemValue.run {
text = grade.entry
setBackgroundResource(valueColor)
}
gradeItemDescription.text = if (grade.description.isNotEmpty()) grade.description else grade.gradeSymbol
gradeItemDate.text = grade.date.toFormattedString()
gradeItemWeight.text = "$weightString: ${grade.weight}"
gradeItemNote.visibility = if (grade.isNew) VISIBLE else GONE
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as GradeDetailsItem
if (grade != other.grade) return false
if (weightString != other.weightString) return false
if (valueColor != other.valueColor) return false
return true
}
override fun hashCode(): Int {
var result = grade.hashCode()
result = 31 * result + weightString.hashCode()
result = 31 * result + valueColor
return result
}
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter),
LayoutContainer {
override val containerView: View
get() = contentView
}
}

View File

@ -0,0 +1,124 @@
package io.github.wulkanowy.ui.main.grade.details
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.ErrorHandler
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.repositories.GradeRepository
import io.github.wulkanowy.data.repositories.SessionRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.utils.calcAverage
import io.github.wulkanowy.utils.schedulers.SchedulersManager
import io.github.wulkanowy.utils.valueColor
import javax.inject.Inject
class GradeDetailsPresenter @Inject constructor(
private val errorHandler: ErrorHandler,
private val schedulers: SchedulersManager,
private val gradeRepository: GradeRepository,
private val sessionRepository: SessionRepository) : BasePresenter<GradeDetailsView>(errorHandler) {
override fun attachView(view: GradeDetailsView) {
super.attachView(view)
view.initView()
}
fun loadData(semesterId: String, forceRefresh: Boolean) {
disposable.add(sessionRepository.getSemesters()
.flatMap { gradeRepository.getGrades(it.first { item -> item.semesterId == semesterId }, forceRefresh) }
.map { createGradeItems(it.groupBy { grade -> grade.subject }.toSortedMap()) }
.subscribeOn(schedulers.backgroundThread())
.observeOn(schedulers.mainThread())
.doFinally {
view?.run {
showRefresh(false)
showProgress(false)
notifyParentDataLoaded(semesterId)
}
}
.subscribe({
view?.run {
showEmpty(it.isEmpty())
showContent(it.isNotEmpty())
updateData(it)
}
}) {
view?.run { showEmpty(isViewEmpty()) }
errorHandler.proceed(it)
})
}
fun onGradeItemSelected(item: AbstractFlexibleItem<*>?) {
if (item is GradeDetailsItem) {
view?.apply {
showGradeDialog(item.grade)
if (item.grade.isNew) {
item.grade.isNew = false
updateItem(item)
getHeaderOfItem(item)?.let { header ->
if (header is GradeDetailsHeader) {
header.newGrades--
updateItem(header)
}
}
updateGrade(item.grade)
}
}
}
}
fun onSwipeRefresh() {
view?.notifyParentRefresh()
}
fun onParentViewReselected() {
view?.run {
if (!isViewEmpty()) resetView()
}
}
fun onParentChangeSemester() {
view?.run {
showProgress(true)
showRefresh(false)
showContent(false)
showEmpty(false)
clearView()
}
disposable.clear()
}
private fun createGradeItems(items: Map<String, List<Grade>>): List<GradeDetailsHeader> {
return items.map {
it.value.calcAverage().let { average ->
GradeDetailsHeader(
subject = it.key,
average = formatAverage(average),
number = view?.gradeNumberString(it.value.size).orEmpty(),
newGrades = it.value.filter { grade -> grade.isNew }.size
).apply {
subItems = it.value.map { item ->
GradeDetailsItem(
grade = item,
weightString = view?.weightString().orEmpty(),
valueColor = item.valueColor
)
}
}
}
}
}
private fun formatAverage(average: Double): String {
return view?.run {
if (average == 0.0) emptyAverageString()
else averageString().format(average)
}.orEmpty()
}
private fun updateGrade(grade: Grade) {
disposable.add(gradeRepository.updateGrade(grade)
.subscribeOn(schedulers.backgroundThread())
.observeOn(schedulers.mainThread())
.subscribe({}) { error -> errorHandler.proceed(error) })
}
}

View File

@ -0,0 +1,46 @@
package io.github.wulkanowy.ui.main.grade.details
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IExpandable
import eu.davidea.flexibleadapter.items.IFlexible
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.ui.base.BaseView
interface GradeDetailsView : BaseView {
fun initView()
fun updateData(data: List<GradeDetailsHeader>)
fun updateItem(item: AbstractFlexibleItem<*>)
fun getHeaderOfItem(item: AbstractFlexibleItem<*>): IExpandable<*, out IFlexible<*>>?
fun resetView()
fun clearView()
fun isViewEmpty(): Boolean
fun showGradeDialog(grade: Grade)
fun showContent(show: Boolean)
fun showEmpty(show: Boolean)
fun showProgress(show: Boolean)
fun showRefresh(show: Boolean)
fun emptyAverageString(): String
fun averageString(): String
fun gradeNumberString(number: Int): String
fun weightString(): String
fun notifyParentDataLoaded(semesterId: String)
fun notifyParentRefresh()
}

View File

@ -0,0 +1,111 @@
package io.github.wulkanowy.ui.main.grade.summary
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.View.*
import android.view.ViewGroup
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.main.grade.GradeFragment
import io.github.wulkanowy.ui.main.grade.GradeView
import kotlinx.android.synthetic.main.fragment_grade_summary.*
import javax.inject.Inject
class GradeSummaryFragment : BaseFragment(), GradeSummaryView, GradeView.GradeChildView {
@Inject
lateinit var presenter: GradeSummaryPresenter
@Inject
lateinit var gradeSummaryAdapter: FlexibleAdapter<AbstractFlexibleItem<*>>
companion object {
fun newInstance() = GradeSummaryFragment()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_grade_summary, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
presenter.attachView(this)
}
override fun initView() {
gradeSummaryAdapter.setDisplayHeadersAtStartUp(true)
gradeSummaryRecycler.run {
layoutManager = SmoothScrollLinearLayoutManager(context)
adapter = gradeSummaryAdapter
}
gradeSummarySwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
}
override fun updateDataSet(data: List<GradeSummaryItem>, header: GradeSummaryScrollableHeader) {
gradeSummaryAdapter.apply {
updateDataSet(data, true)
removeAllScrollableHeaders()
addScrollableHeader(header)
}
}
override fun clearView() {
gradeSummaryAdapter.clear()
}
override fun resetView() {
gradeSummaryAdapter.smoothScrollToPosition(0)
}
override fun isViewEmpty() = gradeSummaryAdapter.isEmpty
override fun showContent(show: Boolean) {
gradeSummaryRecycler.visibility = if (show) VISIBLE else INVISIBLE
}
override fun showEmpty(show: Boolean) {
gradeSummaryEmpty.visibility = if (show) VISIBLE else INVISIBLE
}
override fun showProgress(show: Boolean) {
gradeSummaryProgress.visibility = if (show) VISIBLE else GONE
}
override fun showRefresh(show: Boolean) {
gradeSummarySwipe.isRefreshing = show
}
override fun onParentLoadData(semesterId: String, forceRefresh: Boolean) {
presenter.loadData(semesterId, forceRefresh)
}
override fun onParentReselected() {
presenter.onParentViewReselected()
}
override fun onParentChangeSemester() {
presenter.onParentChangeSemester()
}
override fun notifyParentDataLoaded(semesterId: String) {
(parentFragment as? GradeFragment)?.onChildFragmentLoaded(semesterId)
}
override fun notifyParentRefresh() {
(parentFragment as? GradeFragment)?.onChildRefresh()
}
override fun predictedString() = getString(R.string.grade_summary_predicted_grade)
override fun finalString() = getString(R.string.grade_summary_final_grade)
override fun onDestroyView() {
super.onDestroyView()
presenter.detachView()
}
}

View File

@ -0,0 +1,52 @@
package io.github.wulkanowy.ui.main.grade.summary
import android.view.View
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractHeaderItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
import io.github.wulkanowy.R
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.header_grade_summary.*
class GradeSummaryHeader(private val name: String, private val average: String) : AbstractHeaderItem<GradeSummaryHeader.ViewHolder>() {
override fun getLayoutRes() = R.layout.header_grade_summary
override fun createViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?): ViewHolder {
return ViewHolder(view, adapter)
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>?, holder: ViewHolder?,
position: Int, payloads: MutableList<Any>?) {
holder?.run {
gradeSummaryHeaderName.text = name
gradeSummaryHeaderAverage.text = average
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as GradeSummaryHeader
if (name != other.name) return false
if (average != other.average) return false
return true
}
override fun hashCode(): Int {
var result = name.hashCode()
result = 31 * result + average.hashCode()
return result
}
class ViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?) :
FlexibleViewHolder(view, adapter), LayoutContainer {
override val containerView: View?
get() = contentView
}
}

View File

@ -0,0 +1,56 @@
package io.github.wulkanowy.ui.main.grade.summary
import android.view.View
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractSectionableItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
import io.github.wulkanowy.R
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_grade_summary.*
class GradeSummaryItem(header: GradeSummaryHeader, private val grade: String, private val title: String)
: AbstractSectionableItem<GradeSummaryItem.ViewHolder, GradeSummaryHeader>(header) {
override fun getLayoutRes() = R.layout.item_grade_summary
override fun createViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?): ViewHolder {
return ViewHolder(view, adapter)
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>?, holder: ViewHolder?,
position: Int, payloads: MutableList<Any>?) {
holder?.run {
gradeSummaryItemGrade.text = grade
gradeSummaryItemTitle.text = title
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as GradeSummaryItem
if (grade != other.grade) return false
if (title != other.title) return false
if (header != other.header) return false
return true
}
override fun hashCode(): Int {
var result = header.hashCode()
result = 31 * result + grade.hashCode()
result = 31 * result + title.hashCode()
return result
}
class ViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?)
: FlexibleViewHolder(view, adapter), LayoutContainer {
override val containerView: View?
get() = contentView
}
}

View File

@ -0,0 +1,121 @@
package io.github.wulkanowy.ui.main.grade.summary
import io.github.wulkanowy.data.ErrorHandler
import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.repositories.GradeRepository
import io.github.wulkanowy.data.repositories.GradeSummaryRepository
import io.github.wulkanowy.data.repositories.SessionRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.utils.calcAverage
import io.github.wulkanowy.utils.schedulers.SchedulersManager
import java.lang.String.format
import java.util.Locale.FRANCE
import javax.inject.Inject
class GradeSummaryPresenter @Inject constructor(
private val errorHandler: ErrorHandler,
private val gradeSummaryRepository: GradeSummaryRepository,
private val gradeRepository: GradeRepository,
private val sessionRepository: SessionRepository,
private val schedulers: SchedulersManager)
: BasePresenter<GradeSummaryView>(errorHandler) {
override fun attachView(view: GradeSummaryView) {
super.attachView(view)
view.initView()
}
fun loadData(semesterId: String, forceRefresh: Boolean) {
disposable.add(sessionRepository.getSemesters()
.map { semester -> semester.first { it.semesterId == semesterId } }
.flatMap {
gradeSummaryRepository.getGradesSummary(it, forceRefresh)
.flatMap { gradesSummary ->
gradeRepository.getGrades(it, forceRefresh)
.map { grades ->
grades.groupBy { grade -> grade.subject }
.mapValues { entry -> entry.value.calcAverage() }
.filterValues { value -> value != 0.0 }
.let { averages ->
createGradeSummaryItems(gradesSummary, averages) to
GradeSummaryScrollableHeader(
formatAverage(gradesSummary.calcAverage()),
formatAverage(averages.values.average())
)
}
}
}
}
.subscribeOn(schedulers.backgroundThread())
.observeOn(schedulers.mainThread())
.doFinally {
view?.run {
showRefresh(false)
showProgress(false)
notifyParentDataLoaded(semesterId)
}
}.subscribe({
view?.run {
showEmpty(it.first.isEmpty())
showContent(it.first.isNotEmpty())
updateDataSet(it.first, it.second)
}
}) {
view?.run { showEmpty(isViewEmpty()) }
errorHandler.proceed(it)
})
}
fun onSwipeRefresh() {
view?.notifyParentRefresh()
}
fun onParentViewReselected() {
view?.run {
if (!isViewEmpty()) resetView()
}
}
fun onParentChangeSemester() {
view?.run {
showProgress(true)
showRefresh(false)
showContent(false)
showEmpty(false)
clearView()
}
disposable.clear()
}
private fun createGradeSummaryItems(gradesSummary: List<GradeSummary>, averages: Map<String, Double>)
: List<GradeSummaryItem> {
return gradesSummary.filter { !checkEmpty(it, averages) }
.flatMap { gradeSummary ->
GradeSummaryHeader(
name = gradeSummary.subject,
average = formatAverage(averages.getOrElse(gradeSummary.subject) { 0.0 }, "")
).let {
listOf(GradeSummaryItem(
header = it,
title = view?.predictedString().orEmpty(),
grade = gradeSummary.predictedGrade
), GradeSummaryItem(
header = it,
title = view?.finalString().orEmpty(),
grade = gradeSummary.finalGrade
))
}
}
}
private fun checkEmpty(gradeSummary: GradeSummary, averages: Map<String, Double>): Boolean {
return gradeSummary.run {
finalGrade.isEmpty() && predictedGrade.isEmpty() && averages[subject] == null
}
}
private fun formatAverage(average: Double, defaultValue: String = "-- --"): String {
return if (average == 0.0 || average.isNaN()) defaultValue
else format(FRANCE, "%.2f", average)
}
}

View File

@ -0,0 +1,53 @@
package io.github.wulkanowy.ui.main.grade.summary
import android.view.View
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
import io.github.wulkanowy.R
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.scrollable_header_grade_summary.*
class GradeSummaryScrollableHeader(private val finalAverage: String, private val calculatedAverage: String)
: AbstractFlexibleItem<GradeSummaryScrollableHeader.ViewHolder>() {
override fun getLayoutRes() = R.layout.scrollable_header_grade_summary
override fun createViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?): ViewHolder {
return ViewHolder(view, adapter)
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>?, holder: ViewHolder?,
position: Int, payloads: MutableList<Any>?) {
holder?.apply {
gradeSummaryScrollableHeaderFinal.text = finalAverage
gradeSummaryScrollableHeaderCalculated.text = calculatedAverage
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as GradeSummaryScrollableHeader
if (calculatedAverage != other.calculatedAverage) return false
if (finalAverage != other.finalAverage) return false
return true
}
override fun hashCode(): Int {
var result = calculatedAverage.hashCode()
result = 31 * result + finalAverage.hashCode()
return result
}
class ViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?) : FlexibleViewHolder(view, adapter),
LayoutContainer {
override val containerView: View?
get() = contentView
}
}

View File

@ -0,0 +1,32 @@
package io.github.wulkanowy.ui.main.grade.summary
import io.github.wulkanowy.ui.base.BaseView
interface GradeSummaryView : BaseView {
fun initView()
fun updateDataSet(data: List<GradeSummaryItem>, header: GradeSummaryScrollableHeader)
fun resetView()
fun clearView()
fun isViewEmpty(): Boolean
fun showProgress(show: Boolean)
fun showRefresh(show: Boolean)
fun showContent(show: Boolean)
fun showEmpty(show: Boolean)
fun predictedString(): String
fun finalString(): String
fun notifyParentDataLoaded(semesterId: String)
fun notifyParentRefresh()
}

View File

@ -1,4 +1,4 @@
package io.github.wulkanowy.utils.extension
package io.github.wulkanowy.utils
import android.app.Activity
import android.content.Context.INPUT_METHOD_SERVICE

View File

@ -1,40 +0,0 @@
package io.github.wulkanowy.utils;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.view.View;
public final class AnimationUtils {
public static void slideDown(final View view) {
view.setVisibility(View.VISIBLE);
view.setAlpha(0.f);
view.setTranslationY(-(view.getHeight() / 2));
view.animate()
.translationY(0)
.alpha(1.f)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
view.setVisibility(View.VISIBLE);
view.setAlpha(1.f);
}
});
}
public static void slideUp(final View view) {
view.animate()
.translationY(-(view.getHeight() / 2))
.alpha(0.f)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// superfluous restoration
view.setVisibility(View.GONE);
view.setAlpha(1.f);
view.setTranslationY(0.f);
}
});
}
}

View File

@ -1,11 +0,0 @@
package io.github.wulkanowy.utils
const val APP_NAME = "Wulkanowy"
const val DATABASE_NAME = "wulkanowy_db"
const val DEFAULT_SYMBOL = "Default"
const val DATE_PATTERN = "yyyy-MM-dd"
const val REPO_URL = "https://github.com/wulkanowy/wulkanowy"

View File

@ -1,44 +0,0 @@
package io.github.wulkanowy.utils;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.annotation.AttrRes;
import android.support.annotation.ColorInt;
import io.github.wulkanowy.R;
public final class CommonUtils {
private CommonUtils() {
throw new IllegalStateException("Utility class");
}
public static int colorHexToColorName(String hexColor) {
switch (hexColor) {
case "000000":
return R.string.all_black;
case "F04C4C":
return R.string.all_red;
case "20A4F7":
return R.string.all_blue;
case "6ECD07":
return R.string.all_green;
default:
return R.string.all_empty_color;
}
}
@ColorInt
public static int getThemeAttrColor(Context context, @AttrRes int colorAttr) {
final TypedArray array = context.obtainStyledAttributes(null, new int[]{colorAttr});
try {
return array.getColor(0, 0);
} finally {
array.recycle();
}
}
}

View File

@ -1,37 +0,0 @@
package io.github.wulkanowy.utils;
import com.crashlytics.android.answers.Answers;
import com.crashlytics.android.answers.CustomEvent;
import com.crashlytics.android.answers.LoginEvent;
import com.crashlytics.android.answers.SignUpEvent;
public final class FabricUtils {
private FabricUtils() {
throw new IllegalStateException("Utility class");
}
public static void logLogin(String method, boolean result) {
Answers.getInstance().logLogin(new LoginEvent()
.putMethod(method)
.putSuccess(result)
);
}
public static void logRegister(boolean result, String symbol, String message) {
Answers.getInstance().logSignUp(new SignUpEvent()
.putMethod("Login activity")
.putSuccess(result)
.putCustomAttribute("symbol", symbol)
.putCustomAttribute("message", message)
);
}
public static void logRefresh(String name, boolean result, String date) {
Answers.getInstance().logCustom(
new CustomEvent(name + " refresh")
.putCustomAttribute("Success", result ? "true" : "false")
.putCustomAttribute("Date", date)
);
}
}

View File

@ -0,0 +1,10 @@
package io.github.wulkanowy.utils
import eu.davidea.flexibleadapter.FlexibleAdapter
inline fun FlexibleAdapter<*>.setOnItemClickListener(crossinline listener: (position: Int) -> Unit) {
addListener(FlexibleAdapter.OnItemClickListener { _, position ->
listener(position)
true
})
}

View File

@ -0,0 +1,13 @@
package io.github.wulkanowy.utils
import android.support.v4.app.Fragment
import com.ncapdevi.fragnav.FragNavController
inline fun FragNavController.setOnTabTransactionListener(crossinline listener: (index: Int) -> Unit) {
transactionListener = object : FragNavController.TransactionListener {
override fun onFragmentTransaction(fragment: Fragment?, transactionType: FragNavController.TransactionType) {}
override fun onTabTransaction(fragment: Fragment?, index: Int) {
listener(index)
}
}
}

View File

@ -0,0 +1,48 @@
package io.github.wulkanowy.utils
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.GradeSummary
fun List<Grade>.calcAverage(): Double {
var counter = 0.0
var denominator = 0.0
forEach {
counter += (it.value + it.modifier) * it.weightValue
denominator += it.weightValue
}
return if (denominator != 0.0) counter / denominator else 0.0
}
@JvmName("calcSummaryAverage")
fun List<GradeSummary>.calcAverage(): Double {
return asSequence().mapNotNull {
if (it.finalGrade.matches("[0-6]".toRegex())) it.finalGrade.toDouble() else null
}.average()
}
inline val Grade.valueColor: Int
get() {
return when (value) {
6 -> R.color.grade_six
5 -> R.color.grade_five
4 -> R.color.grade_four
3 -> R.color.grade_three
2 -> R.color.grade_two
1 -> R.color.grade_one
else -> R.color.grade_default
}
}
inline val Grade.colorStringId: Int
get() {
return when (color) {
"000000" -> R.string.all_black
"F04C4C" -> R.string.all_red
"20A4F7" -> R.string.all_blue
"6ECD07" -> R.string.all_green
else -> R.string.all_empty_color
}
}

View File

@ -1,166 +0,0 @@
package io.github.wulkanowy.utils;
import java.util.regex.Pattern;
public final class GradeUtils {
private final static Pattern validGradePattern = Pattern.compile("^(\\++|-|--|=)?[0-6](\\++|-|--|=)?$");
private final static Pattern simpleGradeValuePattern = Pattern.compile("([0-6])");
private GradeUtils() {
throw new IllegalStateException("Utility class");
}
/*public static float calculateWeightedAverage(List<Grade> gradeList) {
float counter = 0f;
float denominator = 0f;
for (Grade grade : gradeList) {
int weight = getWeightValue(grade.getWeight());
float value = getWeightedGradeValue(grade.getValue());
if (value != -1.0f) {
counter += value * weight;
denominator += weight;
}
}
if (counter == 0f) {
return -1.0f;
}
return counter / denominator;
}
public static float calculateSubjectsAverage(List<Subject> subjectList, boolean usePredicted) {
return calculateSubjectsAverage(subjectList, usePredicted, false);
}
public static float calculateDetailedSubjectsAverage(List<Subject> subjectList) {
return calculateSubjectsAverage(subjectList, false, true);
}
public static int getValueColor(String value) {
Matcher m1 = validGradePattern.matcher(value);
if (!m1.find()) {
return R.color.grade_default;
}
Matcher m2 = simpleGradeValuePattern.matcher(m1.group());
if (!m2.find()) {
return R.color.grade_default;
}
switch (Integer.parseInt(m2.group())) {
case 6:
return R.color.grade_six;
case 5:
return R.color.grade_five;
case 4:
return R.color.grade_four;
case 3:
return R.color.grade_three;
case 2:
return R.color.grade_two;
case 1:
return R.color.grade_one;
default:
return R.color.grade_default;
}
}
private static float calculateSubjectsAverage(List<Subject> subjectList, boolean usePredicted, boolean useSubjectsAverages) {
float counter = 0f;
float denominator = 0f;
for (Subject subject : subjectList) {
float value;
if (useSubjectsAverages) {
value = calculateWeightedAverage(subject.getGradeList());
} else {
value = getGradeValue(usePredicted ? subject.getPredictedRating() : subject.getFinalRating());
}
if (value != -1.0f) {
counter += Math.round(value);
denominator++;
}
}
if (counter == 0) {
return -1.0f;
}
return counter / denominator;
}
public static float getGradeValue(String grade) {
if (validGradePattern.matcher(grade).matches()) {
return getWeightedGradeValue(grade);
}
return getVerbalGradeValue(grade);
}
private static float getVerbalGradeValue(String grade) {
switch (grade) {
case "celujący":
return 6f;
case "bardzo dobry":
return 5f;
case "dobry":
return 4f;
case "dostateczny":
return 3f;
case "dopuszczający":
return 2f;
case "niedostateczny":
return 1f;
default:
return -1f;
}
}
public static String getShortGradeValue(String grade) {
switch (grade) {
case "celujący":
return "6";
case "bardzo dobry":
return "5";
case "dobry":
return "4";
case "dostateczny":
return "3";
case "dopuszczający":
return "2";
case "niedostateczny":
return "1";
default:
return grade;
}
}
private static float getWeightedGradeValue(String value) {
if (validGradePattern.matcher(value).matches()) {
if (value.matches("[-][0-6]") || value.matches("[0-6][-]")) {
String replacedValue = value.replaceAll("[-]", "");
return Float.valueOf(replacedValue) - 0.33f;
} else if (value.matches("[+][0-6]") || value.matches("[0-6][+]")) {
String replacedValue = value.replaceAll("[+]", "");
return Float.valueOf((replacedValue)) + 0.33f;
} else if (value.matches("[-|=]{1,2}[0-6]") || value.matches("[0-6][-|=]{1,2}")) {
String replacedValue = value.replaceAll("[-|=]{1,2}", "");
return Float.valueOf((replacedValue)) - 0.5f;
} else {
return Float.valueOf(value);
}
} else {
return -1;
}
}
private static int getWeightValue(String weightOfGrade) {
return Integer.valueOf(weightOfGrade.substring(0, weightOfGrade.length() - 3));
}*/
}

View File

@ -1,47 +0,0 @@
package io.github.wulkanowy.utils;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import com.crashlytics.android.Crashlytics;
import timber.log.Timber;
public final class LoggerUtils {
public static class CrashlyticsTree extends Timber.Tree {
@Override
protected void log(int priority, @Nullable String tag, @Nullable String message, @Nullable Throwable t) {
Crashlytics.setInt("priority", priority);
Crashlytics.setString("tag", tag);
if (t == null) {
Crashlytics.log(message);
} else {
Crashlytics.setString("message", message);
Crashlytics.logException(t);
}
}
}
public static class DebugLogTree extends Timber.DebugTree {
@Override
protected void log(int priority, String tag, @NonNull String message, Throwable t) {
if ("HUAWEI".equals(Build.MANUFACTURER) || "samsung".equals(Build.MANUFACTURER)) {
if (priority == Log.VERBOSE || priority == Log.DEBUG || priority == Log.INFO) {
priority = Log.ERROR;
}
}
super.log(priority, AppConstantKt.APP_NAME, message, t);
}
@Override
protected String createStackElementTag(@NonNull StackTraceElement element) {
return super.createStackElementTag(element) + " - " + element.getLineNumber();
}
}
}

View File

@ -0,0 +1,27 @@
package io.github.wulkanowy.utils
import com.crashlytics.android.Crashlytics
import timber.log.Timber
object CrashlyticsTree : Timber.Tree() {
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
Crashlytics.setInt("priority", priority)
Crashlytics.setString("tag", tag)
if (t == null) {
Crashlytics.log(message)
} else {
Crashlytics.setString("message", message)
Crashlytics.logException(t)
}
}
}
object DebugLogTree : Timber.DebugTree() {
override fun createStackElementTag(element: StackTraceElement): String? {
return super.createStackElementTag(element) + " - ${element.lineNumber}"
}
}

View File

@ -0,0 +1,87 @@
package io.github.wulkanowy.utils
import org.threeten.bp.DayOfWeek.*
import org.threeten.bp.LocalDate
import org.threeten.bp.format.DateTimeFormatter
import org.threeten.bp.format.DateTimeFormatter.ofPattern
import org.threeten.bp.temporal.TemporalAdjusters
import org.threeten.bp.temporal.TemporalAdjusters.*
import java.text.SimpleDateFormat
import java.util.*
private const val DATE_PATTERN = "yyyy-MM-dd"
fun Date.toLocalDate(): LocalDate {
return LocalDate.parse(SimpleDateFormat(DATE_PATTERN, Locale.getDefault()).format(this))
}
fun String.toLocalDate(format: String = DATE_PATTERN): LocalDate {
return LocalDate.parse(this, DateTimeFormatter.ofPattern(format))
}
fun LocalDate.toFormattedString(format: String): String = this.format(ofPattern(format))
fun LocalDate.toFormattedString(): String = this.toFormattedString(DATE_PATTERN)
inline val LocalDate.nextWorkDay: LocalDate
get() {
return when (this.dayOfWeek) {
FRIDAY, SATURDAY, SUNDAY -> this.with(next(MONDAY))
else -> this.plusDays(1)
}
}
inline val LocalDate.previousWorkDay: LocalDate
get() {
return when (this.dayOfWeek) {
SATURDAY, SUNDAY, MONDAY -> this.with(previous(FRIDAY))
else -> this.minusDays(1)
}
}
inline val LocalDate.nearSchoolDayPrevOnWeekEnd: LocalDate
get() {
return when (this.dayOfWeek) {
SATURDAY, SUNDAY -> this.with(previous(FRIDAY))
else -> this
}
}
inline val LocalDate.nearSchoolDayNextOnWeekEnd: LocalDate
get() {
return when (this.dayOfWeek) {
SATURDAY, SUNDAY -> this.with(next(MONDAY))
else -> this
}
}
inline val LocalDate.weekDayName: String
get() = this.format(ofPattern("EEEE", Locale.getDefault()))
inline val LocalDate.weekFirstDayAlwaysCurrent: LocalDate
get() = this.with(TemporalAdjusters.previousOrSame(MONDAY))
inline val LocalDate.weekFirstDayNextOnWeekEnd: LocalDate
get() {
return when (this.dayOfWeek) {
SATURDAY, SUNDAY -> this.with(next(MONDAY))
else -> this.with(previousOrSame(MONDAY))
}
}
/**
* [Dz.U. 2016 poz. 1335](http://prawo.sejm.gov.pl/isap.nsf/DocDetails.xsp?id=WDU20160001335)
*/
inline val LocalDate.isHolidays: Boolean
get() {
return LocalDate.of(this.year, 9, 1).run {
when (dayOfWeek) {
FRIDAY, SATURDAY, SUNDAY -> with(firstInMonth(MONDAY))
else -> this
}
}.let { firstSchoolDay ->
LocalDate.of(this.year, 6, 20)
.with(next(FRIDAY))
.let { lastSchoolDay -> this.isBefore(firstSchoolDay) && this.isAfter(lastSchoolDay) }
}
}

View File

@ -1,13 +1,12 @@
package io.github.wulkanowy.utils.extension
package io.github.wulkanowy.utils
import android.support.v4.view.ViewPager
fun ViewPager.setOnSelectPageListener(selectListener: (position: Int) -> Unit) {
inline fun ViewPager.setOnSelectPageListener(crossinline selectListener: (position: Int) -> Unit) {
addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageSelected(position: Int) {
selectListener(position)
}
override fun onPageScrollStateChanged(state: Int) {}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}
})

View File

@ -1,15 +0,0 @@
package io.github.wulkanowy.utils.extension
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
fun FlexibleAdapter<*>.setOnItemClickListener(listener: (position: Int) -> Unit) {
addListener(FlexibleAdapter.OnItemClickListener { _, position ->
listener(position)
true
})
}
fun FlexibleAdapter<*>.setOnUpdateListener(listener: (size: Int) -> Unit) {
addListener(FlexibleAdapter.OnUpdateListener { listener(it) })
}

View File

@ -1,79 +0,0 @@
package io.github.wulkanowy.utils.extension
import io.github.wulkanowy.utils.DATE_PATTERN
import org.threeten.bp.*
import org.threeten.bp.format.DateTimeFormatter
import org.threeten.bp.temporal.TemporalAdjusters
import java.text.SimpleDateFormat
import java.util.*
fun Date.toLocalDate(): LocalDate = LocalDate.parse(SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(this))
fun String.toDate(format: String = "yyyy-MM-dd"): LocalDate = LocalDate.parse(this, DateTimeFormatter.ofPattern(format))
fun LocalDate.toFormat(format: String): String = this.format(DateTimeFormatter.ofPattern(format))
fun LocalDate.toFormat(): String = this.toFormat(DATE_PATTERN)
fun LocalDate.getNextWorkDay(): LocalDate {
return when(this.dayOfWeek) {
DayOfWeek.FRIDAY, DayOfWeek.SATURDAY, DayOfWeek.SUNDAY -> this.with(TemporalAdjusters.next(DayOfWeek.MONDAY))
else -> this.plusDays(1)
}
}
fun LocalDate.getPreviousWorkDay(): LocalDate {
return when(this.dayOfWeek) {
DayOfWeek.SATURDAY, DayOfWeek.SUNDAY, DayOfWeek.MONDAY -> this.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY))
else -> this.minusDays(1)
}
}
fun LocalDate.getNearSchoolDayPrevOnWeekEnd(): LocalDate {
return when(this.dayOfWeek) {
DayOfWeek.SATURDAY, DayOfWeek.SUNDAY -> this.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY))
else -> this
}
}
fun LocalDate.getNearSchoolDayNextOnWeekEnd(): LocalDate {
return when(this.dayOfWeek) {
DayOfWeek.SATURDAY, DayOfWeek.SUNDAY -> this.with(TemporalAdjusters.next(DayOfWeek.MONDAY))
else -> this
}
}
fun LocalDate.getWeekDayName(): String = this.format(DateTimeFormatter.ofPattern("EEEE", Locale.getDefault()))
fun LocalDate.getWeekFirstDayAlwaysCurrent(): LocalDate {
return this.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY))
}
fun LocalDate.getWeekFirstDayNextOnWeekEnd(): LocalDate {
return when(this.dayOfWeek) {
DayOfWeek.SATURDAY, DayOfWeek.SUNDAY -> this.with(TemporalAdjusters.next(DayOfWeek.MONDAY))
else -> this.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY))
}
}
/**
* [Dz.U. 2016 poz. 1335](http://prawo.sejm.gov.pl/isap.nsf/DocDetails.xsp?id=WDU20160001335)
*/
fun LocalDate.isHolidays(): Boolean = this.isAfter(this.getLastSchoolDay()) && this.isBefore(this.getFirstSchoolDay())
fun LocalDate.getSchoolYear(): Int = if (this.monthValue <= 8) this.year - 1 else this.year
fun LocalDate.getFirstSchoolDay(): LocalDate {
return LocalDate.of(this.year, 9, 1).run {
when (dayOfWeek) {
DayOfWeek.FRIDAY, DayOfWeek.SATURDAY, DayOfWeek.SUNDAY -> with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY))
else -> this
}
}
}
fun LocalDate.getLastSchoolDay(): LocalDate {
return LocalDate
.of(this.year, 6, 20)
.with(TemporalAdjusters.next(DayOfWeek.FRIDAY))
}

View File

@ -12,7 +12,6 @@ import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties.*
import android.util.Base64
import android.util.Base64.DEFAULT
import org.apache.commons.lang3.StringUtils
import timber.log.Timber
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
@ -76,7 +75,7 @@ object Scrambler {
@JvmStatic
fun decrypt(cipherText: String): String {
if (StringUtils.isEmpty(cipherText)) throw ScramblerException("Text to be encrypted is empty")
if (cipherText.isEmpty()) throw ScramblerException("Text to be encrypted is empty")
if (SDK_INT < JELLY_BEAN_MR2 || cipherText.length < 250) {
return String(Base64.decode(cipherText.toByteArray(KEY_CHARSET), DEFAULT), KEY_CHARSET)

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -1,5 +1,9 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
android:opacity="opaque">
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<solid android:color="#FFF" />
</shape>
</item>
<item>
<bitmap
android:gravity="left|right|top|bottom"

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -0,0 +1,12 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<solid android:color="#FFF" />
</shape>
</item>
<item
android:width="200dp"
android:height="200dp"
android:gravity="center"
android:drawable="@drawable/img_splash_logo" />
</layer-list>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -1,19 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/mainContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="@+id/mainAppBarContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:elevation="0dp">
<android.support.v7.widget.Toolbar
android:id="@+id/mainToolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" />
</android.support.design.widget.AppBarLayout>
<FrameLayout
android:id="@+id/mainFragmentContainer"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
android:layout_height="match_parent"
android:layout_marginBottom="@dimen/bottom_navigation_height"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<com.aurelhubert.ahbottomnavigation.AHBottomNavigation
android:id="@+id/mainBottomNav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom" />
</LinearLayout>
</android.support.design.widget.CoordinatorLayout>

View File

@ -5,45 +5,42 @@
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="300dp"
android:orientation="vertical">
android:minWidth="300dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:padding="20dp"
tools:ignore="UselessParent">
<TextView
android:id="@+id/grade_dialog_value"
android:id="@+id/gradeDialogValue"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_gravity="end"
android:background="@color/grade_default"
android:gravity="center"
android:text="@string/app_name"
android:textColor="@color/grade_text"
android:background="@color/grade_default"
android:textSize="30sp" />
<TextView
android:id="@+id/grade_dialog_subject"
android:id="@+id/gradeDialogSubject"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_gravity="start"
android:layout_marginBottom="10dp"
android:layout_marginEnd="90dp"
android:layout_marginRight="90dp"
android:layout_marginBottom="10dp"
android:gravity="center_vertical"
android:maxLines="5"
android:minHeight="80dp"
@ -53,134 +50,157 @@
android:textSize="20sp" />
<TextView
android:id="@+id/grade_dialog_description"
android:id="@+id/gradeDialogDescription"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/gradeDialogSubject"
android:layout_alignParentStart="true"
android:layout_below="@+id/grade_dialog_subject"
android:layout_alignParentLeft="true"
android:text="@string/all_description"
android:textIsSelectable="true"
android:textSize="17sp" />
<TextView
android:id="@+id/grade_dialog_description_value"
android:id="@+id/gradeDialogDescriptionValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/gradeDialogDescription"
android:layout_alignParentStart="true"
android:layout_below="@+id/grade_dialog_description"
android:layout_alignParentLeft="true"
android:layout_marginTop="3dp"
android:text="@string/all_no_description"
android:textIsSelectable="true"
android:textSize="12sp" />
<TextView
android:id="@+id/grade_dialog_weight"
android:id="@+id/gradeDialogWeight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/gradeDialogDescriptionValue"
android:layout_alignParentStart="true"
android:layout_below="@+id/grade_dialog_description_value"
android:layout_alignParentLeft="true"
android:layout_marginTop="10dp"
android:text="@string/grade_weight"
android:textSize="17sp" />
<TextView
android:id="@+id/grade_dialog_weight_value"
android:id="@+id/gradeDialogWeightValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/gradeDialogWeight"
android:layout_alignParentStart="true"
android:layout_below="@+id/grade_dialog_weight"
android:layout_alignParentLeft="true"
android:layout_marginTop="3dp"
android:text="@string/grade_weight"
android:textIsSelectable="true"
android:textSize="12sp" />
<TextView
android:id="@+id/grade_dialog_teacher"
android:id="@+id/gradeDialogComment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/gradeDialogWeightValue"
android:layout_alignParentStart="true"
android:layout_below="@+id/grade_dialog_weight_value"
android:layout_alignParentLeft="true"
android:layout_marginTop="10dp"
android:text="@string/grade_comment"
android:textSize="17sp" />
<TextView
android:id="@+id/gradeDialogCommentValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/gradeDialogComment"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_marginTop="3dp"
android:text="@string/grade_comment"
android:textIsSelectable="true"
android:textSize="12sp" />
<TextView
android:id="@+id/gradeDialogTeacher"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/gradeDialogCommentValue"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_marginTop="10dp"
android:text="@string/all_teacher"
android:textSize="17sp" />
<TextView
android:id="@+id/grade_dialog_teacher_value"
android:id="@+id/gradeDialogTeacherValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/gradeDialogTeacher"
android:layout_alignParentStart="true"
android:layout_below="@+id/grade_dialog_teacher"
android:layout_alignParentLeft="true"
android:layout_marginTop="3dp"
android:text="@string/all_teacher"
android:textIsSelectable="true"
android:textSize="12sp" />
<TextView
android:id="@+id/grade_dialog_color"
android:id="@+id/gradeDialogColor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/gradeDialogTeacherValue"
android:layout_alignParentStart="true"
android:layout_below="@+id/grade_dialog_teacher_value"
android:layout_alignParentLeft="true"
android:layout_marginTop="10dp"
android:text="@string/all_color"
android:textSize="17sp" />
<TextView
android:id="@+id/grade_dialog_color_value"
android:id="@+id/gradeDialogColorValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/gradeDialogColor"
android:layout_alignParentStart="true"
android:layout_below="@+id/grade_dialog_color"
android:layout_alignParentLeft="true"
android:layout_marginTop="3dp"
android:text="@string/all_color"
android:textIsSelectable="true"
android:textSize="12sp" />
<TextView
android:id="@+id/grade_dialog_date"
android:id="@+id/gradeDialogDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/gradeDialogColorValue"
android:layout_alignParentStart="true"
android:layout_below="@+id/grade_dialog_color_value"
android:layout_alignParentLeft="true"
android:layout_marginTop="10dp"
android:text="@string/all_date"
android:textSize="17sp" />
<TextView
android:id="@+id/grade_dialog_date_value"
android:id="@+id/gradeDialogDateValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/gradeDialogDate"
android:layout_alignParentStart="true"
android:layout_below="@+id/grade_dialog_date"
android:layout_alignParentLeft="true"
android:layout_marginTop="3dp"
android:text="@string/all_date"
android:textIsSelectable="true"
android:textSize="12sp" />
<Button
android:id="@+id/grade_dialog_close_button"
android:id="@+id/gradeDialogClose"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignTop="@+id/gradeDialogDateValue"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignTop="@+id/grade_dialog_date_value"
android:layout_alignParentBottom="true"
android:layout_marginTop="25dp"
android:background="?attr/selectableItemBackground"
android:focusable="true"
android:text="@string/all_close"
android:textColor="?android:attr/android:textColorSecondary"
android:textAllCaps="true"
android:textColor="?android:attr/android:textColorSecondary"
android:textSize="15sp" />
</RelativeLayout>

View File

@ -1,174 +1,33 @@
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/grade_fragment_container"
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
<android.support.design.widget.TabLayout
android:id="@+id/gradeTabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:elevation="5dp"
android:visibility="invisible"
app:tabGravity="fill"
app:tabIndicatorColor="@android:color/white"
app:tabMaxWidth="0dp"
app:tabMode="fixed"
app:tabTextColor="@android:color/white" />
<android.support.v4.view.ViewPager
android:id="@+id/gradeViewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="Grade" />
android:layout_below="@id/gradeTabLayout"
android:visibility="invisible" />
<!--<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/grade_fragment_swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/grade_fragment_details_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/grade_fragment_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
<android.support.v4.widget.NestedScrollView
android:id="@+id/grade_fragment_summary_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="blocksDescendants">
<RelativeLayout
android:id="@+id/grade_fragment_summary_calculated_container"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginStart="10dp"
android:layout_marginTop="20dp"
android:layout_toLeftOf="@id/grade_fragment_summary_final_container"
android:layout_toStartOf="@id/grade_fragment_summary_final_container">
<TextView
android:id="@+id/grade_fragment_summary_calculated_average_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:gravity="center"
android:text="@string/grades_summary_calculated_average"
android:textSize="16sp" />
<TextView
android:id="@+id/grade_fragment_summary_calculated_average"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/grade_fragment_summary_calculated_average_text"
android:layout_centerHorizontal="true"
android:text="6,00"
android:textSize="21sp" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/grade_fragment_summary_final_container"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginEnd="10dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginStart="10dp"
android:layout_marginTop="20dp">
<TextView
android:id="@+id/grade_fragment_summary_final_average_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:gravity="center"
android:minLines="2"
android:text="@string/grades_summary_final_average"
android:textSize="16sp" />
<TextView
android:id="@+id/grade_fragment_summary_final_average"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/grade_fragment_summary_final_average_text"
android:layout_centerHorizontal="true"
android:layout_marginLeft="10dp"
android:layout_marginStart="10dp"
android:text="6,00"
android:textSize="21sp" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/grade_fragment_summary_predicted_container"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:layout_marginRight="10dp"
android:layout_marginTop="20dp"
android:layout_toEndOf="@id/grade_fragment_summary_final_container"
android:layout_toRightOf="@id/grade_fragment_summary_final_container">
<TextView
android:id="@+id/grade_fragment_summary_predicted_average_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:gravity="center"
android:maxLines="2"
android:text="@string/grades_summary_predicted_average"
android:textSize="16sp" />
<TextView
android:id="@+id/grade_fragment_summary_predicted_average"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/grade_fragment_summary_predicted_average_text"
android:layout_centerHorizontal="true"
android:text="6,00"
android:textSize="21sp" />
</RelativeLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/grade_fragment_summary_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/grade_fragment_summary_calculated_container"
android:layout_marginTop="20dp" />
</RelativeLayout>
</android.support.v4.widget.NestedScrollView>
<RelativeLayout
android:id="@+id/grade_fragment_no_item_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<android.support.v7.widget.AppCompatImageView
android:id="@+id/grade_fragment_no_item_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/grade_fragment_no_item_text"
android:layout_centerHorizontal="true"
android:layout_marginTop="40dp"
android:minHeight="100dp"
android:minWidth="100dp"
app:srcCompat="@drawable/ic_menu_main_grade_26dp"
app:tint="?android:attr/textColorPrimary"
tools:ignore="contentDescription" />
<TextView
android:id="@+id/grade_fragment_no_item_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="46dp"
android:gravity="center"
android:text="@string/fragment_no_grades"
android:textSize="20sp" />
</RelativeLayout>
</FrameLayout>
</android.support.v4.widget.SwipeRefreshLayout>-->
</android.support.design.widget.CoordinatorLayout>
<ProgressBar
android:id="@+id/gradeProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:indeterminate="true" />
</RelativeLayout>

View File

@ -0,0 +1,50 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/gradeDetailsSwipe"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/gradeDetailsRecycler"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.v4.widget.SwipeRefreshLayout>
<ProgressBar
android:id="@+id/gradeDetailsProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true" />
<LinearLayout
android:id="@+id/gradeDetailsEmpty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:visibility="invisible">
<android.support.v7.widget.AppCompatImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="100dp"
android:minHeight="100dp"
app:srcCompat="@drawable/ic_menu_main_grade_26dp"
app:tint="?android:attr/textColorPrimary"
tools:ignore="contentDescription" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:gravity="center"
android:text="@string/grade_no_items"
android:textSize="20sp" />
</LinearLayout>
</FrameLayout>

View File

@ -0,0 +1,50 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/gradeSummarySwipe"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/gradeSummaryRecycler"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.v4.widget.SwipeRefreshLayout>
<ProgressBar
android:id="@+id/gradeSummaryProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true" />
<LinearLayout
android:id="@+id/gradeSummaryEmpty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:visibility="invisible">
<android.support.v7.widget.AppCompatImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="100dp"
android:minHeight="100dp"
app:srcCompat="@drawable/ic_menu_main_grade_26dp"
app:tint="?android:attr/textColorPrimary"
tools:ignore="contentDescription" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:gravity="center"
android:text="@string/grade_no_items"
android:textSize="20sp" />
</LinearLayout>
</FrameLayout>

View File

@ -68,7 +68,7 @@
android:layout_marginBottom="15dp"
android:hint="@string/login_email_hint">
<EditText
<android.support.design.widget.TextInputEditText
android:id="@+id/loginEmailEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -82,7 +82,7 @@
android:layout_height="wrap_content"
android:hint="@string/login_password_hint">
<EditText
<android.support.design.widget.TextInputEditText
android:id="@+id/loginPassEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@ -1,4 +1,4 @@
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/timetable_fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -9,22 +9,4 @@
android:layout_height="match_parent"
android:gravity="center"
android:text="Timetable" />
<!--<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.TabLayout
android:id="@+id/timetable_fragment_tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMinWidth="125dp"
app:tabMode="scrollable"/>
<android.support.v4.view.ViewPager
android:id="@+id/timetable_fragment_viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/timetable_fragment_tab_layout" />
</RelativeLayout>-->
</android.support.design.widget.CoordinatorLayout>
</FrameLayout>

View File

@ -9,10 +9,10 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin">
android:paddingBottom="16dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="16dp">
<TextView
android:id="@+id/attendance_header_day"

View File

@ -1,56 +1,57 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tool="http://schemas.android.com/tools"
android:id="@+id/grade_header_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/ic_all_divider"
android:foreground="?attr/selectableItemBackgroundBorderless"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin">
android:paddingBottom="10dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="10dp">
<TextView
android:id="@+id/grade_header_subject_text"
android:layout_width="wrap_content"
android:id="@+id/gradeHeaderSubject"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="35dp"
android:layout_marginRight="35dp"
android:layout_marginEnd="20dp"
android:layout_marginRight="20dp"
android:layout_toLeftOf="@id/gradeHeaderNote"
android:layout_toStartOf="@id/gradeHeaderNote"
android:ellipsize="end"
android:maxLines="1"
android:text="@string/app_name"
android:textSize="17sp" />
android:textSize="15sp" />
<TextView
android:id="@+id/grade_header_average_text"
android:id="@+id/gradeHeaderAverage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/grade_header_subject_text"
android:layout_below="@+id/gradeHeaderSubject"
android:layout_marginTop="5dp"
android:text="@string/app_name"
android:textColor="?android:attr/android:textColorSecondary"
android:textSize="12sp" />
<TextView
android:id="@+id/grade_header_number_of_grade_text"
android:id="@+id/gradeHeaderNumber"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/grade_header_subject_text"
android:layout_below="@id/gradeHeaderSubject"
android:layout_marginLeft="10dp"
android:layout_marginStart="10dp"
android:layout_marginTop="5dp"
android:layout_toEndOf="@+id/grade_header_average_text"
android:layout_toRightOf="@+id/grade_header_average_text"
android:layout_toEndOf="@+id/gradeHeaderAverage"
android:layout_toRightOf="@+id/gradeHeaderAverage"
android:text="@string/app_name"
android:textColor="?android:attr/android:textColorSecondary"
android:textSize="12sp" />
<TextView
android:id="@+id/grade_header_predicted_rating_text"
android:id="@+id/gradeHeaderPredicted"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/grade_header_average_text"
android:layout_below="@id/gradeHeaderAverage"
android:layout_marginEnd="10dp"
android:layout_marginRight="10dp"
android:layout_marginTop="5dp"
@ -59,19 +60,19 @@
android:textSize="12sp" />
<TextView
android:id="@+id/grade_header_final_rating_text"
android:id="@+id/gradeHeaderFinal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/grade_header_average_text"
android:layout_below="@id/gradeHeaderAverage"
android:layout_marginTop="5dp"
android:layout_toEndOf="@+id/grade_header_predicted_rating_text"
android:layout_toRightOf="@+id/grade_header_predicted_rating_text"
android:layout_toEndOf="@+id/gradeHeaderPredicted"
android:layout_toRightOf="@+id/gradeHeaderPredicted"
android:text="@string/grade_final"
android:textColor="?android:attr/android:textColorSecondary"
android:textSize="12sp" />
<ImageView
android:id="@+id/grade_header_alert_image"
android:id="@+id/gradeHeaderNote"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"

View File

@ -9,19 +9,19 @@
android:paddingTop="7dp">
<TextView
android:id="@+id/grades_summary_header_name"
android:id="@+id/gradeSummaryHeaderName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginEnd="40dp"
android:layout_marginRight="40dp"
android:layout_toLeftOf="@id/grades_summary_header_average"
android:layout_toStartOf="@id/grades_summary_header_average"
android:layout_toLeftOf="@id/gradeSummaryHeaderAverage"
android:layout_toStartOf="@id/gradeSummaryHeaderAverage"
android:text="@string/app_name"
android:textSize="17sp" />
<TextView
android:id="@+id/grades_summary_header_average"
android:id="@+id/gradeSummaryHeaderAverage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"

View File

@ -9,10 +9,10 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin">
android:paddingBottom="16dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="16dp">
<TextView
android:id="@+id/timetable_header_day"

View File

@ -1,88 +0,0 @@
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tool="http://schemas.android.com/tools"
android:id="@+id/grade_subitem_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:layout_marginEnd="5dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_marginStart="5dp"
android:foreground="?attr/selectableItemBackgroundBorderless"
card_view:cardElevation="0dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="7dp"
android:layout_marginEnd="7dp"
android:layout_marginLeft="7dp"
android:layout_marginRight="7dp"
android:layout_marginStart="7dp"
android:layout_marginTop="7dp">
<TextView
android:id="@+id/grade_subitem_value"
android:layout_width="45dp"
android:layout_height="45dp"
android:layout_centerVertical="true"
android:gravity="center"
android:maxLength="5"
android:text="@string/app_name"
android:textColor="@color/grade_text"
android:background="@color/grade_default"
android:textSize="16sp" />
<TextView
android:id="@+id/grade_subitem_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginEnd="40dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="40dp"
android:layout_marginStart="10dp"
android:layout_toEndOf="@+id/grade_subitem_value"
android:layout_toRightOf="@+id/grade_subitem_value"
android:ellipsize="end"
android:maxLines="1"
android:text="@string/all_description"
android:textSize="15sp"
tool:ignore="all" />
<TextView
android:id="@+id/grade_subitem_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/grade_subitem_value"
android:layout_alignLeft="@+id/grade_subitem_description"
android:layout_alignStart="@+id/grade_subitem_description"
android:text="@string/all_date"
android:textSize="12sp" />
<TextView
android:id="@+id/grade_subitem_weight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@+id/grade_subitem_date"
android:layout_toRightOf="@+id/grade_subitem_date"
android:layout_alignBottom="@+id/grade_subitem_value"
android:layout_marginLeft="10dp"
android:layout_marginStart="10dp"
android:text="@string/grade_weight"
android:textSize="12sp" />
<ImageView
android:id="@+id/grade_subitem_alert_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginTop="10dp"
app:srcCompat="@drawable/ic_all_round_note_24dp"
tool:ignore="contentDescription" />
</RelativeLayout>
</android.support.v7.widget.CardView>

Some files were not shown because too many files have changed in this diff Show More