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

Settings refactor (#166)

This commit is contained in:
Rafał Borcz 2018-10-22 22:47:54 +02:00 committed by Mikołaj Pich
parent b680cc4366
commit 837bce7286
51 changed files with 679 additions and 225 deletions

View File

@ -77,11 +77,11 @@ dependencies {
implementation "androidx.legacy:legacy-support-v4:$androidx_version" implementation "androidx.legacy:legacy-support-v4:$androidx_version"
implementation "androidx.appcompat:appcompat:$androidx_version" implementation "androidx.appcompat:appcompat:$androidx_version"
implementation "androidx.cardview:cardview:$androidx_version" implementation "androidx.cardview:cardview:$androidx_version"
implementation "androidx.legacy:legacy-preference-v14:$androidx_version"
implementation "com.google.android.material:material:$androidx_version" implementation "com.google.android.material:material:$androidx_version"
implementation 'androidx.multidex:multidex:2.0.0' implementation 'androidx.multidex:multidex:2.0.0'
implementation "com.google.android.gms:play-services-oss-licenses:16.0.1" implementation 'com.takisoft.preferencex:preferencex:1.0.0'
implementation "com.mikepenz:aboutlibraries:6.2.0"
implementation "com.firebase:firebase-jobdispatcher:0.8.5" implementation "com.firebase:firebase-jobdispatcher:0.8.5"
//Do not update dagger https://github.com/google/dagger/issues/1245 //Do not update dagger https://github.com/google/dagger/issues/1245

View File

@ -0,0 +1,16 @@
package io.github.wulkanowy.data.repositories
import android.content.SharedPreferences
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class PreferencesRepository @Inject constructor(private val sharedPref: SharedPreferences) {
val startMenuIndex: Int
get() = sharedPref.getString("start_menu", "0")?.toInt() ?: 0
val showPresent: Boolean
get() = sharedPref.getBoolean("attendance_present", true)
}

View File

@ -24,6 +24,9 @@ class LoginActivity : BaseActivity(), LoginView, LoginSwitchListener {
fun getStartIntent(context: Context) = Intent(context, LoginActivity::class.java) fun getStartIntent(context: Context) = Intent(context, LoginActivity::class.java)
} }
override val currentViewIndex: Int
get() = loginViewpager.currentItem
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login) setContentView(R.layout.activity_login)
@ -63,8 +66,6 @@ class LoginActivity : BaseActivity(), LoginView, LoginSwitchListener {
(loginAdapter.getItem(index) as LoginOptionsFragment).loadData() (loginAdapter.getItem(index) as LoginOptionsFragment).loadData()
} }
override fun currentViewPosition() = loginViewpager.currentItem
public override fun onDestroy() { public override fun onDestroy() {
presenter.onDetachView() presenter.onDetachView()
super.onDestroy() super.onDestroy()

View File

@ -25,7 +25,7 @@ class LoginPresenter @Inject constructor(errorHandler: ErrorHandler)
fun onBackPressed(default: () -> Unit) { fun onBackPressed(default: () -> Unit) {
view?.run { view?.run {
if (currentViewPosition() == 1) { if (currentViewIndex == 1) {
switchView(0) switchView(0)
hideActionBar() hideActionBar()
} else default() } else default()

View File

@ -4,6 +4,8 @@ import io.github.wulkanowy.ui.base.BaseView
interface LoginView : BaseView { interface LoginView : BaseView {
val currentViewIndex: Int
fun initAdapter() fun initAdapter()
fun loadOptionsView(index: Int) fun loadOptionsView(index: Int)
@ -11,6 +13,4 @@ interface LoginView : BaseView {
fun switchView(position: Int) fun switchView(position: Int)
fun hideActionBar() fun hideActionBar()
fun currentViewPosition(): Int
} }

View File

@ -4,10 +4,11 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.aurelhubert.ahbottomnavigation.AHBottomNavigation import androidx.fragment.app.Fragment
import com.aurelhubert.ahbottomnavigation.AHBottomNavigation.TitleState.ALWAYS_SHOW
import com.aurelhubert.ahbottomnavigation.AHBottomNavigationItem import com.aurelhubert.ahbottomnavigation.AHBottomNavigationItem
import com.ncapdevi.fragnav.FragNavController import com.ncapdevi.fragnav.FragNavController
import com.ncapdevi.fragnav.FragNavController.Companion.DETACH_ON_NAVIGATE_HIDE_ON_SWITCH import com.ncapdevi.fragnav.FragNavController.Companion.HIDE
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.main.attendance.AttendanceFragment import io.github.wulkanowy.ui.main.attendance.AttendanceFragment
@ -15,7 +16,8 @@ import io.github.wulkanowy.ui.main.exam.ExamFragment
import io.github.wulkanowy.ui.main.grade.GradeFragment import io.github.wulkanowy.ui.main.grade.GradeFragment
import io.github.wulkanowy.ui.main.more.MoreFragment import io.github.wulkanowy.ui.main.more.MoreFragment
import io.github.wulkanowy.ui.main.timetable.TimetableFragment import io.github.wulkanowy.ui.main.timetable.TimetableFragment
import io.github.wulkanowy.utils.setOnTabTransactionListener import io.github.wulkanowy.utils.safelyPopFragment
import io.github.wulkanowy.utils.setOnViewChangeListener
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.*
import javax.inject.Inject import javax.inject.Inject
@ -28,11 +30,20 @@ class MainActivity : BaseActivity(), MainView {
lateinit var navController: FragNavController lateinit var navController: FragNavController
companion object { companion object {
const val DEFAULT_TAB = 2
fun getStartIntent(context: Context) = Intent(context, MainActivity::class.java) fun getStartIntent(context: Context) = Intent(context, MainActivity::class.java)
} }
override val isRootView: Boolean
get() = navController.isRootFragment
override val currentViewTitle: String?
get() = (navController.currentFrag as? MainView.TitledView)?.titleStringId?.let { getString(it) }
override val currentStackSize: Int?
get() = navController.currentStack?.size
override var startMenuIndex = 0
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
@ -40,12 +51,16 @@ class MainActivity : BaseActivity(), MainView {
messageContainer = mainFragmentContainer messageContainer = mainFragmentContainer
presenter.onAttachView(this) presenter.onAttachView(this)
navController.initialize(DEFAULT_TAB, savedInstanceState) navController.initialize(startMenuIndex, savedInstanceState)
} }
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
presenter.onStartView() presenter.onViewStart()
}
override fun onSupportNavigateUp(): Boolean {
return presenter.onUpNavigate()
} }
override fun initView() { override fun initView() {
@ -59,19 +74,18 @@ class MainActivity : BaseActivity(), MainView {
)) ))
accentColor = ContextCompat.getColor(context, R.color.colorPrimary) accentColor = ContextCompat.getColor(context, R.color.colorPrimary)
inactiveColor = ContextCompat.getColor(context, android.R.color.black) inactiveColor = ContextCompat.getColor(context, android.R.color.black)
titleState = AHBottomNavigation.TitleState.ALWAYS_SHOW titleState = ALWAYS_SHOW
currentItem = DEFAULT_TAB currentItem = startMenuIndex
isBehaviorTranslationEnabled = false isBehaviorTranslationEnabled = false
setTitleTextSizeInSp(10f, 10f) setTitleTextSizeInSp(10f, 10f)
setOnTabSelectedListener { position, wasSelected -> setOnTabSelectedListener { position, wasSelected ->
presenter.onTabSelected(position, wasSelected) presenter.onTabSelected(position, wasSelected)
} }
} }
navController.run { navController.run {
setOnTabTransactionListener { presenter.onMenuViewChange(it) } setOnViewChangeListener { presenter.onViewStart() }
fragmentHideStrategy = DETACH_ON_NAVIGATE_HIDE_ON_SWITCH fragmentHideStrategy = HIDE
rootFragments = listOf( rootFragments = listOf(
GradeFragment.newInstance(), GradeFragment.newInstance(),
AttendanceFragment.newInstance(), AttendanceFragment.newInstance(),
@ -90,22 +104,24 @@ class MainActivity : BaseActivity(), MainView {
supportActionBar?.title = title supportActionBar?.title = title
} }
override fun viewTitle(index: Int): String { override fun showHomeArrow(show: Boolean) {
return getString(listOf(R.string.grade_title, supportActionBar?.setDisplayHomeAsUpEnabled(show)
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() { override fun notifyMenuViewReselected() {
(navController.currentFrag as? MainView.MenuFragmentView)?.onFragmentReselected() (navController.currentStack?.get(0) as? MainView.MainChildView)?.onFragmentReselected()
}
fun pushView(fragment: Fragment) {
navController.pushFragment(fragment)
}
override fun popView() {
navController.safelyPopFragment()
} }
override fun onBackPressed() { override fun onBackPressed() {
navController.apply { if (isRootFragment) super.onBackPressed() else popFragment() } presenter.onBackPressed { super.onBackPressed() }
} }
override fun onSaveInstanceState(outState: Bundle?) { override fun onSaveInstanceState(outState: Bundle?) {

View File

@ -7,6 +7,8 @@ import dagger.android.ContributesAndroidInjector
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.di.scopes.PerActivity import io.github.wulkanowy.di.scopes.PerActivity
import io.github.wulkanowy.di.scopes.PerFragment import io.github.wulkanowy.di.scopes.PerFragment
import io.github.wulkanowy.ui.main.about.AboutFragment
import io.github.wulkanowy.ui.main.about.AboutModule
import io.github.wulkanowy.ui.main.attendance.AttendanceFragment import io.github.wulkanowy.ui.main.attendance.AttendanceFragment
import io.github.wulkanowy.ui.main.exam.ExamFragment import io.github.wulkanowy.ui.main.exam.ExamFragment
import io.github.wulkanowy.ui.main.grade.GradeFragment import io.github.wulkanowy.ui.main.grade.GradeFragment
@ -47,4 +49,8 @@ abstract class MainModule {
@PerFragment @PerFragment
@ContributesAndroidInjector @ContributesAndroidInjector
abstract fun bindTimetableFragment(): TimetableFragment abstract fun bindTimetableFragment(): TimetableFragment
@PerFragment
@ContributesAndroidInjector(modules = [AboutModule::class])
abstract fun bindAboutFragment(): AboutFragment
} }

View File

@ -1,23 +1,44 @@
package io.github.wulkanowy.ui.main package io.github.wulkanowy.ui.main
import io.github.wulkanowy.data.ErrorHandler import io.github.wulkanowy.data.ErrorHandler
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import javax.inject.Inject import javax.inject.Inject
class MainPresenter @Inject constructor(errorHandler: ErrorHandler) class MainPresenter @Inject constructor(
errorHandler: ErrorHandler,
private val prefRepository: PreferencesRepository)
: BasePresenter<MainView>(errorHandler) { : BasePresenter<MainView>(errorHandler) {
override fun onAttachView(view: MainView) { override fun onAttachView(view: MainView) {
super.onAttachView(view) super.onAttachView(view)
view.initView() view.run {
startMenuIndex = prefRepository.startMenuIndex
initView()
}
} }
fun onStartView() { fun onViewStart() {
view?.run { setViewTitle(viewTitle(currentMenuIndex())) } view?.apply {
currentViewTitle?.let { setViewTitle(it) }
currentStackSize?.let {
if (it > 1) showHomeArrow(true)
else showHomeArrow(false)
}
}
} }
fun onMenuViewChange(index: Int) { fun onUpNavigate(): Boolean {
view?.run { setViewTitle(viewTitle(index)) } view?.popView()
return true
}
fun onBackPressed(default: () -> Unit) {
view?.run {
if (isRootView) default()
else popView()
}
} }
fun onTabSelected(index: Int, wasSelected: Boolean): Boolean { fun onTabSelected(index: Int, wasSelected: Boolean): Boolean {

View File

@ -4,20 +4,33 @@ import io.github.wulkanowy.ui.base.BaseView
interface MainView : BaseView { interface MainView : BaseView {
var startMenuIndex: Int
val isRootView: Boolean
val currentViewTitle: String?
val currentStackSize: Int?
fun initView() fun initView()
fun switchMenuView(position: Int) fun switchMenuView(position: Int)
fun setViewTitle(title: String) fun showHomeArrow(show: Boolean)
fun viewTitle(index: Int): String
fun currentMenuIndex(): Int
fun notifyMenuViewReselected() fun notifyMenuViewReselected()
interface MenuFragmentView { fun setViewTitle(title: String)
fun popView()
interface MainChildView {
fun onFragmentReselected() fun onFragmentReselected()
} }
interface TitledView {
val titleStringId: Int
}
} }

View File

@ -0,0 +1,68 @@
package io.github.wulkanowy.ui.main.about
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.mikepenz.aboutlibraries.LibsBuilder
import com.mikepenz.aboutlibraries.LibsFragmentCompat
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.main.MainView
import io.github.wulkanowy.utils.withOnExtraListener
import javax.inject.Inject
class AboutFragment : BaseFragment(), AboutView, MainView.TitledView {
@Inject
lateinit var presenter: AboutPresenter
@Inject
lateinit var fragmentCompat: LibsFragmentCompat
companion object {
fun newInstance() = AboutFragment()
}
override val titleStringId: Int
get() = R.string.about_title
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
presenter.onAttachView(this)
return Bundle().apply {
putSerializable("data", LibsBuilder()
.withAboutAppName(getString(R.string.app_name))
.withAboutVersionShown(true)
.withAboutIconShown(true)
.withLicenseShown(true)
.withAboutSpecial1(getString(R.string.about_source_code))
.withAboutSpecial2(getString(R.string.about_feedback))
.withCheckCachedDetection(false)
.withExcludedLibraries("fastadapter", "AndroidIconics", "gson",
"Jsoup", "Retrofit", "okio", "OkHttp")
.withOnExtraListener { presenter.onExtraSelect(it) })
}.let {
fragmentCompat.onCreateView(inflater.context, inflater, container, savedInstanceState, it)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
fragmentCompat.onViewCreated(view, savedInstanceState)
}
override fun openSourceWebView() {
startActivity(Intent.parseUri("https://github.com/wulkanowy/wulkanowy", 0))
}
override fun openIssuesWebView() {
startActivity(Intent.parseUri("https://github.com/wulkanowy/wulkanowy/issues", 0))
}
override fun onDestroyView() {
fragmentCompat.onDestroyView()
presenter.onDetachView()
super.onDestroyView()
}
}

View File

@ -0,0 +1,15 @@
package io.github.wulkanowy.ui.main.about
import com.mikepenz.aboutlibraries.LibsFragmentCompat
import dagger.Module
import dagger.Provides
import io.github.wulkanowy.di.scopes.PerFragment
@Module
class AboutModule {
@PerFragment
@Provides
fun provideLibsFragmentCompat() = LibsFragmentCompat()
}

View File

@ -0,0 +1,21 @@
package io.github.wulkanowy.ui.main.about
import com.mikepenz.aboutlibraries.Libs
import com.mikepenz.aboutlibraries.Libs.SpecialButton.SPECIAL1
import com.mikepenz.aboutlibraries.Libs.SpecialButton.SPECIAL2
import io.github.wulkanowy.data.ErrorHandler
import io.github.wulkanowy.ui.base.BasePresenter
import javax.inject.Inject
class AboutPresenter @Inject constructor(errorHandler: ErrorHandler) : BasePresenter<AboutView>(errorHandler) {
fun onExtraSelect(type: Libs.SpecialButton?) {
view?.run {
when (type) {
SPECIAL1 -> openSourceWebView()
SPECIAL2 -> openIssuesWebView()
else -> TODO()
}
}
}
}

View File

@ -0,0 +1,10 @@
package io.github.wulkanowy.ui.main.about
import io.github.wulkanowy.ui.base.BaseView
interface AboutView : BaseView {
fun openSourceWebView()
fun openIssuesWebView()
}

View File

@ -15,7 +15,7 @@ import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_attendance.* import kotlinx.android.synthetic.main.fragment_attendance.*
import javax.inject.Inject import javax.inject.Inject
class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MenuFragmentView { class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildView, MainView.TitledView {
@Inject @Inject
lateinit var presenter: AttendancePresenter lateinit var presenter: AttendancePresenter
@ -29,6 +29,12 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MenuFragment
fun newInstance() = AttendanceFragment() fun newInstance() = AttendanceFragment()
} }
override val titleStringId: Int
get() = R.string.attendance_title
override val isViewEmpty: Boolean
get() = attendanceAdapter.isEmpty
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_attendance, container, false) return inflater.inflate(R.layout.fragment_attendance, container, false)
} }
@ -65,8 +71,6 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MenuFragment
attendanceAdapter.clear() attendanceAdapter.clear()
} }
override fun isViewEmpty() = attendanceAdapter.isEmpty
override fun onFragmentReselected() { override fun onFragmentReselected() {
presenter.onViewReselected() presenter.onViewReselected()
} }
@ -109,4 +113,3 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MenuFragment
super.onDestroyView() super.onDestroyView()
} }
} }

View File

@ -3,6 +3,7 @@ package io.github.wulkanowy.ui.main.attendance
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.ErrorHandler import io.github.wulkanowy.data.ErrorHandler
import io.github.wulkanowy.data.repositories.AttendanceRepository import io.github.wulkanowy.data.repositories.AttendanceRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.SessionRepository import io.github.wulkanowy.data.repositories.SessionRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.utils.* import io.github.wulkanowy.utils.*
@ -17,7 +18,8 @@ class AttendancePresenter @Inject constructor(
private val errorHandler: ErrorHandler, private val errorHandler: ErrorHandler,
private val schedulers: SchedulersManager, private val schedulers: SchedulersManager,
private val attendanceRepository: AttendanceRepository, private val attendanceRepository: AttendanceRepository,
private val sessionRepository: SessionRepository private val sessionRepository: SessionRepository,
private val prefRepository: PreferencesRepository
) : BasePresenter<AttendanceView>(errorHandler) { ) : BasePresenter<AttendanceView>(errorHandler) {
lateinit var currentDate: LocalDate lateinit var currentDate: LocalDate
@ -61,6 +63,10 @@ class AttendancePresenter @Inject constructor(
.delay(200, MILLISECONDS) .delay(200, MILLISECONDS)
.map { it.single { semester -> semester.current } } .map { it.single { semester -> semester.current } }
.flatMap { attendanceRepository.getAttendance(it, date, date, forceRefresh) } .flatMap { attendanceRepository.getAttendance(it, date, date, forceRefresh) }
.map { list ->
if (prefRepository.showPresent) list
else list.filter { !it.presence }
}
.map { items -> items.map { AttendanceItem(it) } } .map { items -> items.map { AttendanceItem(it) } }
.subscribeOn(schedulers.backgroundThread()) .subscribeOn(schedulers.backgroundThread())
.observeOn(schedulers.mainThread()) .observeOn(schedulers.mainThread())
@ -77,7 +83,7 @@ class AttendancePresenter @Inject constructor(
showContent(it.isNotEmpty()) showContent(it.isNotEmpty())
} }
}) { }) {
view?.run { showEmpty(isViewEmpty()) } view?.run { showEmpty(isViewEmpty) }
errorHandler.proceed(it) errorHandler.proceed(it)
} }
) )

View File

@ -5,6 +5,8 @@ import io.github.wulkanowy.ui.base.BaseView
interface AttendanceView : BaseView { interface AttendanceView : BaseView {
val isViewEmpty: Boolean
fun initView() fun initView()
fun updateData(data: List<AttendanceItem>) fun updateData(data: List<AttendanceItem>)
@ -13,8 +15,6 @@ interface AttendanceView : BaseView {
fun clearData() fun clearData()
fun isViewEmpty(): Boolean
fun hideRefresh() fun hideRefresh()
fun showEmpty(show: Boolean) fun showEmpty(show: Boolean)

View File

@ -16,7 +16,7 @@ import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_exam.* import kotlinx.android.synthetic.main.fragment_exam.*
import javax.inject.Inject import javax.inject.Inject
class ExamFragment : BaseFragment(), ExamView, MainView.MenuFragmentView { class ExamFragment : BaseFragment(), ExamView, MainView.MainChildView, MainView.TitledView {
@Inject @Inject
lateinit var presenter: ExamPresenter lateinit var presenter: ExamPresenter
@ -26,9 +26,16 @@ class ExamFragment : BaseFragment(), ExamView, MainView.MenuFragmentView {
companion object { companion object {
private const val SAVED_DATE_KEY = "CURRENT_DATE" private const val SAVED_DATE_KEY = "CURRENT_DATE"
fun newInstance() = ExamFragment() fun newInstance() = ExamFragment()
} }
override val titleStringId: Int
get() = R.string.exam_title
override val isViewEmpty: Boolean
get() = examAdapter.isEmpty
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_exam, container, false) return inflater.inflate(R.layout.fragment_exam, container, false)
} }
@ -68,8 +75,6 @@ class ExamFragment : BaseFragment(), ExamView, MainView.MenuFragmentView {
examAdapter.clear() examAdapter.clear()
} }
override fun isViewEmpty() = examAdapter.isEmpty
override fun onFragmentReselected() { override fun onFragmentReselected() {
presenter.onViewReselected() presenter.onViewReselected()
} }

View File

@ -80,7 +80,7 @@ class ExamPresenter @Inject constructor(
showContent(it.isNotEmpty()) showContent(it.isNotEmpty())
} }
}) { }) {
view?.run { showEmpty(isViewEmpty()) } view?.run { showEmpty(isViewEmpty) }
errorHandler.proceed(it) errorHandler.proceed(it)
}) })
} }

View File

@ -5,6 +5,8 @@ import io.github.wulkanowy.ui.base.BaseView
interface ExamView : BaseView { interface ExamView : BaseView {
val isViewEmpty: Boolean
fun initView() fun initView()
fun updateData(data: List<ExamItem>) fun updateData(data: List<ExamItem>)
@ -13,8 +15,6 @@ interface ExamView : BaseView {
fun clearData() fun clearData()
fun isViewEmpty(): Boolean
fun hideRefresh() fun hideRefresh()
fun showEmpty(show: Boolean) fun showEmpty(show: Boolean)

View File

@ -15,7 +15,7 @@ import io.github.wulkanowy.utils.setOnSelectPageListener
import kotlinx.android.synthetic.main.fragment_grade.* import kotlinx.android.synthetic.main.fragment_grade.*
import javax.inject.Inject import javax.inject.Inject
class GradeFragment : BaseFragment(), GradeView, MainView.MenuFragmentView { class GradeFragment : BaseFragment(), GradeView, MainView.MainChildView, MainView.TitledView {
@Inject @Inject
lateinit var presenter: GradePresenter lateinit var presenter: GradePresenter
@ -29,6 +29,12 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MenuFragmentView {
fun newInstance() = GradeFragment() fun newInstance() = GradeFragment()
} }
override val titleStringId: Int
get() = R.string.grade_title
override val currentPageIndex: Int
get() = gradeViewPager.currentItem
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setHasOptionsMenu(true) setHasOptionsMenu(true)
@ -93,8 +99,6 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MenuFragmentView {
} }
} }
override fun currentPageIndex() = gradeViewPager.currentItem
fun onChildRefresh() { fun onChildRefresh() {
presenter.onChildViewRefresh() presenter.onChildViewRefresh()
} }
@ -124,4 +128,4 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MenuFragmentView {
super.onDestroyView() super.onDestroyView()
presenter.onDetachView() presenter.onDetachView()
} }
} }

View File

@ -32,7 +32,7 @@ class GradePresenter @Inject constructor(
} }
fun onViewReselected() { fun onViewReselected() {
view?.run { notifyChildParentReselected(currentPageIndex()) } view?.run { notifyChildParentReselected(currentPageIndex) }
} }
fun onSemesterSwitch(): Boolean { fun onSemesterSwitch(): Boolean {
@ -46,20 +46,20 @@ class GradePresenter @Inject constructor(
loadedSemesterId.clear() loadedSemesterId.clear()
view?.let { view?.let {
notifyChildrenSemesterChange() notifyChildrenSemesterChange()
loadChild(it.currentPageIndex()) loadChild(it.currentPageIndex)
} }
} }
} }
fun onChildViewRefresh() { fun onChildViewRefresh() {
view?.let { loadChild(it.currentPageIndex(), forceRefresh = true) } view?.let { loadChild(it.currentPageIndex, forceRefresh = true) }
} }
fun onChildViewLoaded(semesterId: Int) { fun onChildViewLoaded(semesterId: Int) {
view?.apply { view?.apply {
showContent(true) showContent(true)
showProgress(false) showProgress(false)
loadedSemesterId[currentPageIndex()] = semesterId loadedSemesterId[currentPageIndex] = semesterId
} }
} }
@ -78,7 +78,7 @@ class GradePresenter @Inject constructor(
.subscribeOn(schedulers.backgroundThread()) .subscribeOn(schedulers.backgroundThread())
.observeOn(schedulers.mainThread()) .observeOn(schedulers.mainThread())
.subscribe({ _ -> .subscribe({ _ ->
view?.let { loadChild(it.currentPageIndex()) } view?.let { loadChild(it.currentPageIndex) }
}) { errorHandler.proceed(it) }) }) { errorHandler.proceed(it) })
} }

View File

@ -4,9 +4,9 @@ import io.github.wulkanowy.ui.base.BaseView
interface GradeView : BaseView { interface GradeView : BaseView {
fun initView() val currentPageIndex: Int
fun currentPageIndex(): Int fun initView()
fun showContent(show: Boolean) fun showContent(show: Boolean)

View File

@ -31,6 +31,18 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh
fun newInstance() = GradeDetailsFragment() fun newInstance() = GradeDetailsFragment()
} }
override val emptyAverageString: String
get() = getString(R.string.grade_no_average)
override val averageString: String
get() = getString(R.string.grade_average)
override val weightString: String
get() = getString(R.string.grade_weight)
override val isViewEmpty
get() = gradeDetailsAdapter.isEmpty
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_grade_details, container, false) return inflater.inflate(R.layout.fragment_grade_details, container, false)
} }
@ -80,7 +92,9 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh
return gradeDetailsAdapter.getExpandableOf(item) return gradeDetailsAdapter.getExpandableOf(item)
} }
override fun isViewEmpty() = gradeDetailsAdapter.isEmpty override fun getGradeNumberString(number: Int): String {
return resources.getQuantityString(R.plurals.grade_number_item, number, number)
}
override fun showProgress(show: Boolean) { override fun showProgress(show: Boolean) {
gradeDetailsProgress.visibility = if (show) VISIBLE else GONE gradeDetailsProgress.visibility = if (show) VISIBLE else GONE
@ -122,14 +136,6 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh
(parentFragment as? GradeFragment)?.onChildRefresh() (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() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
presenter.onDetachView() presenter.onDetachView()

View File

@ -42,7 +42,7 @@ class GradeDetailsPresenter @Inject constructor(
updateData(it) updateData(it)
} }
}) { }) {
view?.run { showEmpty(isViewEmpty()) } view?.run { showEmpty(isViewEmpty) }
errorHandler.proceed(it) errorHandler.proceed(it)
}) })
} }
@ -72,7 +72,7 @@ class GradeDetailsPresenter @Inject constructor(
fun onParentViewReselected() { fun onParentViewReselected() {
view?.run { view?.run {
if (!isViewEmpty()) resetView() if (!isViewEmpty) resetView()
} }
} }
@ -93,13 +93,13 @@ class GradeDetailsPresenter @Inject constructor(
GradeDetailsHeader( GradeDetailsHeader(
subject = it.key, subject = it.key,
average = formatAverage(average), average = formatAverage(average),
number = view?.gradeNumberString(it.value.size).orEmpty(), number = view?.getGradeNumberString(it.value.size).orEmpty(),
newGrades = it.value.filter { grade -> grade.isNew }.size newGrades = it.value.filter { grade -> grade.isNew }.size
).apply { ).apply {
subItems = it.value.map { item -> subItems = it.value.map { item ->
GradeDetailsItem( GradeDetailsItem(
grade = item, grade = item,
weightString = view?.weightString().orEmpty(), weightString = view?.weightString.orEmpty(),
valueColor = item.valueColor valueColor = item.valueColor
) )
} }
@ -110,8 +110,8 @@ class GradeDetailsPresenter @Inject constructor(
private fun formatAverage(average: Double): String { private fun formatAverage(average: Double): String {
return view?.run { return view?.run {
if (average == 0.0) emptyAverageString() if (average == 0.0) emptyAverageString
else averageString().format(average) else averageString.format(average)
}.orEmpty() }.orEmpty()
} }

View File

@ -8,20 +8,24 @@ import io.github.wulkanowy.ui.base.BaseView
interface GradeDetailsView : BaseView { interface GradeDetailsView : BaseView {
val isViewEmpty: Boolean
val emptyAverageString: String
val averageString: String
val weightString: String
fun initView() fun initView()
fun updateData(data: List<GradeDetailsHeader>) fun updateData(data: List<GradeDetailsHeader>)
fun updateItem(item: AbstractFlexibleItem<*>) fun updateItem(item: AbstractFlexibleItem<*>)
fun getHeaderOfItem(item: AbstractFlexibleItem<*>): IExpandable<*, out IFlexible<*>>?
fun resetView() fun resetView()
fun clearView() fun clearView()
fun isViewEmpty(): Boolean
fun showGradeDialog(grade: Grade) fun showGradeDialog(grade: Grade)
fun showContent(show: Boolean) fun showContent(show: Boolean)
@ -32,15 +36,11 @@ interface GradeDetailsView : BaseView {
fun showRefresh(show: Boolean) fun showRefresh(show: Boolean)
fun emptyAverageString(): String
fun averageString(): String
fun gradeNumberString(number: Int): String
fun weightString(): String
fun notifyParentDataLoaded(semesterId: Int) fun notifyParentDataLoaded(semesterId: Int)
fun notifyParentRefresh() fun notifyParentRefresh()
fun getGradeNumberString(number: Int): String
fun getHeaderOfItem(item: AbstractFlexibleItem<*>): IExpandable<*, out IFlexible<*>>?
} }

View File

@ -27,6 +27,15 @@ class GradeSummaryFragment : BaseFragment(), GradeSummaryView, GradeView.GradeCh
fun newInstance() = GradeSummaryFragment() fun newInstance() = GradeSummaryFragment()
} }
override val isViewEmpty
get() = gradeSummaryAdapter.isEmpty
override val predictedString
get() = getString(R.string.grade_summary_predicted_grade)
override val finalString
get() = getString(R.string.grade_summary_final_grade)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_grade_summary, container, false) return inflater.inflate(R.layout.fragment_grade_summary, container, false)
} }
@ -63,8 +72,6 @@ class GradeSummaryFragment : BaseFragment(), GradeSummaryView, GradeView.GradeCh
gradeSummaryAdapter.smoothScrollToPosition(0) gradeSummaryAdapter.smoothScrollToPosition(0)
} }
override fun isViewEmpty() = gradeSummaryAdapter.isEmpty
override fun showContent(show: Boolean) { override fun showContent(show: Boolean) {
gradeSummaryRecycler.visibility = if (show) VISIBLE else INVISIBLE gradeSummaryRecycler.visibility = if (show) VISIBLE else INVISIBLE
} }
@ -101,10 +108,6 @@ class GradeSummaryFragment : BaseFragment(), GradeSummaryView, GradeView.GradeCh
(parentFragment as? GradeFragment)?.onChildRefresh() (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() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
presenter.onDetachView() presenter.onDetachView()

View File

@ -61,7 +61,7 @@ class GradeSummaryPresenter @Inject constructor(
updateDataSet(it.first, it.second) updateDataSet(it.first, it.second)
} }
}) { }) {
view?.run { showEmpty(isViewEmpty()) } view?.run { showEmpty(isViewEmpty) }
errorHandler.proceed(it) errorHandler.proceed(it)
}) })
} }
@ -72,7 +72,7 @@ class GradeSummaryPresenter @Inject constructor(
fun onParentViewReselected() { fun onParentViewReselected() {
view?.run { view?.run {
if (!isViewEmpty()) resetView() if (!isViewEmpty) resetView()
} }
} }
@ -97,11 +97,11 @@ class GradeSummaryPresenter @Inject constructor(
).let { ).let {
listOf(GradeSummaryItem( listOf(GradeSummaryItem(
header = it, header = it,
title = view?.predictedString().orEmpty(), title = view?.predictedString.orEmpty(),
grade = gradeSummary.predictedGrade grade = gradeSummary.predictedGrade
), GradeSummaryItem( ), GradeSummaryItem(
header = it, header = it,
title = view?.finalString().orEmpty(), title = view?.finalString.orEmpty(),
grade = gradeSummary.finalGrade grade = gradeSummary.finalGrade
)) ))
} }

View File

@ -4,6 +4,12 @@ import io.github.wulkanowy.ui.base.BaseView
interface GradeSummaryView : BaseView { interface GradeSummaryView : BaseView {
val isViewEmpty: Boolean
val predictedString: String
val finalString: String
fun initView() fun initView()
fun updateDataSet(data: List<GradeSummaryItem>, header: GradeSummaryScrollableHeader) fun updateDataSet(data: List<GradeSummaryItem>, header: GradeSummaryScrollableHeader)
@ -12,8 +18,6 @@ interface GradeSummaryView : BaseView {
fun clearView() fun clearView()
fun isViewEmpty(): Boolean
fun showProgress(show: Boolean) fun showProgress(show: Boolean)
fun showRefresh(show: Boolean) fun showRefresh(show: Boolean)
@ -22,10 +26,6 @@ interface GradeSummaryView : BaseView {
fun showEmpty(show: Boolean) fun showEmpty(show: Boolean)
fun predictedString(): String
fun finalString(): String
fun notifyParentDataLoaded(semesterId: Int) fun notifyParentDataLoaded(semesterId: Int)
fun notifyParentRefresh() fun notifyParentRefresh()

View File

@ -1,20 +1,95 @@
package io.github.wulkanowy.ui.main.more package io.github.wulkanowy.ui.main.more
import android.graphics.drawable.Drawable
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.content.ContextCompat
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.R
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.main.MainActivity
import io.github.wulkanowy.ui.main.MainView
import io.github.wulkanowy.ui.main.about.AboutFragment
import io.github.wulkanowy.ui.main.settings.SettingsFragment
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_more.*
import javax.inject.Inject
class MoreFragment : BaseFragment() { class MoreFragment : BaseFragment(), MoreView, MainView.TitledView, MainView.MainChildView {
@Inject
lateinit var presenter: MorePresenter
@Inject
lateinit var moreAdapter: FlexibleAdapter<AbstractFlexibleItem<*>>
companion object { companion object {
fun newInstance() = MoreFragment() fun newInstance() = MoreFragment()
} }
override val titleStringId: Int
get() = R.string.more_title
override val settingsRes: Pair<String, Drawable?>?
get() {
return context?.run {
getString(R.string.settings_title) to
ContextCompat.getDrawable(this, R.drawable.ic_more_settings_24dp)
}
}
override val aboutRes: Pair<String, Drawable?>?
get() {
return context?.run {
getString(R.string.about_title) to
ContextCompat.getDrawable(this, R.drawable.ic_more_about_24dp)
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_attendance, container, false) return inflater.inflate(R.layout.fragment_more, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
presenter.onAttachView(this)
}
override fun initView() {
moreAdapter.run { setOnItemClickListener { presenter.onItemSelected(getItem(it)) } }
moreRecycler.apply {
layoutManager = SmoothScrollLinearLayoutManager(context)
adapter = moreAdapter
}
}
override fun onFragmentReselected() {
presenter.onViewReselected()
}
override fun updateData(data: List<MoreItem>) {
moreAdapter.updateDataSet(data)
}
override fun openSettingsView() {
(activity as? MainActivity)?.pushView(SettingsFragment.newInstance())
}
override fun openAboutView() {
(activity as? MainActivity)?.pushView(AboutFragment.newInstance())
}
override fun popView() {
(activity as? MainActivity)?.popView()
}
override fun onDestroyView() {
presenter.onDetachView()
super.onDestroyView()
} }
} }

View File

@ -0,0 +1,51 @@
package io.github.wulkanowy.ui.main.more
import android.graphics.drawable.Drawable
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.item_more.*
class MoreItem(val title: String, private val drawable: Drawable?)
: AbstractFlexibleItem<MoreItem.ViewHolder>() {
override fun getLayoutRes() = R.layout.item_more
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 {
moreItemTitle.text = title
moreItemImage.setImageDrawable(drawable)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as MoreItem
if (title != other.title) return false
return true
}
override fun hashCode(): Int {
return title.hashCode()
}
class ViewHolder(view: View?, adapter: FlexibleAdapter<*>?)
: FlexibleViewHolder(view, adapter), LayoutContainer {
override val containerView: View?
get() = contentView
}
}

View File

@ -0,0 +1,39 @@
package io.github.wulkanowy.ui.main.more
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.ErrorHandler
import io.github.wulkanowy.ui.base.BasePresenter
import javax.inject.Inject
class MorePresenter @Inject constructor(errorHandler: ErrorHandler)
: BasePresenter<MoreView>(errorHandler) {
override fun onAttachView(view: MoreView) {
super.onAttachView(view)
view.initView()
loadData()
}
fun onItemSelected(item: AbstractFlexibleItem<*>?) {
if (item is MoreItem) {
view?.run {
when (item.title) {
settingsRes?.first -> openSettingsView()
aboutRes?.first -> openAboutView()
}
}
}
}
fun onViewReselected() {
view?.popView()
}
private fun loadData() {
view?.run {
updateData(listOfNotNull(
settingsRes?.let { MoreItem(it.first, it.second) },
aboutRes?.let { MoreItem(it.first, it.second) }))
}
}
}

View File

@ -0,0 +1,21 @@
package io.github.wulkanowy.ui.main.more
import android.graphics.drawable.Drawable
import io.github.wulkanowy.ui.base.BaseView
interface MoreView : BaseView {
val settingsRes: Pair<String, Drawable?>?
val aboutRes: Pair<String, Drawable?>?
fun initView()
fun updateData(data: List<MoreItem>)
fun openSettingsView()
fun openAboutView()
fun popView()
}

View File

@ -0,0 +1,20 @@
package io.github.wulkanowy.ui.main.settings
import android.os.Bundle
import com.takisoft.preferencex.PreferenceFragmentCompat
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.main.MainView
class SettingsFragment : PreferenceFragmentCompat(), MainView.TitledView {
companion object {
fun newInstance() = SettingsFragment()
}
override val titleStringId: Int
get() = R.string.settings_title
override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.scheme_preferences)
}
}

View File

@ -15,7 +15,7 @@ import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_timetable.* import kotlinx.android.synthetic.main.fragment_timetable.*
import javax.inject.Inject import javax.inject.Inject
class TimetableFragment : BaseFragment(), TimetableView, MainView.MenuFragmentView { class TimetableFragment : BaseFragment(), TimetableView, MainView.MainChildView, MainView.TitledView {
@Inject @Inject
lateinit var presenter: TimetablePresenter lateinit var presenter: TimetablePresenter
@ -29,6 +29,12 @@ class TimetableFragment : BaseFragment(), TimetableView, MainView.MenuFragmentVi
fun newInstance() = TimetableFragment() fun newInstance() = TimetableFragment()
} }
override val titleStringId: Int
get() = R.string.timetable_title
override val roomString: String
get() = getString(R.string.timetable_room)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_timetable, container, false) return inflater.inflate(R.layout.fragment_timetable, container, false)
} }
@ -99,8 +105,6 @@ class TimetableFragment : BaseFragment(), TimetableView, MainView.MenuFragmentVi
TimetableDialog.newInstance(lesson).show(fragmentManager, lesson.toString()) TimetableDialog.newInstance(lesson).show(fragmentManager, lesson.toString())
} }
override fun roomString() = getString(R.string.timetable_room)
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay()) outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay())

View File

@ -61,7 +61,7 @@ class TimetablePresenter @Inject constructor(
.delay(200, MILLISECONDS) .delay(200, MILLISECONDS)
.map { it.single { semester -> semester.current } } .map { it.single { semester -> semester.current } }
.flatMap { timetableRepository.getTimetable(it, currentDate, currentDate, forceRefresh) } .flatMap { timetableRepository.getTimetable(it, currentDate, currentDate, forceRefresh) }
.map { items -> items.map { TimetableItem(it, view?.roomString().orEmpty()) } } .map { items -> items.map { TimetableItem(it, view?.roomString.orEmpty()) } }
.subscribeOn(schedulers.backgroundThread()) .subscribeOn(schedulers.backgroundThread())
.observeOn(schedulers.mainThread()) .observeOn(schedulers.mainThread())
.doFinally { .doFinally {

View File

@ -5,6 +5,8 @@ import io.github.wulkanowy.ui.base.BaseView
interface TimetableView : BaseView { interface TimetableView : BaseView {
val roomString: String
fun initView() fun initView()
fun updateData(data: List<TimetableItem>) fun updateData(data: List<TimetableItem>)
@ -28,6 +30,4 @@ interface TimetableView : BaseView {
fun showNextButton(show: Boolean) fun showNextButton(show: Boolean)
fun showTimetableDialog(lesson: Timetable) fun showTimetableDialog(lesson: Timetable)
fun roomString(): String
} }

View File

@ -3,11 +3,18 @@ package io.github.wulkanowy.utils
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.ncapdevi.fragnav.FragNavController import com.ncapdevi.fragnav.FragNavController
inline fun FragNavController.setOnTabTransactionListener(crossinline listener: (index: Int) -> Unit) { inline fun FragNavController.setOnViewChangeListener(crossinline listener: (fragment: Fragment?) -> Unit) {
transactionListener = object : FragNavController.TransactionListener { transactionListener = object : FragNavController.TransactionListener {
override fun onFragmentTransaction(fragment: Fragment?, transactionType: FragNavController.TransactionType) {} override fun onFragmentTransaction(fragment: Fragment?, transactionType: FragNavController.TransactionType) {
listener(fragment)
}
override fun onTabTransaction(fragment: Fragment?, index: Int) { override fun onTabTransaction(fragment: Fragment?, index: Int) {
listener(index) listener(fragment)
} }
} }
} }
fun FragNavController.safelyPopFragment() {
if (!isRootFragment) popFragment()
}

View File

@ -0,0 +1,16 @@
package io.github.wulkanowy.utils
import android.view.View
import com.mikepenz.aboutlibraries.Libs
import com.mikepenz.aboutlibraries.LibsBuilder
import com.mikepenz.aboutlibraries.LibsConfiguration
inline fun LibsBuilder.withOnExtraListener(crossinline listener: (Libs.SpecialButton?) -> Unit): LibsBuilder {
withListener(object : LibsConfiguration.LibsListenerImpl() {
override fun onExtraClicked(v: View?, specialButton: Libs.SpecialButton?): Boolean {
listener(specialButton)
return true
}
})
return this
}

View File

@ -4,10 +4,10 @@
android:viewportHeight="24" android:viewportHeight="24"
android:viewportWidth="24"> android:viewportWidth="24">
<path <path
android:fillColor="#FFF" android:fillColor="#000"
android:pathData="M11,7h2v2h-2zM11,11h2v6h-2z" /> android:pathData="M11,7h2v2h-2zM11,11h2v6h-2z" />
<path <path
android:fillColor="#FFF" android:fillColor="#000"
android:pathData="M12,2a10,10 0,1 0,0 20,10 10,0 0,0 0,-20zM12,20a8,8 0,1 1,0 -16,8 8,0 0,1 android:pathData="M12,2a10,10 0,1 0,0 20,10 10,0 0,0 0,-20zM12,20a8,8 0,1 1,0 -16,8 8,0 0,1
0,16z" /> 0,16z" />
</vector> </vector>

View File

@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#000"
android:pathData="M19.43 12.98a7.8 7.8 0 0 0 0-1.96l2.11-1.65a0.5 0.5 0 0 0
0.12-0.64l-2-3.46c-0.12-0.22-0.39-0.3-0.61-0.22l-2.49 1a7.3 7.3 0 0 0-1.69-0.98l-0.38-2.65A0.49
0.49 0 0 0 14 2h-4a0.49 0.49 0 0 0-0.49 0.42l-0.38 2.65c-0.61 0.25-1.17 0.59-1.69 0.98l
-2.49-1a 0.49 0.49 0 0 0-0.61 0.22l-2 3.46a0.5 0.5 0 0 0 0.12 0.64l2.11 1.65a7.93 7.93
0 0 0 0 1.96l-2.11 1.65a0.5 0.5 0 0 0-0.12 0.64l2 3.46c0.12 0.22 0.39 0.3 0.61 0.22l2.49-1c0.52
0.4 1.08 0.73 1.69 0.98l 0.38 2.65c 0.03 0.24 0.24 0.42 0.49 0.42h4c0.25 0 0.46-0.18 0.49-0.42l
0.38-2.65a7.68 7.68 0 0 0 1.69-0.98l2.49 1c0.23 0.09 0.49 0 0.61-0.22l2-3.46a0.5 0.5
0 0 0-0.12-0.64l-2.11-1.65zM12 15.5a3.5 3.5 0 1 1 0-7 3.5 3.5 0 0 1 0 7z" />
</vector>

View File

@ -9,6 +9,7 @@
android:id="@+id/mainAppBarContainer" android:id="@+id/mainAppBarContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:theme="@style/WulkanowyTheme.ActionBar"
app:elevation="0dp"> app:elevation="0dp">
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/moreRecycler"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="56dp"
android:background="?attr/selectableItemBackground"
android:orientation="horizontal"
android:paddingLeft="16dp"
android:paddingTop="8dp"
android:paddingRight="16dp"
android:paddingBottom="8dp">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/moreItemImage"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_vertical"
app:srcCompat="@drawable/ic_more_settings_24dp" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/moreItemTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="32dp"
android:layout_marginLeft="32dp"
android:text="@string/app_name"
android:textSize="16sp" />
</LinearLayout>

View File

@ -10,6 +10,7 @@
<string name="timetable_title">Plan lekcji</string> <string name="timetable_title">Plan lekcji</string>
<string name="settings_title">Ustawienia</string> <string name="settings_title">Ustawienia</string>
<string name="more_title">Więcej</string> <string name="more_title">Więcej</string>
<string name="about_title">O aplikacji</string>
<!--Login form--> <!--Login form-->
@ -92,6 +93,9 @@
<string name="exam_type">Typ</string> <string name="exam_type">Typ</string>
<string name="exam_entry_date">Data wpisu</string> <string name="exam_entry_date">Data wpisu</string>
<!--About-->
<string name="about_source_code">Kod źródłowy</string>
<string name="about_feedback">Zgłoś błąd</string>
<!--Generic--> <!--Generic-->
<string name="all_description">Opis</string> <string name="all_description">Opis</string>
@ -130,22 +134,11 @@
<string name="pref_services_interval">Interwał aktualizacji</string> <string name="pref_services_interval">Interwał aktualizacji</string>
<string name="pref_services_wifi">Tylko WiFi</string> <string name="pref_services_wifi">Tylko WiFi</string>
<string name="pref_about_description">Informacje o Wulkanowym</string>
<string name="pref_about_version">Wersja</string>
<string name="pref_about_osl">Licencje open source</string>
<string name="pref_about_osl_summary">Szczegóły licencji na oprogramowanie open source</string>
<string name="pref_about_support">Kod źródłowy i feedback</string>
<string name="about_programmer_step1">Nie, nie zostaniesz programistą!</string>
<string name="about_programmer_step2">Musisz bardziej się postarać!</string>
<string name="about_programmer_step3">Kliknij jeszcze parę razy</string>
<string name="about_programmer_description">Odwiedź zakładkę Kod źródłowy i pokaż jaki z ciebie programista!</string>
<string name="pref_restart">Wymagany restart</string> <string name="pref_restart">Wymagany restart</string>
<!--Grade notify--> <!--Grade notify-->
<string name="notify_grade_chanell">Nowe oceny</string> <string name="notify_grade_chanel">Nowe oceny</string>
<plurals name="notify_grade_new_items"> <plurals name="notify_grade_new_items">
<item quantity="one">Dostałeś %1$d ocenę</item> <item quantity="one">Dostałeś %1$d ocenę</item>
<item quantity="few">"Dostałeś %1$d oceny</item> <item quantity="few">"Dostałeś %1$d oceny</item>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="define_WulkanowyApi" translatable="false" />
<string name="library_WulkanowyApi_author" translatable="false">Wulkanowy</string>
<string name="library_WulkanowyApi_authorWebsite" translatable="false">https://github.com/wulkanowy</string>
<string name="library_WulkanowyApi_libraryName" translatable="false">UONET+ Scraping API</string>
<string name="library_WulkanowyApi_libraryDescription" translatable="false">The UONET+ client using web scraping</string>
<string name="library_WulkanowyApi_libraryWebsite" translatable="false">https://github.com/wulkanowy/api</string>
<string name="library_WulkanowyApi_libraryVersion" translatable="false">Development</string>
<string name="library_WulkanowyApi_isOpenSource" translatable="false">true</string>
<string name="library_WulkanowyApi_repositoryLink" translatable="false">https://github.com/wulkanowy/api</string>
<string name="library_WulkanowyApi_licenseId" translatable="false">apache_2_0</string>
</resources>

View File

@ -10,6 +10,7 @@
<string name="timetable_title">Timetable</string> <string name="timetable_title">Timetable</string>
<string name="settings_title">Settings</string> <string name="settings_title">Settings</string>
<string name="more_title">More</string> <string name="more_title">More</string>
<string name="about_title">About</string>
<!--Login form--> <!--Login form-->
@ -87,6 +88,10 @@
<string name="exam_type">Type</string> <string name="exam_type">Type</string>
<string name="exam_entry_date">Entry date</string> <string name="exam_entry_date">Entry date</string>
<!--About-->
<string name="about_source_code">Source code</string>
<string name="about_feedback">Report a bug</string>
<!--Generic--> <!--Generic-->
<string name="all_description">Description</string> <string name="all_description">Description</string>
@ -125,22 +130,11 @@
<string name="pref_services_interval">Updates interval</string> <string name="pref_services_interval">Updates interval</string>
<string name="pref_services_wifi">Only WiFi</string> <string name="pref_services_wifi">Only WiFi</string>
<string name="pref_about_description">About Wulkanowy</string>
<string name="pref_about_version">Version</string>
<string name="pref_about_osl">Open source licences</string>
<string name="pref_about_osl_summary">License details for open source software</string>
<string name="pref_about_support">Source code &amp; feedback</string>
<string name="about_programmer_step1">No, you will not become a programmer!</string>
<string name="about_programmer_step2">You must try harder!</string>
<string name="about_programmer_step3">Click a few more times</string>
<string name="about_programmer_description">Visit the Source code tab and show how good a programmer you are!</string>
<string name="pref_restart">Restart required</string> <string name="pref_restart">Restart required</string>
<!--Grade notify--> <!--Grade notify-->
<string name="notify_grade_chanell">New grades</string> <string name="notify_grade_chanel">New grades</string>
<plurals name="notify_grade_new_items"> <plurals name="notify_grade_new_items">
<item quantity="one">You received %1$d grade</item> <item quantity="one">You received %1$d grade</item>
<item quantity="other">You received %1$d grades</item> <item quantity="other">You received %1$d grades</item>

View File

@ -1,6 +1,6 @@
<resources> <resources>
<style name="WulkanowyTheme" parent="@style/Base.Theme.AppCompat.Light"> <style name="WulkanowyTheme" parent="@style/Theme.AppCompat.Light">
<item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorPrimary</item> <item name="colorAccent">@color/colorPrimary</item>
@ -13,7 +13,6 @@
<item name="titleTextColor">@android:color/primary_text_dark</item> <item name="titleTextColor">@android:color/primary_text_dark</item>
<item name="subtitleTextColor">@android:color/primary_text_dark</item> <item name="subtitleTextColor">@android:color/primary_text_dark</item>
<item name="android:colorBackground">@android:color/white</item> <item name="android:colorBackground">@android:color/white</item>
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
<item name="bottomNavBackground">@color/bottom_nav_background_inverse</item> <item name="bottomNavBackground">@color/bottom_nav_background_inverse</item>
<item name="android:windowAnimationStyle">@null</item> <item name="android:windowAnimationStyle">@null</item>
</style> </style>
@ -27,12 +26,7 @@
<item name="windowNoTitle">true</item> <item name="windowNoTitle">true</item>
</style> </style>
<style name="DialogFragmentTheme" parent="Theme.AppCompat.DayNight.Dialog.Alert"> <style name="WulkanowyTheme.ActionBar" parent="WulkanowyTheme">
<item name="android:textColorTertiary">@android:color/primary_text_light</item> <item name="colorControlNormal">@android:color/white</item>
<item name="windowActionBar">false</item>
<item name="windowNoTitle">false</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">false</item>
</style> </style>
</resources> </resources>

View File

@ -1,67 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
<PreferenceCategory android:title="@string/pref_view_header"> xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory
android:title="@string/pref_view_header"
app:iconSpaceReserved="false">
<ListPreference <ListPreference
android:defaultValue="0" android:defaultValue="0"
android:entries="@array/startup_tab_entries" android:entries="@array/startup_tab_entries"
android:entryValues="@array/startup_tab_value" android:entryValues="@array/startup_tab_value"
android:key="startup_tab" android:key="start_menu"
android:summary="%s" android:summary="%s"
android:title="@string/pref_view_list" /> android:title="@string/pref_view_list"
<SwitchPreference app:iconSpaceReserved="false" />
android:defaultValue="false"
android:key="grades_summary"
android:summary="@string/pref_restart"
android:title="@string/pref_view_summary" />
<SwitchPreference <SwitchPreference
android:defaultValue="true" android:defaultValue="true"
android:key="attendance_present" android:key="attendance_present"
android:summary="@string/pref_restart" android:title="@string/pref_view_present"
android:title="@string/pref_view_present" /> app:iconSpaceReserved="false" />
<ListPreference
android:defaultValue="1"
android:entries="@array/theme_entries"
android:entryValues="@array/theme_values"
android:key="theme"
android:summary="%s"
android:title="@string/pref_view_theme_dark" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/pref_services_header">
<SwitchPreference
android:defaultValue="true"
android:key="services_enable"
android:title="@string/pref_services_switch" />
<ListPreference
android:defaultValue="60"
android:dependency="services_enable"
android:entries="@array/services_interval_entries"
android:entryValues="@array/services_interval_value"
android:key="services_interval"
android:summary="%s"
android:title="@string/pref_services_interval" />
<SwitchPreference
android:defaultValue="false"
android:dependency="services_enable"
android:key="services_disable_mobile"
android:title="@string/pref_services_wifi" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/pref_notify_header">
<SwitchPreference
android:defaultValue="true"
android:dependency="services_enable"
android:key="notify_enable"
android:title="@string/pref_notify_switch" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/pref_about_description">
<Preference
android:key="about_version"
android:title="@string/pref_about_version" />
<Preference
android:key="about_osl"
android:summary="@string/pref_about_osl_summary"
android:title="@string/pref_about_osl" />
<Preference
android:key="about_repo"
android:title="@string/pref_about_support" />
</PreferenceCategory> </PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>

View File

@ -21,6 +21,7 @@ class LoginPresenterTest {
fun initPresenter() { fun initPresenter() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
clearInvocations(loginView) clearInvocations(loginView)
presenter = LoginPresenter(errorHandler) presenter = LoginPresenter(errorHandler)
presenter.onAttachView(loginView) presenter.onAttachView(loginView)
} }
@ -49,7 +50,7 @@ class LoginPresenterTest {
@Test @Test
fun onBackPressedTest() { fun onBackPressedTest() {
clearInvocations(loginView) clearInvocations(loginView)
doReturn(1).`when`(loginView).currentViewPosition() doReturn(1).`when`(loginView).currentViewIndex
presenter.onBackPressed { } presenter.onBackPressed { }
verify(loginView).switchView(0) verify(loginView).switchView(0)
verify(loginView).hideActionBar() verify(loginView).hideActionBar()
@ -58,7 +59,7 @@ class LoginPresenterTest {
@Test @Test
fun onBackPressedDefaultTest() { fun onBackPressedDefaultTest() {
var i = 0 var i = 0
doReturn(0).`when`(loginView).currentViewPosition() doReturn(0).`when`(loginView).currentViewIndex
presenter.onBackPressed { i++ } presenter.onBackPressed { i++ }
assertNotEquals(0, i) assertNotEquals(0, i)
} }

View File

@ -1,10 +1,12 @@
package io.github.wulkanowy.ui.main package io.github.wulkanowy.ui.main
import io.github.wulkanowy.data.ErrorHandler import io.github.wulkanowy.data.ErrorHandler
import io.github.wulkanowy.data.repositories.PreferencesRepository
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.* import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
class MainPresenterTest { class MainPresenterTest {
@ -12,6 +14,9 @@ class MainPresenterTest {
@Mock @Mock
lateinit var errorHandler: ErrorHandler lateinit var errorHandler: ErrorHandler
@Mock
lateinit var prefRepository: PreferencesRepository
@Mock @Mock
lateinit var mainView: MainView lateinit var mainView: MainView
@ -22,7 +27,7 @@ class MainPresenterTest {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
clearInvocations(mainView) clearInvocations(mainView)
presenter = MainPresenter(errorHandler) presenter = MainPresenter(errorHandler, prefRepository)
presenter.onAttachView(mainView) presenter.onAttachView(mainView)
} }
@ -36,12 +41,5 @@ class MainPresenterTest {
presenter.onTabSelected(1, false) presenter.onTabSelected(1, false)
verify(mainView).switchMenuView(1) verify(mainView).switchMenuView(1)
} }
@Test
fun onMenuFragmentChangeTest() {
doReturn("Test").`when`(mainView).viewTitle(1)
presenter.onMenuViewChange(1)
verify(mainView).setViewTitle("Test")
}
} }

View File

@ -11,7 +11,6 @@ buildscript {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.android.tools.build:gradle:3.2.1' classpath 'com.android.tools.build:gradle:3.2.1'
classpath "io.fabric.tools:gradle:1.26.0" classpath "io.fabric.tools:gradle:1.26.0"
classpath "com.google.gms:oss-licenses:0.9.2"
classpath "com.github.triplet.gradle:play-publisher:1.2.2" classpath "com.github.triplet.gradle:play-publisher:1.2.2"
classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.6.2" classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.6.2"
} }