1
0
mirror of https://github.com/wulkanowy/wulkanowy.git synced 2024-09-20 01:29:09 -05:00

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: 'com.android.application'
apply plugin: 'kotlin-android' 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: 'kotlin-android-extensions'
apply plugin: 'io.fabric' apply plugin: 'io.fabric'
apply plugin: 'com.github.triplet.play'
apply from: 'jacoco.gradle' apply from: 'jacoco.gradle'
apply from: 'sonarqube.gradle' apply from: 'sonarqube.gradle'
apply plugin: 'com.github.triplet.play'
android { android {
compileSdkVersion 28 compileSdkVersion 28
buildToolsVersion '28.0.2' buildToolsVersion '28.0.3'
playAccountConfigs { playAccountConfigs {
defaultAccountConfig { defaultAccountConfig {
@ -75,13 +75,15 @@ dependencies {
implementation('com.github.wulkanowy:api:07201a4') { implementation('com.github.wulkanowy:api:07201a4') {
exclude module: "threetenbp" exclude module: "threetenbp"
} }
implementation "com.android.support:support-v4:$supportVersion" implementation "com.android.support:support-v4:$supportVersion"
implementation "com.android.support:appcompat-v7:$supportVersion"
implementation "com.android.support:design:$supportVersion" implementation "com.android.support:design:$supportVersion"
implementation "com.android.support:cardview-v7:$supportVersion" implementation "com.android.support:cardview-v7:$supportVersion"
implementation "com.android.support:preference-v14:$supportVersion" implementation "com.android.support:preference-v14:$supportVersion"
implementation 'com.android.support:multidex:1.0.3' 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.firebase:firebase-jobdispatcher:0.8.5"
implementation "com.google.dagger:dagger-android-support:2.17" implementation "com.google.dagger:dagger-android-support:2.17"
@ -92,7 +94,7 @@ dependencies {
implementation "android.arch.persistence.room:rxjava2:1.1.1" implementation "android.arch.persistence.room:rxjava2:1.1.1"
kapt "android.arch.persistence.room:compiler: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 "eu.davidea:flexible-adapter-ui:1.0.0-b5"
implementation "com.aurelhubert:ahbottomnavigation:2.2.0" implementation "com.aurelhubert:ahbottomnavigation:2.2.0"
implementation 'com.ncapdevi:frag-nav:3.0.0-RC3' 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:rxandroid:2.0.2'
implementation "io.reactivex.rxjava2:rxjava:2.2.1" 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.threetenabp:threetenabp:1.1.0"
implementation "com.jakewharton.timber:timber:4.7.1" implementation "com.jakewharton.timber:timber:4.7.1"

View File

@ -34,13 +34,13 @@ class AttendanceLocalTest {
@Test @Test
fun saveAndReadTest() { fun saveAndReadTest() {
attendanceLocal.saveAttendance(listOf( attendanceLocal.saveAttendance(listOf(
Attendance(studentId = "1", diaryId = "2", date = LocalDate.of(2018, 9, 10)), Attendance(0, "1", "2", LocalDate.of(2018, 9, 10), 0, "", ""),
Attendance(studentId = "1", diaryId = "2", date = LocalDate.of(2018, 9, 14)), Attendance(0, "1", "2", LocalDate.of(2018, 9, 14), 0, "", ""),
Attendance(studentId = "1", diaryId = "2", date = LocalDate.of(2018, 9, 17)) // in next week Attendance(0, "1", "2", LocalDate.of(2018, 9, 17), 0, "", "")
)) ))
val attendance = attendanceLocal 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, 10),
LocalDate.of(2018, 9, 14) LocalDate.of(2018, 9, 14)
) )

View File

@ -34,13 +34,13 @@ class ExamLocalTest {
@Test @Test
fun saveAndReadTest() { fun saveAndReadTest() {
examLocal.saveExams(listOf( examLocal.saveExams(listOf(
Exam(studentId = "1", diaryId = "2", date = LocalDate.of(2018, 9, 10)), Exam(0, "1", "2", LocalDate.of(2018, 9, 10), LocalDate.now(), "", "", "", "", "", ""),
Exam(studentId = "1", diaryId = "2", date = LocalDate.of(2018, 9, 14)), Exam(0, "1", "2", LocalDate.of(2018, 9, 14), LocalDate.now(), "", "", "", "", "", ""),
Exam(studentId = "1", diaryId = "2", date = LocalDate.of(2018, 9, 17)) // in next week Exam(0, "1", "2", LocalDate.of(2018, 9, 17), LocalDate.now(), "", "", "", "", "", "")
)) ))
val exams = examLocal 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, 10),
LocalDate.of(2018, 9, 14) LocalDate.of(2018, 9, 14)
) )

View File

@ -16,7 +16,7 @@ import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class SessionLocalTest { class SessionLocalTest {
private lateinit var studentLocal: SessionLocal private lateinit var sessionLocal: SessionLocal
private lateinit var testDb: AppDatabase private lateinit var testDb: AppDatabase
@ -28,7 +28,7 @@ class SessionLocalTest {
testDb = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java) testDb = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java)
.build() .build()
sharedHelper = SharedPrefHelper(context.getSharedPreferences("TEST", Context.MODE_PRIVATE)) 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 @After
@ -38,12 +38,12 @@ class SessionLocalTest {
@Test @Test
fun saveAndReadTest() { 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(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) assertEquals("23", student.schoolId)
} }
} }

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="io.github.wulkanowy" package="io.github.wulkanowy"
android:installLocation="internalOnly"> android:installLocation="internalOnly">
@ -15,13 +14,11 @@
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/WulkanowyTheme" android:theme="@style/WulkanowyTheme"
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true">
tools:targetApi="m">
<activity <activity
android:name=".ui.splash.SplashActivity" android:name=".ui.splash.SplashActivity"
android:configChanges="orientation|screenSize" android:screenOrientation="portrait"
android:noHistory="true" android:theme="@style/WulkanowyTheme.SplashScreen">
android:theme="@style/WulkanowyTheme.SplashTheme">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@ -32,39 +29,13 @@
android:name=".ui.login.LoginActivity" android:name=".ui.login.LoginActivity"
android:configChanges="orientation|screenSize" android:configChanges="orientation|screenSize"
android:label="@string/login_title" android:label="@string/login_title"
android:theme="@style/WulkanowyTheme.DarkActionBar"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<activity <activity
android:name=".ui.main.MainActivity" android:name=".ui.main.MainActivity"
android:configChanges="orientation|screenSize" android:configChanges="orientation|screenSize"
android:label="@string/main_title" android:label="@string/main_title"
android:launchMode="singleTop" /> android:launchMode="singleTop"
<activity android:theme="@style/WulkanowyTheme.NoActionBar" />
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>
<meta-data <meta-data
android:name="io.fabric.ApiKey" android:name="io.fabric.ApiKey"

View File

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

View File

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

View File

@ -1,16 +1,12 @@
package io.github.wulkanowy.data.db package io.github.wulkanowy.data.db
import android.arch.persistence.room.Database import android.arch.persistence.room.Database
import android.arch.persistence.room.Room
import android.arch.persistence.room.RoomDatabase import android.arch.persistence.room.RoomDatabase
import android.arch.persistence.room.TypeConverters import android.arch.persistence.room.TypeConverters
import io.github.wulkanowy.data.db.dao.AttendanceDao import android.content.Context
import io.github.wulkanowy.data.db.dao.ExamDao import io.github.wulkanowy.data.db.dao.*
import io.github.wulkanowy.data.db.dao.SemesterDao import io.github.wulkanowy.data.db.entities.*
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 javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
@ -19,7 +15,9 @@ import javax.inject.Singleton
Student::class, Student::class,
Semester::class, Semester::class,
Exam::class, Exam::class,
Attendance::class Attendance::class,
Grade::class,
GradeSummary::class
], ],
version = 1, version = 1,
exportSchema = false exportSchema = false
@ -27,6 +25,13 @@ import javax.inject.Singleton
@TypeConverters(Converters::class) @TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() { 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 studentDao(): StudentDao
abstract fun semesterDao(): SemesterDao abstract fun semesterDao(): SemesterDao
@ -34,4 +39,8 @@ abstract class AppDatabase : RoomDatabase() {
abstract fun examsDao(): ExamDao abstract fun examsDao(): ExamDao
abstract fun attendanceDao(): AttendanceDao 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, var id: Long = 0,
@ColumnInfo(name = "student_id") @ColumnInfo(name = "student_id")
var studentId: String = "", var studentId: String,
@ColumnInfo(name = "diary_id") @ColumnInfo(name = "diary_id")
var diaryId: String = "", var diaryId: String,
var date: LocalDate, 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, var presence: Boolean = false,

View File

@ -13,26 +13,26 @@ data class Exam(
var id: Long = 0, var id: Long = 0,
@ColumnInfo(name = "student_id") @ColumnInfo(name = "student_id")
var studentId: String = "", var studentId: String,
@ColumnInfo(name = "diary_id") @ColumnInfo(name = "diary_id")
var diaryId: String = "", var diaryId: String,
var date: LocalDate, var date: LocalDate,
@ColumnInfo(name = "entry_date") @ColumnInfo(name = "entry_date")
var entryDate: LocalDate = LocalDate.now(), 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") @ColumnInfo(name = "teacher_symbol")
var teacherSymbol: String = "" var teacherSymbol: String
) : Serializable ) : 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, var diaryId: String,
@ColumnInfo(name = "diary_name") @ColumnInfo(name = "diary_name")
var diaryName: String = "", var diaryName: String,
@ColumnInfo(name = "semester_id") @ColumnInfo(name = "semester_id")
var semesterId: String, var semesterId: String,
@ColumnInfo(name = "semester_name") @ColumnInfo(name = "semester_name")
var semesterName: Int = 0, var semesterName: Int,
@ColumnInfo(name = "is_current") @ColumnInfo(name = "is_current")
var current: Boolean = false 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.db.entities.Semester
import io.github.wulkanowy.data.repositories.local.AttendanceLocal import io.github.wulkanowy.data.repositories.local.AttendanceLocal
import io.github.wulkanowy.data.repositories.remote.AttendanceRemote 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 io.reactivex.Single
import org.threeten.bp.DayOfWeek import org.threeten.bp.DayOfWeek
import org.threeten.bp.LocalDate 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>> { 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)) val end = endDate.with(TemporalAdjusters.nextOrSame(DayOfWeek.FRIDAY))
return local.getAttendance(semester, start, end).filter { !forceRefresh } 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.db.entities.Semester
import io.github.wulkanowy.data.repositories.local.ExamLocal import io.github.wulkanowy.data.repositories.local.ExamLocal
import io.github.wulkanowy.data.repositories.remote.ExamRemote import io.github.wulkanowy.data.repositories.remote.ExamRemote
import io.github.wulkanowy.utils.extension.getWeekFirstDayAlwaysCurrent import io.github.wulkanowy.utils.weekFirstDayAlwaysCurrent
import io.github.wulkanowy.utils.extension.toDate
import io.reactivex.Single import io.reactivex.Single
import org.threeten.bp.DayOfWeek import org.threeten.bp.DayOfWeek
import org.threeten.bp.LocalDate 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>> { 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)) val end = endDate.with(TemporalAdjusters.nextOrSame(DayOfWeek.FRIDAY))
return local.getExams(semester, start, end).filter { !forceRefresh } 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 { fun saveStudent(student: Student): Completable {
return remote.getSemesters(student).flatMapCompletable { return remote.getSemesters(student).flatMapCompletable { local.saveSemesters(it) }
local.saveSemesters(it) .concatWith(local.saveStudent(student))
}.concatWith(local.saveStudent(student))
} }
fun clearCache() { 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.dao.ExamDao
import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.utils.extension.toDate
import io.reactivex.Maybe import io.reactivex.Maybe
import org.threeten.bp.DayOfWeek
import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate
import org.threeten.bp.temporal.TemporalAdjusters
import javax.inject.Inject import javax.inject.Inject
class ExamLocal @Inject constructor(private val examDb: ExamDao) { 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.api.Api
import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.db.entities.Semester 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 io.reactivex.Single
import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate
import javax.inject.Inject 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.api.Api
import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.db.entities.Semester 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 io.reactivex.Single
import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate
import javax.inject.Inject 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.WulkanowyApp
import io.github.wulkanowy.utils.schedulers.SchedulersManager import io.github.wulkanowy.utils.schedulers.SchedulersManager
import io.github.wulkanowy.utils.schedulers.SchedulersProvider import io.github.wulkanowy.utils.schedulers.SchedulersProvider
import javax.inject.Singleton
@Module @Module
internal class AppModule { internal class AppModule {
@Singleton
@Provides @Provides
fun provideContext(app: WulkanowyApp): Context = app fun provideContext(app: WulkanowyApp): Context = app
@Singleton
@Provides @Provides
fun provideSchedulers(): SchedulersManager = SchedulersProvider() 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.support.v7.app.AppCompatDelegate
import android.view.View import android.view.View
import dagger.android.support.DaggerAppCompatActivity import dagger.android.support.DaggerAppCompatActivity
import io.github.wulkanowy.R
abstract class BaseActivity : DaggerAppCompatActivity(), BaseView { abstract class BaseActivity : DaggerAppCompatActivity(), BaseView {
protected lateinit var messageView: View protected lateinit var messageContainer: View
public override fun onCreate(savedInstanceState: Bundle?) { public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -18,12 +17,7 @@ abstract class BaseActivity : DaggerAppCompatActivity(), BaseView {
} }
override fun showMessage(text: String) { override fun showMessage(text: String) {
Snackbar.make(messageView, text, LENGTH_LONG).show() Snackbar.make(messageContainer, text, LENGTH_LONG).show()
}
override fun showNoNetworkMessage() {
showMessage(getString(R.string.all_no_internet))
} }
override fun onDestroy() { override fun onDestroy() {

View File

@ -1,24 +1,10 @@
package io.github.wulkanowy.ui.base package io.github.wulkanowy.ui.base
import android.support.annotation.StringRes
import dagger.android.support.DaggerFragment import dagger.android.support.DaggerFragment
abstract class BaseFragment : DaggerFragment(), BaseView { abstract class BaseFragment : DaggerFragment(), BaseView {
fun setTitle(title: String) {
activity?.title = title
}
override fun showMessage(text: String) { override fun showMessage(text: String) {
(activity as BaseActivity?)?.showMessage(text) (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.Fragment
import android.support.v4.app.FragmentManager import android.support.v4.app.FragmentManager
import android.support.v4.app.FragmentStatePagerAdapter import android.support.v4.app.FragmentStatePagerAdapter
import android.view.ViewGroup
class BasePagerAdapter(fragmentManager: FragmentManager) : FragmentStatePagerAdapter(fragmentManager) { 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) { override fun getItem(position: Int) = fragments.values.elementAt(position)
fragmentList.add(fragment)
titleList.add(title)
}
fun addFragments(vararg fragments: Fragment) { override fun getCount() = fragments.size
fragmentList.addAll(fragments)
}
override fun getItem(position: Int): Fragment = fragmentList[position]
override fun getCount(): Int = fragmentList.size
override fun getPageTitle(position: Int): CharSequence? { override fun getPageTitle(position: Int): CharSequence? {
return if (!titleList.isEmpty() && titleList.size == fragmentList.size) titleList[position] return fragments.keys.elementAtOrNull(position)
else null }
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 { interface BaseView {
fun showMessage(text: String) 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.base.BasePagerAdapter
import io.github.wulkanowy.ui.login.form.LoginFormFragment import io.github.wulkanowy.ui.login.form.LoginFormFragment
import io.github.wulkanowy.ui.login.options.LoginOptionsFragment 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 kotlinx.android.synthetic.main.activity_login.*
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Named
class LoginActivity : BaseActivity(), LoginView, LoginSwitchListener { class LoginActivity : BaseActivity(), LoginView, LoginSwitchListener {
@ -19,7 +18,6 @@ class LoginActivity : BaseActivity(), LoginView, LoginSwitchListener {
lateinit var presenter: LoginPresenter lateinit var presenter: LoginPresenter
@Inject @Inject
@field:Named("Login")
lateinit var loginAdapter: BasePagerAdapter lateinit var loginAdapter: BasePagerAdapter
companion object { companion object {
@ -30,7 +28,7 @@ class LoginActivity : BaseActivity(), LoginView, LoginSwitchListener {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login) setContentView(R.layout.activity_login)
presenter.attachView(this) presenter.attachView(this)
messageView = loginContainer messageContainer = loginContainer
} }
override fun onBackPressed() { override fun onBackPressed() {
@ -38,7 +36,10 @@ class LoginActivity : BaseActivity(), LoginView, LoginSwitchListener {
} }
override fun initAdapter() { override fun initAdapter() {
loginAdapter.addFragments(LoginFormFragment(), LoginOptionsFragment()) loginAdapter.fragments.putAll(mapOf(
"1" to LoginFormFragment.newInstance(),
"2" to LoginOptionsFragment.newInstance()
))
loginViewpager.run { loginViewpager.run {
adapter = loginAdapter adapter = loginAdapter
setOnSelectPageListener { presenter.onPageSelected(it) } setOnSelectPageListener { presenter.onPageSelected(it) }
@ -61,7 +62,7 @@ class LoginActivity : BaseActivity(), LoginView, LoginSwitchListener {
(loginAdapter.getItem(index) as LoginOptionsFragment).loadData() (loginAdapter.getItem(index) as LoginOptionsFragment).loadData()
} }
override fun currentViewPosition(): Int = loginViewpager.currentItem override fun currentViewPosition() = loginViewpager.currentItem
public override fun onDestroy() { public override fun onDestroy() {
presenter.detachView() 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.base.BasePagerAdapter
import io.github.wulkanowy.ui.login.form.LoginFormFragment import io.github.wulkanowy.ui.login.form.LoginFormFragment
import io.github.wulkanowy.ui.login.options.LoginOptionsFragment import io.github.wulkanowy.ui.login.options.LoginOptionsFragment
import io.github.wulkanowy.ui.login.options.LoginOptionsModule
import javax.inject.Named
@Module @Module
internal abstract class LoginModule { internal abstract class LoginModule {
@ -19,8 +17,8 @@ internal abstract class LoginModule {
companion object { companion object {
@JvmStatic @JvmStatic
@PerActivity
@Provides @Provides
@Named("Login")
fun provideLoginAdapter(activity: LoginActivity) = BasePagerAdapter(activity.supportFragmentManager) fun provideLoginAdapter(activity: LoginActivity) = BasePagerAdapter(activity.supportFragmentManager)
@JvmStatic @JvmStatic
@ -34,6 +32,6 @@ internal abstract class LoginModule {
abstract fun bindLoginFormFragment(): LoginFormFragment abstract fun bindLoginFormFragment(): LoginFormFragment
@PerFragment @PerFragment
@ContributesAndroidInjector(modules = [LoginOptionsModule::class]) @ContributesAndroidInjector()
abstract fun bindLoginOptionsFragment(): LoginOptionsFragment abstract fun bindLoginOptionsFragment(): LoginOptionsFragment
} }

View File

@ -11,8 +11,8 @@ import android.widget.ArrayAdapter
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.login.LoginSwitchListener import io.github.wulkanowy.ui.login.LoginSwitchListener
import io.github.wulkanowy.utils.extension.hideSoftInput import io.github.wulkanowy.utils.hideSoftInput
import io.github.wulkanowy.utils.extension.showSoftInput import io.github.wulkanowy.utils.showSoftInput
import kotlinx.android.synthetic.main.fragment_login_form.* import kotlinx.android.synthetic.main.fragment_login_form.*
import javax.inject.Inject import javax.inject.Inject
@ -21,6 +21,10 @@ class LoginFormFragment : BaseFragment(), LoginFormView {
@Inject @Inject
lateinit var presenter: LoginFormPresenter lateinit var presenter: LoginFormPresenter
companion object {
fun newInstance() = LoginFormFragment()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_login_form, container, false) 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 loginFormProgressContainer.visibility = if (show) VISIBLE else GONE
} }
override fun onDestroy() { override fun onDestroyView() {
super.onDestroy() super.onDestroyView()
presenter.detachView() 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.data.repositories.SessionRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.login.LoginErrorHandler import io.github.wulkanowy.ui.login.LoginErrorHandler
import io.github.wulkanowy.utils.DEFAULT_SYMBOL
import io.github.wulkanowy.utils.schedulers.SchedulersManager import io.github.wulkanowy.utils.schedulers.SchedulersManager
import javax.inject.Inject import javax.inject.Inject
@ -83,6 +82,6 @@ class LoginFormPresenter @Inject constructor(
} }
private fun normalizeSymbol(symbol: String): String { 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.R
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.main.MainActivity 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 kotlinx.android.synthetic.main.fragment_login_options.*
import javax.inject.Inject 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.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v4.content.ContextCompat import android.support.v4.content.ContextCompat
import com.aurelhubert.ahbottomnavigation.AHBottomNavigation import com.aurelhubert.ahbottomnavigation.AHBottomNavigation
import com.aurelhubert.ahbottomnavigation.AHBottomNavigationItem 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.grade.GradeFragment
import io.github.wulkanowy.ui.main.more.MoreFragment import io.github.wulkanowy.ui.main.more.MoreFragment
import io.github.wulkanowy.ui.main.timetable.TimetableFragment import io.github.wulkanowy.ui.main.timetable.TimetableFragment
import io.github.wulkanowy.utils.setOnTabTransactionListener
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.*
import javax.inject.Inject import javax.inject.Inject
class MainActivity : BaseActivity(), MainView, FragNavController.TransactionListener { class MainActivity : BaseActivity(), MainView {
@Inject @Inject
lateinit var presenter: MainPresenter lateinit var presenter: MainPresenter
@ -27,7 +28,7 @@ class MainActivity : BaseActivity(), MainView, FragNavController.TransactionList
lateinit var navController: FragNavController lateinit var navController: FragNavController
companion object { companion object {
const val DEFAULT_TAB = 0 const val DEFAULT_TAB = 2
fun getStartIntent(context: Context) = Intent(context, MainActivity::class.java) fun getStartIntent(context: Context) = Intent(context, MainActivity::class.java)
} }
@ -35,27 +36,19 @@ class MainActivity : BaseActivity(), MainView, FragNavController.TransactionList
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
messageView = mainContainer setSupportActionBar(mainToolbar)
messageContainer = mainFragmentContainer
presenter.attachView(this) presenter.attachView(this)
navController.initialize(DEFAULT_TAB, savedInstanceState) navController.initialize(DEFAULT_TAB, savedInstanceState)
} }
override fun initFragmentController() { override fun onStart() {
navController.run { super.onStart()
rootFragments = listOf( presenter.onStartView()
GradeFragment.newInstance(),
AttendanceFragment.newInstance(),
ExamFragment.newInstance(),
TimetableFragment.newInstance(),
MoreFragment.newInstance()
)
fragmentHideStrategy = DETACH_ON_NAVIGATE_HIDE_ON_SWITCH
createEager = true
transactionListener = this@MainActivity
}
} }
override fun initBottomNav() { override fun initView() {
mainBottomNav.run { mainBottomNav.run {
addItems(mutableListOf( addItems(mutableListOf(
AHBottomNavigationItem(R.string.grade_title, R.drawable.ic_menu_main_grade_26dp, 0), 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 titleState = AHBottomNavigation.TitleState.ALWAYS_SHOW
currentItem = DEFAULT_TAB currentItem = DEFAULT_TAB
isBehaviorTranslationEnabled = false isBehaviorTranslationEnabled = false
setOnTabSelectedListener { position, _ -> setTitleTextSizeInSp(10f, 10f)
presenter.onTabSelected(position)
} setOnTabSelectedListener { position, wasSelected ->
presenter.onTabSelected(position, wasSelected)
} }
} }
override fun onFragmentTransaction(fragment: Fragment?, transactionType: FragNavController.TransactionType) {} navController.run {
setOnTabTransactionListener { presenter.onMenuViewChange(it) }
override fun onTabTransaction(fragment: Fragment?, index: Int) { fragmentHideStrategy = DETACH_ON_NAVIGATE_HIDE_ON_SWITCH
presenter.onMenuFragmentChange(index) rootFragments = listOf(
GradeFragment.newInstance(),
AttendanceFragment.newInstance(),
ExamFragment.newInstance(),
TimetableFragment.newInstance(),
MoreFragment.newInstance()
)
}
} }
override fun switchMenuFragment(position: Int) { override fun switchMenuView(position: Int) {
navController.switchTab(position) navController.switchTab(position)
} }
override fun setViewTitle(title: String) { 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> { override fun viewTitle(index: Int): String {
return mapOf(0 to R.string.grade_title, return getString(listOf(R.string.grade_title,
1 to R.string.attendance_title, R.string.attendance_title,
2 to R.string.exam_title, R.string.exam_title,
3 to R.string.timetable_title, R.string.timetable_title,
4 to R.string.more_title R.string.more_title)[index])
).mapValues { getString(it.value) } }
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?) { override fun onSaveInstanceState(outState: Bundle?) {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
navController.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.attendance.AttendanceFragment
import io.github.wulkanowy.ui.main.exam.ExamFragment import io.github.wulkanowy.ui.main.exam.ExamFragment
import io.github.wulkanowy.ui.main.grade.GradeFragment 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.more.MoreFragment
import io.github.wulkanowy.ui.main.timetable.TimetableFragment import io.github.wulkanowy.ui.main.timetable.TimetableFragment
@ -36,7 +37,7 @@ abstract class MainModule {
abstract fun bindExamFragment(): ExamFragment abstract fun bindExamFragment(): ExamFragment
@PerFragment @PerFragment
@ContributesAndroidInjector @ContributesAndroidInjector(modules = [GradeModule::class])
abstract fun bindGradeFragment(): GradeFragment abstract fun bindGradeFragment(): GradeFragment
@PerFragment @PerFragment
@ -47,4 +48,3 @@ abstract class MainModule {
@ContributesAndroidInjector @ContributesAndroidInjector
abstract fun bindTimetableFragment(): TimetableFragment abstract fun bindTimetableFragment(): TimetableFragment
} }

View File

@ -9,21 +9,27 @@ class MainPresenter @Inject constructor(errorHandler: ErrorHandler)
override fun attachView(view: MainView) { override fun attachView(view: MainView) {
super.attachView(view) super.attachView(view)
view.run { view.initView()
initFragmentController()
initBottomNav()
}
} }
fun onTabSelected(position: Int): Boolean { fun onStartView() {
view?.switchMenuFragment(position) view?.run { setViewTitle(viewTitle(currentMenuIndex())) }
return true
} }
fun onMenuFragmentChange(position: Int) { fun onMenuViewChange(index: Int) {
view?.run { view?.run { setViewTitle(viewTitle(index)) }
setViewTitle(mapOfTitles()[position] ?: defaultTitle())
} }
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 { interface MainView : BaseView {
fun initFragmentController() fun initView()
fun initBottomNav() fun switchMenuView(position: Int)
fun switchMenuFragment(position: Int)
fun setViewTitle(title: String) 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 android.view.ViewGroup
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Attendance 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.* import kotlinx.android.synthetic.main.dialog_attendance.*
class AttendanceDialog : DialogFragment() { class AttendanceDialog : DialogFragment() {
@ -42,7 +42,7 @@ class AttendanceDialog : DialogFragment() {
attendanceDialogSubject.text = attendance.subject attendanceDialogSubject.text = attendance.subject
attendanceDialogDescription.text = attendance.name attendanceDialogDescription.text = attendance.name
attendanceDialogDate.text = attendance.date.toFormat() attendanceDialogDate.text = attendance.date.toFormattedString()
attendanceDialogNumber.text = attendance.number.toString() attendanceDialogNumber.text = attendance.number.toString()
attendanceDialogClose.setOnClickListener { dismiss() } attendanceDialogClose.setOnClickListener { dismiss() }
} }

View File

@ -10,7 +10,7 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.ui.base.BaseFragment 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 kotlinx.android.synthetic.main.fragment_attendance.*
import javax.inject.Inject 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.AttendanceRepository
import io.github.wulkanowy.data.repositories.SessionRepository import io.github.wulkanowy.data.repositories.SessionRepository
import io.github.wulkanowy.ui.base.BasePresenter 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 io.github.wulkanowy.utils.schedulers.SchedulersManager
import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate
import javax.inject.Inject import javax.inject.Inject
@ -19,7 +19,7 @@ class AttendancePresenter @Inject constructor(
private val sessionRepository: SessionRepository private val sessionRepository: SessionRepository
) : BasePresenter<AttendanceView>(errorHandler) { ) : BasePresenter<AttendanceView>(errorHandler) {
var currentDate: LocalDate = LocalDate.now().getNearSchoolDayPrevOnWeekEnd() var currentDate: LocalDate = LocalDate.now().nearSchoolDayPrevOnWeekEnd
private set private set
override fun attachView(view: AttendanceView) { override fun attachView(view: AttendanceView) {
@ -27,13 +27,14 @@ class AttendancePresenter @Inject constructor(
view.initView() 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) { fun loadData(date: Long?, forceRefresh: Boolean = false) {
this.currentDate = LocalDate.ofEpochDay(date ?: currentDate.getNearSchoolDayPrevOnWeekEnd().toEpochDay()) this.currentDate = LocalDate.ofEpochDay(date
if (currentDate.isHolidays()) return ?: currentDate.nearSchoolDayPrevOnWeekEnd.toEpochDay())
if (currentDate.isHolidays) return
disposable.clear() disposable.clear()
disposable.add(sessionRepository.getSemesters() disposable.add(sessionRepository.getSemesters()
@ -50,9 +51,9 @@ class AttendancePresenter @Inject constructor(
showEmpty(false) showEmpty(false)
clearData() clearData()
} }
showPreButton(!currentDate.minusDays(1).isHolidays()) showPreButton(!currentDate.minusDays(1).isHolidays)
showNextButton(!currentDate.plusDays(1).isHolidays()) showNextButton(!currentDate.plusDays(1).isHolidays)
updateNavigationDay(currentDate.toFormat("EEEE \n dd.MM.YYYY").capitalize()) updateNavigationDay(currentDate.toFormattedString("EEEE \n dd.MM.YYYY").capitalize())
} }
} }
.doFinally { .doFinally {

View File

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

View File

@ -11,7 +11,7 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.ui.base.BaseFragment 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 kotlinx.android.synthetic.main.fragment_exam.*
import javax.inject.Inject 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.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.ExpandableViewHolder import eu.davidea.viewholders.ExpandableViewHolder
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.utils.extension.getWeekDayName import io.github.wulkanowy.utils.toFormattedString
import io.github.wulkanowy.utils.extension.toFormat import io.github.wulkanowy.utils.weekDayName
import kotlinx.android.extensions.LayoutContainer import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.header_exam.* import kotlinx.android.synthetic.main.header_exam.*
import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate
@ -40,8 +40,8 @@ class ExamHeader : AbstractHeaderItem<ExamHeader.ViewHolder>() {
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>?, holder: ViewHolder, override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>?, holder: ViewHolder,
position: Int, payloads: MutableList<Any>?) { position: Int, payloads: MutableList<Any>?) {
holder.run { holder.run {
examHeaderDay.text = date.getWeekDayName().capitalize() examHeaderDay.text = date.weekDayName.capitalize()
examHeaderDate.text = date.toFormat() 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.ExamRepository
import io.github.wulkanowy.data.repositories.SessionRepository import io.github.wulkanowy.data.repositories.SessionRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.utils.extension.getWeekFirstDayNextOnWeekEnd import io.github.wulkanowy.utils.isHolidays
import io.github.wulkanowy.utils.extension.isHolidays
import io.github.wulkanowy.utils.extension.toFormat
import io.github.wulkanowy.utils.schedulers.SchedulersManager 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 org.threeten.bp.LocalDate
import javax.inject.Inject import javax.inject.Inject
@ -21,7 +21,7 @@ class ExamPresenter @Inject constructor(
private val sessionRepository: SessionRepository private val sessionRepository: SessionRepository
) : BasePresenter<ExamView>(errorHandler) { ) : BasePresenter<ExamView>(errorHandler) {
var currentDate: LocalDate = LocalDate.now().getWeekFirstDayNextOnWeekEnd() var currentDate: LocalDate = LocalDate.now().weekFirstDayNextOnWeekEnd
private set private set
override fun attachView(view: ExamView) { override fun attachView(view: ExamView) {
@ -34,8 +34,9 @@ class ExamPresenter @Inject constructor(
fun loadExamsForNextWeek() = loadData(currentDate.plusDays(7).toEpochDay()) fun loadExamsForNextWeek() = loadData(currentDate.plusDays(7).toEpochDay())
fun loadData(date: Long?, forceRefresh: Boolean = false) { fun loadData(date: Long?, forceRefresh: Boolean = false) {
this.currentDate = LocalDate.ofEpochDay(date ?: currentDate.getWeekFirstDayNextOnWeekEnd().toEpochDay()) this.currentDate = LocalDate.ofEpochDay(date
if (currentDate.isHolidays()) return ?: currentDate.weekFirstDayNextOnWeekEnd.toEpochDay())
if (currentDate.isHolidays) return
disposable.clear() disposable.clear()
disposable.add(sessionRepository.getSemesters() disposable.add(sessionRepository.getSemesters()
@ -51,9 +52,10 @@ class ExamPresenter @Inject constructor(
showProgress(!forceRefresh) showProgress(!forceRefresh)
if (!forceRefresh) showEmpty(false) if (!forceRefresh) showEmpty(false)
showContent(null == date && forceRefresh) showContent(null == date && forceRefresh)
showPreButton(!currentDate.minusDays(7).isHolidays()) showPreButton(!currentDate.minusDays(7).isHolidays)
showNextButton(!currentDate.plusDays(7).isHolidays()) showNextButton(!currentDate.plusDays(7).isHolidays)
updateNavigationWeek("${currentDate.toFormat("dd.MM")}-${currentDate.plusDays(4).toFormat("dd.MM")}") updateNavigationWeek(currentDate.toFormattedString("dd.MM") +
"-${currentDate.plusDays(4).toFormattedString("dd.MM")}")
} }
} }
.doAfterSuccess { .doAfterSuccess {

View File

@ -1,19 +1,120 @@
package io.github.wulkanowy.ui.main.grade package io.github.wulkanowy.ui.main.grade
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.support.v7.app.AlertDialog
import android.view.View import android.view.*
import android.view.ViewGroup import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseFragment 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 { companion object {
fun newInstance() = GradeFragment() fun newInstance() = GradeFragment()
} }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_grade, container, false) 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.app.Activity
import android.content.Context.INPUT_METHOD_SERVICE 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 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 { addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
selectListener(position) selectListener(position)
} }
override fun onPageScrollStateChanged(state: Int) {} override fun onPageScrollStateChanged(state: Int) {}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: 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.security.keystore.KeyProperties.*
import android.util.Base64 import android.util.Base64
import android.util.Base64.DEFAULT import android.util.Base64.DEFAULT
import org.apache.commons.lang3.StringUtils
import timber.log.Timber import timber.log.Timber
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
@ -76,7 +75,7 @@ object Scrambler {
@JvmStatic @JvmStatic
fun decrypt(cipherText: String): String { 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) { if (SDK_INT < JELLY_BEAN_MR2 || cipherText.length < 250) {
return String(Base64.decode(cipherText.toByteArray(KEY_CHARSET), DEFAULT), KEY_CHARSET) 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" <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
android:opacity="opaque"> <item>
<shape>
<solid android:color="#FFF" />
</shape>
</item>
<item> <item>
<bitmap <bitmap
android:gravity="left|right|top|bottom" 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"?> <?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:id="@+id/mainContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:orientation="vertical">
<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 <FrameLayout
android:id="@+id/mainFragmentContainer" android:id="@+id/mainFragmentContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="match_parent"
android:layout_weight="1" /> android:layout_marginBottom="@dimen/bottom_navigation_height"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<com.aurelhubert.ahbottomnavigation.AHBottomNavigation <com.aurelhubert.ahbottomnavigation.AHBottomNavigation
android:id="@+id/mainBottomNav" android:id="@+id/mainBottomNav"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom" /> android:layout_gravity="bottom" />
</LinearLayout> </android.support.design.widget.CoordinatorLayout>

View File

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

View File

@ -1,174 +1,33 @@
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" <?xml version="1.0" encoding="utf-8"?>
android:id="@+id/grade_fragment_container" <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_width="match_parent"
android:layout_height="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_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="center" android:layout_below="@id/gradeTabLayout"
android:text="Grade" /> android:visibility="invisible" />
<!--<android.support.v4.widget.SwipeRefreshLayout <ProgressBar
android:id="@+id/grade_fragment_swipe_refresh" android:id="@+id/gradeProgress"
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_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerHorizontal="true" android:layout_centerInParent="true"
android:gravity="center" android:indeterminate="true" />
android:text="@string/grades_summary_calculated_average" </RelativeLayout>
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>

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:layout_marginBottom="15dp"
android:hint="@string/login_email_hint"> android:hint="@string/login_email_hint">
<EditText <android.support.design.widget.TextInputEditText
android:id="@+id/loginEmailEdit" android:id="@+id/loginEmailEdit"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -82,7 +82,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/login_password_hint"> android:hint="@string/login_password_hint">
<EditText <android.support.design.widget.TextInputEditText
android:id="@+id/loginPassEdit" android:id="@+id/loginPassEdit"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" 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:id="@+id/timetable_fragment_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -9,22 +9,4 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="center" android:gravity="center"
android:text="Timetable" /> android:text="Timetable" />
</FrameLayout>
<!--<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>

View File

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

View File

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

View File

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

View File

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