1
0

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,80 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tool="http://schemas.android.com/tools"
android:id="@+id/grade_subitem_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:foreground="?attr/selectableItemBackgroundBorderless"
android:paddingBottom="7dp"
android:paddingEnd="12dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingStart="12dp"
android:paddingTop="7dp">
<TextView
android:id="@+id/gradeItemValue"
android:layout_width="45dp"
android:layout_height="40dp"
android:layout_centerVertical="true"
android:background="@color/grade_default"
android:gravity="center"
android:maxLength="5"
android:text="@string/app_name"
android:textColor="@color/grade_text"
android:textSize="16sp" />
<TextView
android:id="@+id/gradeItemDescription"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginEnd="20dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="20dp"
android:layout_marginStart="10dp"
android:layout_toEndOf="@+id/gradeItemValue"
android:layout_toLeftOf="@id/gradeItemNote"
android:layout_toRightOf="@+id/gradeItemValue"
android:layout_toStartOf="@id/gradeItemNote"
android:ellipsize="end"
android:maxLines="1"
android:text="@string/all_description"
android:textSize="14sp" />
<TextView
android:id="@+id/gradeItemDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/gradeItemValue"
android:layout_alignLeft="@+id/gradeItemDescription"
android:layout_alignStart="@+id/gradeItemDescription"
android:text="@string/all_date"
android:textSize="12sp" />
<TextView
android:id="@+id/gradeItemWeight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/gradeItemValue"
android:layout_marginEnd="20dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="20dp"
android:layout_marginStart="10dp"
android:layout_toEndOf="@+id/gradeItemDate"
android:layout_toLeftOf="@id/gradeItemNote"
android:layout_toRightOf="@+id/gradeItemDate"
android:layout_toStartOf="@id/gradeItemNote"
android:text="@string/grade_weight"
android:textSize="12sp" />
<ImageView
android:id="@+id/gradeItemNote"
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>

View File

@ -1,75 +1,33 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:background="@drawable/ic_all_divider"
android:minHeight="35dp">
<RelativeLayout
android:id="@+id/grades_summary_subitem_predicted_container"
android:layout_width="match_parent"
<TextView
android:id="@+id/gradeSummaryItemTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/ic_all_divider"
android:minHeight="35dp">
android:layout_centerVertical="true"
android:layout_marginLeft="20dp"
android:layout_marginStart="20dp"
android:text="@string/grade_summary_predicted_grade"
android:textSize="14sp" />
<TextView
android:id="@+id/grades_summary_subitem_predicted_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="20dp"
android:layout_marginStart="20dp"
android:text="@string/grade_summary_predicted_average"
android:textSize="14sp" />
<TextView
android:id="@+id/grades_summary_subitem_predicted_grade"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginEnd="25dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="25dp"
android:layout_marginStart="10dp"
android:layout_toEndOf="@id/grades_summary_subitem_predicted_name"
android:layout_toRightOf="@id/grades_summary_subitem_predicted_name"
android:gravity="end"
android:text="@string/app_name"
android:textSize="12sp" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/grades_summary_subitem_final_container"
android:layout_width="match_parent"
<TextView
android:id="@+id/gradeSummaryItemGrade"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/grades_summary_subitem_predicted_container"
android:background="@drawable/ic_all_divider"
android:minHeight="35dp">
<TextView
android:id="@+id/grades_summary_subitem_final_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="20dp"
android:layout_marginStart="20dp"
android:text="@string/grade_summary_final_average"
android:textSize="14sp" />
<TextView
android:id="@+id/grades_summary_subitem_final_grade"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginEnd="25dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="25dp"
android:layout_marginStart="10dp"
android:layout_toEndOf="@id/grades_summary_subitem_final_name"
android:layout_toRightOf="@id/grades_summary_subitem_final_name"
android:gravity="end"
android:text="@string/app_name"
android:textSize="12sp" />
</RelativeLayout>
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginEnd="25dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="25dp"
android:layout_marginStart="10dp"
android:layout_toEndOf="@id/gradeSummaryItemTitle"
android:layout_toRightOf="@id/gradeSummaryItemTitle"
android:gravity="end"
android:text="@string/app_name"
android:textSize="12sp" />
</RelativeLayout>

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:gravity="center_horizontal"
android:orientation="horizontal">
<LinearLayout
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:layout_marginRight="5dp"
android:layout_marginTop="20dp"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:minLines="2"
android:text="@string/grade_summary_calculated_average"
android:textSize="16sp" />
<TextView
android:id="@+id/gradeSummaryScrollableHeaderCalculated"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="6,00"
android:textSize="21sp" />
</LinearLayout>
<LinearLayout
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:minLines="2"
android:text="@string/grade_summary_final_average"
android:textSize="16sp" />
<TextView
android:id="@+id/gradeSummaryScrollableHeaderFinal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="6,00"
android:textSize="21sp" />
</LinearLayout>
</LinearLayout>

View File

@ -6,7 +6,7 @@
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="@dimen/widget_bar_height"
android:layout_height="@dimen/timetable_widget_bar_height"
android:background="@color/colorPrimary">
<Button
@ -45,7 +45,7 @@
<TextView
android:id="@+id/timetable_widget_title"
android:layout_width="match_parent"
android:layout_height="@dimen/widget_bar_height"
android:layout_height="@dimen/timetable_widget_bar_height"
android:layout_marginLeft="10dp"
android:layout_marginStart="10dp"
android:layout_toLeftOf="@id/timetable_widget_toggle"
@ -62,7 +62,7 @@
android:id="@+id/timetable_widget_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/widget_bar_height" />
android:layout_marginTop="@dimen/timetable_widget_bar_height" />
<TextView
android:id="@+id/timetable_widget_empty"

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