Updated dependencies

This commit is contained in:
Sebastian Seedorf
2021-11-08 00:07:48 +01:00
parent 3b14696b5d
commit cf7c7afe61
24 changed files with 204 additions and 153 deletions

View File

@@ -1,17 +1,16 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
apply plugin: 'androidx.navigation.safeargs.kotlin' apply plugin: 'androidx.navigation.safeargs.kotlin'
android { android {
compileSdkVersion 29 compileSdkVersion 31
buildToolsVersion "29.0.3" buildToolsVersion "30.0.2"
defaultConfig { defaultConfig {
applicationId "de.sebse.fuplanner2" applicationId "de.sebse.fuplanner2"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 29 targetSdkVersion 31
versionCode 1 versionCode 1
versionName "1.0" versionName "1.0"
@@ -31,6 +30,10 @@ android {
} }
} }
buildFeatures {
viewBinding true
}
dataBinding { dataBinding {
enabled = true enabled = true
} }
@@ -52,28 +55,26 @@ android {
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.core:core-ktx:1.2.0' implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'com.google.android.material:material:1.1.0' implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.navigation:navigation-fragment-ktx:2.2.1' implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.2.1' implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
implementation 'androidx.fragment:fragment:1.2.4' implementation 'androidx.fragment:fragment-ktx:1.3.6'
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation 'com.android.volley:volley:1.1.1' implementation 'com.android.volley:volley:1.2.1'
implementation 'androidx.room:room-runtime:2.2.5' implementation 'androidx.room:room-runtime:2.3.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0'
kapt 'androidx.room:room-compiler:2.2.5' kapt 'androidx.room:room-compiler:2.3.0'
implementation 'androidx.room:room-ktx:2.2.5' implementation 'androidx.room:room-ktx:2.3.0'
implementation 'com.beust:klaxon:5.0.1' implementation 'com.beust:klaxon:5.5'
implementation 'androidx.work:work-runtime-ktx:2.3.4' implementation 'androidx.work:work-runtime-ktx:2.7.0'
implementation 'androidx.paging:paging-runtime:2.1.2' implementation 'androidx.paging:paging-runtime-ktx:3.0.1'
implementation 'com.github.thellmund.android-week-view:core:4.1.5' implementation 'com.github.thellmund:android-week-view:5.3.2'
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
debugImplementation 'com.amitshekhar.android:debug-db:1.0.6'
} }

View File

@@ -15,8 +15,10 @@
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/FUTheme" android:theme="@style/FUTheme"
android:name=".CustomApplication"> android:name=".CustomApplication">
<activity android:name=".StartupActivity" <activity
android:theme="@style/FUTheme.NoActionBar"> android:name=".StartupActivity"
android:theme="@style/FUTheme.NoActionBar"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@@ -29,8 +31,10 @@
</activity> </activity>
<activity android:name=".auth.FuplannerAccountActivity" <activity android:name=".auth.FuplannerAccountActivity"
android:theme="@style/FUTheme" /> android:theme="@style/FUTheme" />
<service android:name=".auth.FuplannerAccountService" <service
android:permission="de.sebse.fuauth"> android:name=".auth.FuplannerAccountService"
android:permission="de.sebse.fuauth"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.accounts.AccountAuthenticator" /> <action android:name="android.accounts.AccountAuthenticator" />
</intent-filter> </intent-filter>

View File

@@ -12,6 +12,7 @@ import androidx.core.os.bundleOf
import androidx.lifecycle.* import androidx.lifecycle.*
import androidx.navigation.NavOptions import androidx.navigation.NavOptions
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.setupActionBarWithNavController import androidx.navigation.ui.setupActionBarWithNavController
@@ -20,9 +21,8 @@ import de.sebse.fuplanner2.auth.AppAccounts
import de.sebse.fuplanner2.database.AppDatabase import de.sebse.fuplanner2.database.AppDatabase
import de.sebse.fuplanner2.database.Course import de.sebse.fuplanner2.database.Course
import de.sebse.fuplanner2.database.User import de.sebse.fuplanner2.database.User
import de.sebse.fuplanner2.utils.console import de.sebse.fuplanner2.databinding.ActivityMainBinding
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.android.synthetic.main.app_bar_main.*
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -31,30 +31,33 @@ class MainActivity() : AppCompatActivity() {
private lateinit var appBarConfiguration: AppBarConfiguration private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var activityViewModel: MainActivityViewModel private lateinit var activityViewModel: MainActivityViewModel
private lateinit var binding: ActivityMainBinding
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)
setSupportActionBar(toolbar) val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
binding = ActivityMainBinding.inflate(layoutInflater)
setSupportActionBar(binding.appBarMain.toolbar)
val navController = findNavController(R.id.nav_host_fragment)
activityViewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java) activityViewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java)
activityViewModel.user.observe(this) { activityViewModel.user.observe(this) {
nav_view.getHeaderView(0).run { binding.navView.getHeaderView(0).run {
findViewById<TextView>(R.id.nav_header_title).text = getString(R.string.full_name, it?.firstName, it?.lastName) findViewById<TextView>(R.id.nav_header_title).text = getString(R.string.full_name, it?.firstName, it?.lastName)
findViewById<TextView>(R.id.nav_header_subtitle).text = it?.email findViewById<TextView>(R.id.nav_header_subtitle).text = it?.email
} }
} }
activityViewModel.notificationCnt.observe(this) { activityViewModel.notificationCnt.observe(this) {
nav_view.menu.findItem(R.id.nav_notifications).apply { binding.navView.menu.findItem(R.id.nav_notifications).apply {
icon = ContextCompat.getDrawable( icon = ContextCompat.getDrawable(
this@MainActivity, this@MainActivity,
if (it == 0) R.drawable.ic_menu_notifications_none else R.drawable.ic_menu_notifications if (it == 0) R.drawable.ic_menu_notifications_none else R.drawable.ic_menu_notifications
) )
actionView = if (it == 0) actionView = if (it == 0)
null null
else layoutInflater.inflate(R.layout.nav_action_view_counter, nav_view, false).apply { else layoutInflater.inflate(R.layout.nav_action_view_counter, binding.navView, false).apply {
findViewById<TextView>(R.id.counterText).text = when (it) { findViewById<TextView>(R.id.counterText).text = when (it) {
in 0..99 -> it.toString() in 0..99 -> it.toString()
else -> "99+" else -> "99+"
@@ -63,23 +66,23 @@ class MainActivity() : AppCompatActivity() {
} }
} }
activityViewModel.latestSemester.observe(this) { activityViewModel.latestSemester.observe(this) {
val courseOrder = nav_view.menu.findItem(R.id.nav_courses).order val courseOrder = binding.navView.menu.findItem(R.id.nav_courses).order
var i = nav_view.menu.size() - 1 var i = binding.navView.menu.size() - 1
while(i >= 0) { while(i >= 0) {
val menuItem: MenuItem = nav_view.menu.getItem(i--) val menuItem: MenuItem = binding.navView.menu.getItem(i--)
if (menuItem.order / 100 == courseOrder / 100 && menuItem.order != courseOrder) { if (menuItem.order / 100 == courseOrder / 100 && menuItem.order != courseOrder) {
nav_view.menu.removeItem(menuItem.itemId) binding.navView.menu.removeItem(menuItem.itemId)
} }
} }
it.mapIndexed { index, course -> it.mapIndexed { index, course ->
val itemOrder = courseOrder / 100 * 100 + index + 2 val itemOrder = courseOrder / 100 * 100 + index + 2
nav_view.menu.add(0, itemOrder, itemOrder, course.title).setOnMenuItemClickListener { binding.navView.menu.add(0, itemOrder, itemOrder, course.title).setOnMenuItemClickListener {
navController.navigate( navController.navigate(
R.id.course_details, R.id.course_details,
bundleOf("courseId" to course.uid, "title" to course.title), bundleOf("courseId" to course.uid, "title" to course.title),
NavOptions.Builder().setPopUpTo(R.id.nav_courses, false).build() NavOptions.Builder().setPopUpTo(R.id.nav_courses, false).build()
) )
drawer_layout.closeDrawers() binding.drawerLayout.closeDrawers()
false false
} }
} }
@@ -87,10 +90,10 @@ class MainActivity() : AppCompatActivity() {
appBarConfiguration = AppBarConfiguration( appBarConfiguration = AppBarConfiguration(
setOf(R.id.nav_courses, R.id.nav_canteen, R.id.nav_schedule, R.id.nav_notifications), setOf(R.id.nav_courses, R.id.nav_canteen, R.id.nav_schedule, R.id.nav_notifications),
drawer_layout binding.drawerLayout
) )
setupActionBarWithNavController(navController, appBarConfiguration) setupActionBarWithNavController(navController, appBarConfiguration)
nav_view.setupWithNavController(navController) binding.navView.setupWithNavController(navController)
if (intent.getBooleanExtra(EXTRA_OPEN_NOTIFICATIONS, false)) { if (intent.getBooleanExtra(EXTRA_OPEN_NOTIFICATIONS, false)) {
navController.navigate(R.id.nav_notifications) navController.navigate(R.id.nav_notifications)
@@ -138,6 +141,7 @@ class MainActivityViewModel : ViewModel() {
val notificationCnt: LiveData<Int> = database.notificationDao().getUnreadRowCount() val notificationCnt: LiveData<Int> = database.notificationDao().getUnreadRowCount()
val latestSemester: LiveData<List<Course>> = database.courseDao().getLatestSemester() val latestSemester: LiveData<List<Course>> = database.courseDao().getLatestSemester()
@DelicateCoroutinesApi
fun updateSelectedUser(account: Account) { fun updateSelectedUser(account: Account) {
GlobalScope.launch { GlobalScope.launch {
user.postValue(database.userDao().findByUsername(account.name)) user.postValue(database.userDao().findByUsername(account.name))

View File

@@ -29,7 +29,7 @@ abstract class FUAuthModule {
if (response.networkResponse.statusCode == 200) { if (response.networkResponse.statusCode == 200) {
return parseResponse(response.body) return parseResponse(response.body)
} else { } else {
val relLocation = response.headers["Location"] val relLocation = response.headers?.get("Location")
?: throw invalidResponse(100110, "No IDP form location!") ?: throw invalidResponse(100110, "No IDP form location!")
val formUri = URI(samlUrl).resolve(relLocation).toString() val formUri = URI(samlUrl).resolve(relLocation).toString()
requester.head(formUri, getCookies(user)) requester.head(formUri, getCookies(user))
@@ -72,8 +72,8 @@ abstract class FUAuthModule {
} }
private fun updateCookies(user: User, response: NetData) { private fun updateCookies(user: User, response: NetData) {
val setCookies = parseCookies(response.networkResponse.allHeaders) val setCookies = response.networkResponse.allHeaders?.let { parseCookies(it) }
setCookies["JSESSIONID"]?.let { setCookies?.get("JSESSIONID")?.let {
user.cookies.idpJsessionId = it user.cookies.idpJsessionId = it
} }
} }

View File

@@ -42,7 +42,7 @@ object Blackboard: FUAuthModule() {
override suspend fun login(ctx: Context, name: String, password: String, user: User) { override suspend fun login(ctx: Context, name: String, password: String, user: User) {
val requester = Requester(ctx) val requester = Requester(ctx)
var response = requester.head(LOGIN_URL, cookies = getCookies(user, shib = true)) var response = requester.head(LOGIN_URL, cookies = getCookies(user, shib = true))
val samlUri = response.headers["Location"] val samlUri = response.headers?.get("Location")
?: throw invalidResponse(101100, "Location header not set!") ?: throw invalidResponse(101100, "Location header not set!")
if (!samlUri.startsWith(RESTORE_SESSION_URI)) { if (!samlUri.startsWith(RESTORE_SESSION_URI)) {
@@ -56,13 +56,13 @@ object Blackboard: FUAuthModule() {
updateCookies(user, response) updateCookies(user, response)
// Finish BB // Finish BB
response = requester.get( response = requester.get(
response.networkResponse.headers["Location"] ?: throw invalidResponse(101101, "No Location header to finish Blackboard"), response.networkResponse.headers?.get("Location") ?: throw invalidResponse(101101, "No Location header to finish Blackboard"),
getCookies(user, shib = true) getCookies(user, shib = true)
) )
} }
// Start Session // Start Session
response = requester.get( response = requester.get(
response.networkResponse.headers["Location"] ?: throw invalidResponse(101102, "No Location header to start Blackboard session"), response.networkResponse.headers?.get("Location") ?: throw invalidResponse(101102, "No Location header to start Blackboard session"),
getCookies(user, shib = true) getCookies(user, shib = true)
) )
@@ -88,19 +88,19 @@ object Blackboard: FUAuthModule() {
} }
private fun updateCookies(user: User, response: NetData) { private fun updateCookies(user: User, response: NetData) {
val setCookies = parseCookies(response.networkResponse.allHeaders) val setCookies = response.networkResponse.allHeaders?.let { parseCookies(it) }
setCookies["JSESSIONID"]?.let { setCookies?.get("JSESSIONID")?.let {
user.cookies.bbJsessionId = it user.cookies.bbJsessionId = it
} }
setCookies["session_id"]?.let { setCookies?.get("session_id")?.let {
user.cookies.bbSessionId = it user.cookies.bbSessionId = it
} }
setCookies["s_session_id"]?.let { setCookies?.get("s_session_id")?.let {
user.cookies.bbSSessionId = it user.cookies.bbSSessionId = it
} }
setCookies setCookies
.filter{ (key, _) -> key.startsWith("_shibsession_") } ?.filter{ (key, _) -> key.startsWith("_shibsession_") }
.forEach { (key, value) -> ?.forEach { (key, value) ->
user.cookies.bbShibKey = key user.cookies.bbShibKey = key
user.cookies.bbShibValue = value user.cookies.bbShibValue = value
return@forEach return@forEach

View File

@@ -31,7 +31,7 @@ suspend fun Blackboard.getEvents(ctx: Context, database: AppDatabase, user: User
null null
).let { ).let {
Regex("lv/([0-9]+)\\?") Regex("lv/([0-9]+)\\?")
.find(it.headers["Location"] ?: "") .find(it.headers?.get("Location") ?: "")
?.groups ?.groups
?.get(1) ?.get(1)
?.value ?.value

View File

@@ -72,7 +72,7 @@ class CustomRequest: Request<NetData> {
} }
override fun parseNetworkResponse(response: NetworkResponse): Response<NetData>? { override fun parseNetworkResponse(response: NetworkResponse): Response<NetData>? {
val parsed: String = parse(response.data, response.headers) ?: "" val parsed: String = response.headers?.let { parse(response.data, it) } ?: ""
return Response.success(NetData(parsed, response), HttpHeaderParser.parseCacheHeaders(response)) return Response.success(NetData(parsed, response), HttpHeaderParser.parseCacheHeaders(response))
} }

View File

@@ -16,14 +16,16 @@ import androidx.work.workDataOf
import de.sebse.fuplanner2.R import de.sebse.fuplanner2.R
import de.sebse.fuplanner2.auth.AppAccounts import de.sebse.fuplanner2.auth.AppAccounts
import de.sebse.fuplanner2.database.Course import de.sebse.fuplanner2.database.Course
import de.sebse.fuplanner2.databinding.ActivityMainBinding
import de.sebse.fuplanner2.databinding.FragmentRefreshRecyclerBinding
import de.sebse.fuplanner2.worker.AbstractAccountWorker.Companion.KEY_ACCOUNT_NAME import de.sebse.fuplanner2.worker.AbstractAccountWorker.Companion.KEY_ACCOUNT_NAME
import de.sebse.fuplanner2.worker.CourseWorker import de.sebse.fuplanner2.worker.CourseWorker
import kotlinx.android.synthetic.main.fragment_refresh_recycler.view.*
class CoursesFragment : Fragment() { class CoursesFragment : Fragment() {
private lateinit var coursesViewModel: CoursesViewModel private lateinit var coursesViewModel: CoursesViewModel
private lateinit var navController: NavController private lateinit var navController: NavController
private lateinit var binding: FragmentRefreshRecyclerBinding
override fun onCreateView( override fun onCreateView(
@@ -34,13 +36,14 @@ class CoursesFragment : Fragment() {
val viewManager = LinearLayoutManager(context) val viewManager = LinearLayoutManager(context)
val viewAdapter = CoursesAdapter(this::onClick) val viewAdapter = CoursesAdapter(this::onClick)
coursesViewModel = ViewModelProvider(this).get(CoursesViewModel::class.java) coursesViewModel = ViewModelProvider(this).get(CoursesViewModel::class.java)
binding = FragmentRefreshRecyclerBinding.inflate(inflater, container, false)
return inflater.inflate(R.layout.fragment_refresh_recycler, container, false).apply { return inflater.inflate(R.layout.fragment_refresh_recycler, container, false).apply {
recycler_view.apply { binding.recyclerView.apply {
//setHasFixedSize(true) //setHasFixedSize(true)
layoutManager = viewManager layoutManager = viewManager
adapter = viewAdapter adapter = viewAdapter
} }
swipe_refresh_layout.setOnRefreshListener { binding.swipeRefreshLayout.setOnRefreshListener {
val work = OneTimeWorkRequestBuilder<CourseWorker>() val work = OneTimeWorkRequestBuilder<CourseWorker>()
.setInputData(workDataOf( .setInputData(workDataOf(
KEY_ACCOUNT_NAME to AppAccounts.getInstance().selectedAccount?.name KEY_ACCOUNT_NAME to AppAccounts.getInstance().selectedAccount?.name
@@ -50,9 +53,9 @@ class CoursesFragment : Fragment() {
.enqueue(work) .enqueue(work)
WorkManager.getInstance(context.applicationContext) WorkManager.getInstance(context.applicationContext)
.getWorkInfoByIdLiveData(work.id) .getWorkInfoByIdLiveData(work.id)
.observe(viewLifecycleOwner, Observer { .observe(viewLifecycleOwner, {
if (it.state.isFinished) if (it.state.isFinished)
swipe_refresh_layout.isRefreshing = false binding.swipeRefreshLayout.isRefreshing = false
}) })
} }
coursesViewModel.text.observe(viewLifecycleOwner, Observer { coursesViewModel.text.observe(viewLifecycleOwner, Observer {

View File

@@ -17,12 +17,11 @@ import androidx.work.workDataOf
import de.sebse.fuplanner2.R import de.sebse.fuplanner2.R
import de.sebse.fuplanner2.auth.AppAccounts import de.sebse.fuplanner2.auth.AppAccounts
import de.sebse.fuplanner2.database.Lecturer import de.sebse.fuplanner2.database.Lecturer
import de.sebse.fuplanner2.utils.console import de.sebse.fuplanner2.databinding.FragmentRefreshRecyclerBinding
import de.sebse.fuplanner2.utils.enqueueOneTimeWork import de.sebse.fuplanner2.utils.enqueueOneTimeWork
import de.sebse.fuplanner2.worker.AbstractAccountWorker.Companion.KEY_ACCOUNT_NAME import de.sebse.fuplanner2.worker.AbstractAccountWorker.Companion.KEY_ACCOUNT_NAME
import de.sebse.fuplanner2.worker.AbstractAccountWorker.Companion.KEY_COURSE_ID import de.sebse.fuplanner2.worker.AbstractAccountWorker.Companion.KEY_COURSE_ID
import de.sebse.fuplanner2.worker.CourseWorker import de.sebse.fuplanner2.worker.CourseWorker
import kotlinx.android.synthetic.main.fragment_refresh_recycler.view.*
class DetailsFragment : Fragment() { class DetailsFragment : Fragment() {
@@ -31,6 +30,7 @@ class DetailsFragment : Fragment() {
private val args: DetailsFragmentArgs by navArgs() private val args: DetailsFragmentArgs by navArgs()
private val detailsViewModel: DetailsViewModel by viewModels { DetailsViewModelFactory(args.courseId) } private val detailsViewModel: DetailsViewModel by viewModels { DetailsViewModelFactory(args.courseId) }
private lateinit var navController: NavController private lateinit var navController: NavController
private lateinit var binding: FragmentRefreshRecyclerBinding
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
@@ -39,13 +39,13 @@ class DetailsFragment : Fragment() {
val viewManager = LinearLayoutManager(context) val viewManager = LinearLayoutManager(context)
val viewAdapter = DetailsAdapter(this::launchFragment, this::sendMail) val viewAdapter = DetailsAdapter(this::launchFragment, this::sendMail)
return inflater.inflate(R.layout.fragment_refresh_recycler, container, false).apply { return inflater.inflate(R.layout.fragment_refresh_recycler, container, false).apply {
recycler_view.apply { binding.recyclerView.apply {
setHasFixedSize(true) setHasFixedSize(true)
layoutManager = viewManager layoutManager = viewManager
adapter = viewAdapter adapter = viewAdapter
} }
swipe_refresh_layout.setOnRefreshListener { binding.swipeRefreshLayout.setOnRefreshListener {
enqueueOneTimeWork<CourseWorker>(context.applicationContext) { enqueueOneTimeWork<CourseWorker>(context.applicationContext) {
it.setInputData(workDataOf( it.setInputData(workDataOf(
KEY_ACCOUNT_NAME to AppAccounts.getInstance().selectedAccount?.name, KEY_ACCOUNT_NAME to AppAccounts.getInstance().selectedAccount?.name,
@@ -53,7 +53,7 @@ class DetailsFragment : Fragment() {
)) ))
}.observe(viewLifecycleOwner, Observer { }.observe(viewLifecycleOwner, Observer {
if (it.state.isFinished) if (it.state.isFinished)
swipe_refresh_layout.isRefreshing = false binding.swipeRefreshLayout.isRefreshing = false
}) })
} }
detailsViewModel.course.observe(viewLifecycleOwner, Observer { detailsViewModel.course.observe(viewLifecycleOwner, Observer {

View File

@@ -8,7 +8,7 @@ import de.sebse.fuplanner2.database.Course
class DetailsViewModelFactory(private val courseId: Long): ViewModelProvider.NewInstanceFactory() { class DetailsViewModelFactory(private val courseId: Long): ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T = DetailsViewModel(courseId) as T override fun <T : ViewModel> create(modelClass: Class<T>): T = DetailsViewModel(courseId) as T
} }
class DetailsViewModel(courseId: Long) : ViewModel() { class DetailsViewModel(courseId: Long) : ViewModel() {

View File

@@ -14,12 +14,12 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.work.workDataOf import androidx.work.workDataOf
import de.sebse.fuplanner2.R import de.sebse.fuplanner2.R
import de.sebse.fuplanner2.auth.AppAccounts import de.sebse.fuplanner2.auth.AppAccounts
import de.sebse.fuplanner2.databinding.FragmentRefreshRecyclerBinding
import de.sebse.fuplanner2.utils.console import de.sebse.fuplanner2.utils.console
import de.sebse.fuplanner2.utils.enqueueOneTimeWork import de.sebse.fuplanner2.utils.enqueueOneTimeWork
import de.sebse.fuplanner2.worker.AbstractAccountWorker.Companion.KEY_ACCOUNT_NAME import de.sebse.fuplanner2.worker.AbstractAccountWorker.Companion.KEY_ACCOUNT_NAME
import de.sebse.fuplanner2.worker.AbstractAccountWorker.Companion.KEY_COURSE_ID import de.sebse.fuplanner2.worker.AbstractAccountWorker.Companion.KEY_COURSE_ID
import de.sebse.fuplanner2.worker.AnnouncementWorker import de.sebse.fuplanner2.worker.AnnouncementWorker
import kotlinx.android.synthetic.main.fragment_refresh_recycler.view.*
class AnnouncementsFragment : Fragment() { class AnnouncementsFragment : Fragment() {
@@ -27,6 +27,7 @@ class AnnouncementsFragment : Fragment() {
private val args: AnnouncementsFragmentArgs by navArgs() private val args: AnnouncementsFragmentArgs by navArgs()
private val announcementsViewModel: AnnouncementsViewModel by viewModels { AnnouncementsViewModelFactory(args.courseId) } private val announcementsViewModel: AnnouncementsViewModel by viewModels { AnnouncementsViewModelFactory(args.courseId) }
private lateinit var navController: NavController private lateinit var navController: NavController
private lateinit var binding: FragmentRefreshRecyclerBinding
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
@@ -35,14 +36,14 @@ class AnnouncementsFragment : Fragment() {
val viewManager = LinearLayoutManager(context) val viewManager = LinearLayoutManager(context)
val viewAdapter = AnnouncementsAdapter() val viewAdapter = AnnouncementsAdapter()
return inflater.inflate(R.layout.fragment_refresh_recycler, container, false).apply { return inflater.inflate(R.layout.fragment_refresh_recycler, container, false).apply {
console.log(findViewById(R.id.recycler_view), recycler_view) console.log(findViewById(R.id.recycler_view), binding.recyclerView)
recycler_view.apply { binding.recyclerView.apply {
setHasFixedSize(true) setHasFixedSize(true)
layoutManager = viewManager layoutManager = viewManager
adapter = viewAdapter adapter = viewAdapter
} }
swipe_refresh_layout.setOnRefreshListener { binding.swipeRefreshLayout.setOnRefreshListener {
enqueueOneTimeWork<AnnouncementWorker>(context.applicationContext) { enqueueOneTimeWork<AnnouncementWorker>(context.applicationContext) {
it.setInputData(workDataOf( it.setInputData(workDataOf(
KEY_ACCOUNT_NAME to AppAccounts.getInstance().selectedAccount?.name, KEY_ACCOUNT_NAME to AppAccounts.getInstance().selectedAccount?.name,
@@ -50,7 +51,7 @@ class AnnouncementsFragment : Fragment() {
)) ))
}.observe(viewLifecycleOwner, Observer { }.observe(viewLifecycleOwner, Observer {
if (it.state.isFinished) if (it.state.isFinished)
swipe_refresh_layout.isRefreshing = false binding.swipeRefreshLayout.isRefreshing = false
}) })
} }
announcementsViewModel.events.observe(viewLifecycleOwner, Observer { announcementsViewModel.events.observe(viewLifecycleOwner, Observer {

View File

@@ -9,7 +9,7 @@ import de.sebse.fuplanner2.database.Announcement
import de.sebse.fuplanner2.database.AppDatabase import de.sebse.fuplanner2.database.AppDatabase
class AnnouncementsViewModelFactory(private val courseId: Long): ViewModelProvider.NewInstanceFactory() { class AnnouncementsViewModelFactory(private val courseId: Long): ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T = AnnouncementsViewModel(courseId) as T override fun <T : ViewModel> create(modelClass: Class<T>): T = AnnouncementsViewModel(courseId) as T
} }
class AnnouncementsViewModel(courseId: Long) : ViewModel() { class AnnouncementsViewModel(courseId: Long) : ViewModel() {

View File

@@ -14,9 +14,9 @@ import androidx.navigation.NavController
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import de.sebse.fuplanner2.R import de.sebse.fuplanner2.R
import de.sebse.fuplanner2.databinding.FragmentDescriptionBinding
import de.sebse.fuplanner2.ui.details.DetailsViewModel import de.sebse.fuplanner2.ui.details.DetailsViewModel
import de.sebse.fuplanner2.ui.details.DetailsViewModelFactory import de.sebse.fuplanner2.ui.details.DetailsViewModelFactory
import kotlinx.android.synthetic.main.fragment_description.view.*
class DescriptionFragment : Fragment() { class DescriptionFragment : Fragment() {
@@ -25,6 +25,7 @@ class DescriptionFragment : Fragment() {
private val args: DescriptionFragmentArgs by navArgs() private val args: DescriptionFragmentArgs by navArgs()
private val detailsViewModel: DetailsViewModel by viewModels { DetailsViewModelFactory(args.courseId) } private val detailsViewModel: DetailsViewModel by viewModels { DetailsViewModelFactory(args.courseId) }
private lateinit var navController: NavController private lateinit var navController: NavController
private lateinit var binding: FragmentDescriptionBinding
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
@@ -33,10 +34,10 @@ class DescriptionFragment : Fragment() {
return inflater.inflate(R.layout.fragment_description, container, false).apply { return inflater.inflate(R.layout.fragment_description, container, false).apply {
val nightModeFlags = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK val nightModeFlags = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
if (nightModeFlags == Configuration.UI_MODE_NIGHT_YES && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (nightModeFlags == Configuration.UI_MODE_NIGHT_YES && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
description.settings.forceDark = WebSettings.FORCE_DARK_ON binding.description.settings.forceDark = WebSettings.FORCE_DARK_ON
} }
detailsViewModel.course.observe(viewLifecycleOwner, Observer { detailsViewModel.course.observe(viewLifecycleOwner, Observer {
description.loadDataWithBaseURL("", it.description, "text/html", "UTF-8", "") binding.description.loadDataWithBaseURL("", it.description, "text/html", "UTF-8", "")
}) })
navController = findNavController() navController = findNavController()
} }

View File

@@ -14,12 +14,12 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.work.workDataOf import androidx.work.workDataOf
import de.sebse.fuplanner2.R import de.sebse.fuplanner2.R
import de.sebse.fuplanner2.auth.AppAccounts import de.sebse.fuplanner2.auth.AppAccounts
import de.sebse.fuplanner2.databinding.FragmentRefreshRecyclerBinding
import de.sebse.fuplanner2.utils.enqueueOneTimeWork import de.sebse.fuplanner2.utils.enqueueOneTimeWork
import de.sebse.fuplanner2.worker.AbstractAccountWorker.Companion.KEY_ACCOUNT_NAME import de.sebse.fuplanner2.worker.AbstractAccountWorker.Companion.KEY_ACCOUNT_NAME
import de.sebse.fuplanner2.worker.AbstractAccountWorker.Companion.KEY_COURSE_ID import de.sebse.fuplanner2.worker.AbstractAccountWorker.Companion.KEY_COURSE_ID
import de.sebse.fuplanner2.worker.CourseWorker import de.sebse.fuplanner2.worker.CourseWorker
import de.sebse.fuplanner2.worker.EventWorker import de.sebse.fuplanner2.worker.EventWorker
import kotlinx.android.synthetic.main.fragment_refresh_recycler.view.*
class EventsFragment : Fragment() { class EventsFragment : Fragment() {
@@ -28,6 +28,7 @@ class EventsFragment : Fragment() {
private val args: EventsFragmentArgs by navArgs() private val args: EventsFragmentArgs by navArgs()
private val eventsViewModel: EventsViewModel by viewModels { EventsViewModelFactory(args.courseId) } private val eventsViewModel: EventsViewModel by viewModels { EventsViewModelFactory(args.courseId) }
private lateinit var navController: NavController private lateinit var navController: NavController
private lateinit var binding: FragmentRefreshRecyclerBinding
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
@@ -36,13 +37,13 @@ class EventsFragment : Fragment() {
val viewManager = LinearLayoutManager(context) val viewManager = LinearLayoutManager(context)
val viewAdapter = EventsAdapter() val viewAdapter = EventsAdapter()
return inflater.inflate(R.layout.fragment_refresh_recycler, container, false).apply { return inflater.inflate(R.layout.fragment_refresh_recycler, container, false).apply {
recycler_view.apply { binding.recyclerView.apply {
setHasFixedSize(true) setHasFixedSize(true)
layoutManager = viewManager layoutManager = viewManager
adapter = viewAdapter adapter = viewAdapter
} }
swipe_refresh_layout.setOnRefreshListener { binding.swipeRefreshLayout.setOnRefreshListener {
enqueueOneTimeWork<EventWorker>(context.applicationContext) { enqueueOneTimeWork<EventWorker>(context.applicationContext) {
it.setInputData(workDataOf( it.setInputData(workDataOf(
KEY_ACCOUNT_NAME to AppAccounts.getInstance().selectedAccount?.name, KEY_ACCOUNT_NAME to AppAccounts.getInstance().selectedAccount?.name,
@@ -50,7 +51,7 @@ class EventsFragment : Fragment() {
)) ))
}.observe(viewLifecycleOwner, Observer { }.observe(viewLifecycleOwner, Observer {
if (it.state.isFinished) if (it.state.isFinished)
swipe_refresh_layout.isRefreshing = false binding.swipeRefreshLayout.isRefreshing = false
}) })
} }
eventsViewModel.events.observe(viewLifecycleOwner, Observer { eventsViewModel.events.observe(viewLifecycleOwner, Observer {

View File

@@ -10,7 +10,7 @@ import de.sebse.fuplanner2.database.Event
class EventsViewModelFactory(private val courseId: Long): ViewModelProvider.NewInstanceFactory() { class EventsViewModelFactory(private val courseId: Long): ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T = EventsViewModel(courseId) as T override fun <T : ViewModel> create(modelClass: Class<T>): T = EventsViewModel(courseId) as T
} }
class EventsViewModel(courseId: Long) : ViewModel() { class EventsViewModel(courseId: Long) : ViewModel() {

View File

@@ -10,7 +10,8 @@ import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import de.sebse.fuplanner2.R import de.sebse.fuplanner2.R
import de.sebse.fuplanner2.database.AppDatabase import de.sebse.fuplanner2.database.AppDatabase
import kotlinx.android.synthetic.main.fragment_recycler.view.* import de.sebse.fuplanner2.databinding.FragmentRecyclerBinding
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -19,6 +20,7 @@ class NotificationFragment : Fragment() {
private lateinit var viewModel: NotificationViewModel private lateinit var viewModel: NotificationViewModel
private lateinit var navController: NavController private lateinit var navController: NavController
private lateinit var binding: FragmentRecyclerBinding
init { init {
setHasOptionsMenu(true) setHasOptionsMenu(true)
@@ -43,7 +45,7 @@ class NotificationFragment : Fragment() {
}) })
return inflater.inflate(R.layout.fragment_recycler, container, false).apply { return inflater.inflate(R.layout.fragment_recycler, container, false).apply {
recycler_view.apply { binding.recyclerView.apply {
//setHasFixedSize(true) //setHasFixedSize(true)
layoutManager = viewManager layoutManager = viewManager
adapter = viewAdapter adapter = viewAdapter

View File

@@ -9,15 +9,16 @@ import androidx.annotation.ColorInt
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.alamkanak.weekview.WeekView import com.alamkanak.weekview.WeekView
import com.alamkanak.weekview.WeekViewDisplayable import com.alamkanak.weekview.WeekViewDisplayable
import com.alamkanak.weekview.WeekViewEntity
import com.alamkanak.weekview.WeekViewEvent import com.alamkanak.weekview.WeekViewEvent
import de.sebse.fuplanner2.R import de.sebse.fuplanner2.R
import de.sebse.fuplanner2.database.AppDatabase import de.sebse.fuplanner2.database.AppDatabase
import de.sebse.fuplanner2.database.Course import de.sebse.fuplanner2.database.Course
import de.sebse.fuplanner2.database.Event import de.sebse.fuplanner2.database.Event
import de.sebse.fuplanner2.ui.schedule.MyCustomPagingAdapter.LoadMoreHandler
import de.sebse.fuplanner2.utils.getHtmlSpannedString import de.sebse.fuplanner2.utils.getHtmlSpannedString
import de.sebse.fuplanner2.utils.toTimeString import de.sebse.fuplanner2.utils.toTimeString
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -32,7 +33,7 @@ class ScheduleFragment : Fragment() {
private val scheduleViewModel: ScheduleViewModel by viewModels { ScheduleViewModelFactory() } private val scheduleViewModel: ScheduleViewModel by viewModels { ScheduleViewModelFactory() }
private var currentDate = Calendar.getInstance() private var currentDate = Calendar.getInstance()
private var weekView: WeekView<ContextEvent>? = null private var weekView: WeekView? = null
init { init {
setHasOptionsMenu(true) setHasOptionsMenu(true)
@@ -48,11 +49,6 @@ class ScheduleFragment : Fragment() {
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
scheduleViewModel.eventModel.observe(viewLifecycleOwner, Observer {
weekView?.submit(it.map { ContextEvent(requireContext(), it) })
weekView?.notifyDataSetChanged()
})
val navController = findNavController() val navController = findNavController()
var alertCourse: Course? = null var alertCourse: Course? = null
val alertDialog = AlertDialog.Builder(requireContext()) val alertDialog = AlertDialog.Builder(requireContext())
@@ -69,50 +65,56 @@ class ScheduleFragment : Fragment() {
return inflater.inflate(R.layout.fragment_schedule, container, false).apply { return inflater.inflate(R.layout.fragment_schedule, container, false).apply {
weekView = findViewById<WeekView<ContextEvent>>(R.id.weekView).apply { weekView = findViewById<WeekView>(R.id.weekView).apply {
setOnLoadMoreListener { start, end -> val pagingAdapter = MyCustomPagingAdapter(object : LoadMoreHandler {
scheduleViewModel.loadBetween(start.timeInMillis, end.timeInMillis) override fun setOnLoadMoreListener(startDate: Calendar, endDate: Calendar) =
} scheduleViewModel.loadBetween(startDate.timeInMillis, endDate.timeInMillis)
setOnRangeChangeListener { firstVisibleDate, _ -> override fun setOnRangeChangeListener(
currentDate = firstVisibleDate.clone() as Calendar firstVisibleDate: Calendar,
} lastVisibleDate: Calendar
) {
currentDate = firstVisibleDate.clone() as Calendar
}
setOnEventClickListener { data, _ -> override fun setOnEventClickListener(data: ContextEvent) {
alertDialog.run { alertDialog.run {
GlobalScope.launch { GlobalScope.launch {
val course: Course? = withContext(Dispatchers.IO) { val course: Course = withContext(Dispatchers.IO) {
AppDatabase.getInstance().courseDao().getCourseById2(data.event.courseId) AppDatabase.getInstance().courseDao().getCourseById2(data.event.courseId)
} }
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext")
val cs: CharSequence = SpannableStringBuilder().apply { val cs: CharSequence = SpannableStringBuilder().apply {
if (course != null) {
this.append(context.getHtmlSpannedString(R.string.dialog_course_br, course.title)) this.append(context.getHtmlSpannedString(R.string.dialog_course_br, course.title))
if (!data.event.location.isNullOrBlank()) {
this.append(context.getHtmlSpannedString(R.string.dialog_location_br, data.event.location))
}
this.append(context.getHtmlSpannedString(
R.string.dialog_time,
data.event.startDateTime.toTimeString(context),
(data.event.startDateTime + data.event.duration).toTimeString(context)
))
} }
if (!data.event.location.isNullOrBlank()) { withContext(Dispatchers.Main) {
this.append(context.getHtmlSpannedString(R.string.dialog_location_br, data.event.location)) alertCourse = course
setTitle(data.event.title)
setMessage(cs)
show()
} }
this.append(context.getHtmlSpannedString(
R.string.dialog_time,
data.event.startDateTime.toTimeString(context),
(data.event.startDateTime + data.event.duration).toTimeString(context)
))
}
withContext(Dispatchers.Main) {
alertCourse = course
setTitle(data.event.title)
setMessage(cs)
show()
} }
} }
} }
} })
scheduleViewModel.eventModel.observe(viewLifecycleOwner, {
pagingAdapter.updateEntries(it.map { event -> ContextEvent(requireContext(), event) })
})
val todayDate = Calendar.getInstance() val todayDate = Calendar.getInstance()
todayDate.firstDayOfWeek = Calendar.SATURDAY todayDate.firstDayOfWeek = Calendar.SATURDAY
todayDate.set(Calendar.DAY_OF_WEEK, Calendar.SATURDAY) todayDate.set(Calendar.DAY_OF_WEEK, Calendar.SATURDAY)
todayDate.add(Calendar.DAY_OF_WEEK, 2) todayDate.add(Calendar.DAY_OF_WEEK, 2)
goToDate(todayDate) scrollToDate(todayDate)
} }
} }
} }
@@ -124,18 +126,18 @@ class ScheduleFragment : Fragment() {
currentDate.firstDayOfWeek = Calendar.TUESDAY currentDate.firstDayOfWeek = Calendar.TUESDAY
currentDate.set(Calendar.DAY_OF_WEEK, Calendar.TUESDAY) currentDate.set(Calendar.DAY_OF_WEEK, Calendar.TUESDAY)
currentDate.add(Calendar.DAY_OF_WEEK, -1) currentDate.add(Calendar.DAY_OF_WEEK, -1)
weekView?.goToDate(currentDate) weekView?.scrollToDate(currentDate)
true true
} }
R.id.next_week -> { R.id.next_week -> {
currentDate.firstDayOfWeek = Calendar.MONDAY currentDate.firstDayOfWeek = Calendar.MONDAY
currentDate.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY) currentDate.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY)
currentDate.add(Calendar.DAY_OF_WEEK, 7) currentDate.add(Calendar.DAY_OF_WEEK, 7)
weekView?.goToDate(currentDate) weekView?.scrollToDate(currentDate)
true true
} }
R.id.go_to_today -> { R.id.go_to_today -> {
weekView?.goToToday() weekView?.scrollToDate(Calendar.getInstance())
true true
} }
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
@@ -143,22 +145,21 @@ class ScheduleFragment : Fragment() {
} }
} }
data class ContextEvent(val actCtx: Context, val event: Event): WeekViewDisplayable<ContextEvent> { data class ContextEvent(val actCtx: Context, val event: Event) {
override fun toWeekViewEvent(): WeekViewEvent<ContextEvent> { fun toWeekViewEvent(): WeekViewEntity {
// Build the styling of the event, for instance background color and strike-through // Build the styling of the event, for instance background color and strike-through
val style = WeekViewEvent.Style.Builder() val style = WeekViewEntity.Style.Builder()
.setBackgroundColor(getColor(actCtx, event.courseId)) .setBackgroundColor(getColor(actCtx, event.courseId))
.setTextStrikeThrough(false)
.setTextColorResource(R.color.scheduleOtherText) .setTextColorResource(R.color.scheduleOtherText)
.build() .build()
// Build the WeekViewEvent via the Builder // Build the WeekViewEvent via the Builder
return WeekViewEvent.Builder(this) return WeekViewEntity.Event.Builder(this)
.setId(event.getHash().toLong()) .setId(event.getHash().toLong())
.setTitle(event.title) .setTitle(event.title)
.setStartTime(Calendar.getInstance().apply { timeInMillis = event.startDateTime }) .setStartTime(Calendar.getInstance().apply { timeInMillis = event.startDateTime })
.setEndTime(Calendar.getInstance().apply { timeInMillis = event.startDateTime + event.duration }) .setEndTime(Calendar.getInstance().apply { timeInMillis = event.startDateTime + event.duration })
.setLocation(event.location ?: "") .setSubtitle(event.location ?: "")
.setAllDay(false) .setAllDay(false)
.setStyle(style) .setStyle(style)
.build() .build()
@@ -214,3 +215,35 @@ data class ContextEvent(val actCtx: Context, val event: Event): WeekViewDisplaya
return 0xFF000000.toInt() + (r*0xFF).roundToInt() * 0x10000 + (g*0xFF).roundToInt() * 0x100 + (b*0xFF).roundToInt() return 0xFF000000.toInt() + (r*0xFF).roundToInt() * 0x10000 + (g*0xFF).roundToInt() * 0x100 + (b*0xFF).roundToInt()
} }
} }
class MyCustomPagingAdapter(
private val loadMoreHandler: LoadMoreHandler
) : WeekView.PagingAdapter<ContextEvent>() {
interface LoadMoreHandler {
fun setOnLoadMoreListener(startDate: Calendar, endDate: Calendar)
fun setOnRangeChangeListener(firstVisibleDate: Calendar, lastVisibleDate: Calendar)
fun setOnEventClickListener(data: ContextEvent)
}
override fun onCreateEntity(item: ContextEvent): WeekViewEntity {
return item.toWeekViewEvent()
}
override fun onLoadMore(startDate: Calendar, endDate: Calendar) {
loadMoreHandler.setOnLoadMoreListener(startDate, endDate)
}
override fun onRangeChanged(firstVisibleDate: Calendar, lastVisibleDate: Calendar) {
loadMoreHandler.setOnRangeChangeListener(firstVisibleDate, lastVisibleDate)
}
override fun onEventClick(data: ContextEvent) {
loadMoreHandler.setOnEventClickListener(data)
}
fun updateEntries(elements: List<ContextEvent>) {
this.submitList(elements)
this.refresh()
}
}

View File

@@ -14,7 +14,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class ScheduleViewModelFactory(): ViewModelProvider.NewInstanceFactory() { class ScheduleViewModelFactory(): ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T = ScheduleViewModel() as T override fun <T : ViewModel> create(modelClass: Class<T>): T = ScheduleViewModel() as T
} }
class ScheduleViewModel() : ViewModel() { class ScheduleViewModel() : ViewModel() {

View File

@@ -40,7 +40,7 @@ object Whiteboard: FUAuthModule() {
override suspend fun login(ctx: Context, name: String, password: String, user: User) { override suspend fun login(ctx: Context, name: String, password: String, user: User) {
val requester = Requester(ctx) val requester = Requester(ctx)
val request = requester.get(LOGIN_URL, cookies = getCookies(user, shib = true)) val request = requester.get(LOGIN_URL, cookies = getCookies(user, shib = true))
val samlUri = request.headers["Location"] val samlUri = request.headers?.get("Location")
?: throw invalidResponse(102100, "Location header not set!") ?: throw invalidResponse(102100, "Location header not set!")
if (samlUri == RESTORE_ON_REDIRECT_TO) { if (samlUri == RESTORE_ON_REDIRECT_TO) {
updateCookies(user, request) updateCookies(user, request)
@@ -60,7 +60,7 @@ object Whiteboard: FUAuthModule() {
updateCookies(user, response) updateCookies(user, response)
// Finish BB & Start Session // Finish BB & Start Session
response = requester.get( response = requester.get(
response.networkResponse.headers["Location"] ?: throw invalidResponse(102101, "No Location header to finish Blackboard"), response.networkResponse.headers?.get("Location") ?: throw invalidResponse(102101, "No Location header to finish Blackboard"),
getCookies(user, shib = true) getCookies(user, shib = true)
) )
@@ -77,13 +77,13 @@ object Whiteboard: FUAuthModule() {
} }
private fun updateCookies(user: User, response: NetData) { private fun updateCookies(user: User, response: NetData) {
val setCookies = parseCookies(response.networkResponse.allHeaders) val setCookies = response.networkResponse.allHeaders?.let { parseCookies(it) }
setCookies["JSESSIONID"]?.let { setCookies?.get("JSESSIONID")?.let {
user.cookies.wbJsessionId = it user.cookies.wbJsessionId = it
} }
setCookies setCookies
.filter{ (key, _) -> key.startsWith("_shibsession_") } ?.filter{ (key, _) -> key.startsWith("_shibsession_") }
.forEach { (key, value) -> ?.forEach { (key, value) ->
user.cookies.wbShibKey = key user.cookies.wbShibKey = key
user.cookies.wbShibValue = value user.cookies.wbShibValue = value
return@forEach return@forEach

View File

@@ -9,6 +9,7 @@
tools:openDrawer="start"> tools:openDrawer="start">
<include <include
android:id="@+id/app_bar_main"
layout="@layout/app_bar_main" layout="@layout/app_bar_main"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />

View File

@@ -7,7 +7,7 @@
app:layout_behavior="@string/appbar_scrolling_view_behavior" app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:showIn="@layout/app_bar_main"> tools:showIn="@layout/app_bar_main">
<fragment <androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment" android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment" android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@@ -11,19 +11,18 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:columnGap="0dp" app:columnGap="0dp"
app:headerRowPadding="12dp" app:headerPadding="12dp"
app:hourHeight="45dp" app:hourHeight="45dp"
app:maxHour="20" app:maxHour="20"
app:minHour="7" app:minHour="7"
app:numberOfVisibleDays="5" app:numberOfVisibleDays="5"
app:showCompleteDay="false" app:showCompleteDay="false"
app:showDistinctPastFutureColor="true"
app:timeColumnPadding="8dp" app:timeColumnPadding="8dp"
app:timeColumnTextSize="10sp" app:timeColumnTextSize="10sp"
app:daySeparatorColor="@color/scheduleSeparator" app:daySeparatorColor="@color/scheduleSeparator"
app:futureBackgroundColor="@color/scheduleFutureBackground" app:futureBackgroundColor="@color/scheduleFutureBackground"
app:headerRowBackgroundColor="@color/scheduleNavBackground" app:headerBackgroundColor="@color/scheduleNavBackground"
app:headerRowTextColor="@color/scheduleOtherText" app:headerTextColor="@color/scheduleOtherText"
app:hourSeparatorColor="@color/scheduleSeparator" app:hourSeparatorColor="@color/scheduleSeparator"
app:pastBackgroundColor="@color/schedulePastBackground" app:pastBackgroundColor="@color/schedulePastBackground"
app:timeColumnBackgroundColor="@color/scheduleNavBackground" app:timeColumnBackgroundColor="@color/scheduleNavBackground"

View File

@@ -1,15 +1,16 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = '1.3.71' ext.kotlin_version = '1.5.31'
repositories { repositories {
google() google()
jcenter() mavenCentral()
maven { url 'https://jitpack.io' }
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.6.2' classpath 'com.android.tools.build:gradle:7.0.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.2.1' classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
@@ -19,7 +20,7 @@ buildscript {
allprojects { allprojects {
repositories { repositories {
google() google()
jcenter() mavenCentral()
maven { url 'https://jitpack.io' } maven { url 'https://jitpack.io' }
} }
} }

View File

@@ -1,6 +1,6 @@
#Sun Mar 15 15:45:08 CET 2020 #Sun Nov 07 21:12:26 CET 2021
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip